Skip to content

Commit 901f639

Browse files
Update codacy.yml
1 parent d6bdb7c commit 901f639

File tree

1 file changed

+143
-204
lines changed

1 file changed

+143
-204
lines changed

.github/workflows/codacy.yml

Lines changed: 143 additions & 204 deletions
Original file line numberDiff line numberDiff line change
@@ -8,227 +8,166 @@ on:
88
required: false
99
default: "false"
1010
push:
11-
# branches to consider in the event; optional, defaults to all
1211
branches:
1312
- master
13+
- main
14+
1415
jobs:
1516
sync-codacy-issues:
1617
runs-on: ubuntu-latest
18+
permissions:
19+
issues: write
20+
contents: read
21+
1722
steps:
1823
- name: Checkout
1924
uses: actions/checkout@v4
2025

26+
- name: Install jq and gh
27+
run: |
28+
sudo apt-get update -y
29+
sudo apt-get install -y jq gh
30+
31+
# 🔹 STEP 1: Fetch Codacy Issues
2132
- name: Fetch Codacy Issues
33+
env:
34+
CODACY_API_TOKEN: ${{ secrets.CODACY_API_TOKEN }}
2235
run: |
2336
curl --request POST \
2437
--url "https://app.codacy.com/api/v3/analysis/organizations/gh/${{ github.repository_owner }}/repositories/${{ github.event.repository.name }}/issues/search" \
25-
--header "api-token: ${{ secrets.CODACY_API_TOKEN }}" \
38+
--header "api-token: $CODACY_API_TOKEN" \
2639
--header "content-type: application/json" \
2740
--data '{"levels":["Error","Warning","High"]}' \
2841
--silent \
2942
--fail \
30-
-o issues.json
31-
32-
- name: Extract issues
33-
run: jq '.data' issues.json > filtered_issues.json
34-
35-
- name: Create GitHub Issues
36-
uses: actions/github-script@v7
37-
with:
38-
script: |
39-
const fs = require('fs');
40-
const dryRun = "${{ github.event.inputs.dry_run }}" === "true";
41-
42-
const rawIssues = JSON.parse(fs.readFileSync('filtered_issues.json', 'utf8'));
43-
44-
const grouped = {};
45-
for (const issue of rawIssues) {
46-
grouped[issue.issueId] = grouped[issue.issueId] || [];
47-
grouped[issue.issueId].push(issue);
48-
}
49-
50-
const openIssues = await github.paginate(
51-
github.rest.issues.listForRepo,
52-
{
53-
owner: context.repo.owner,
54-
repo: context.repo.repo,
55-
state: "open"
56-
}
57-
);
58-
59-
console.log(`Fetched ${openIssues.length} open issues from GitHub`);
60-
61-
const existingIds = new Set();
62-
for (const ghIssue of openIssues) {
63-
const matches = ghIssue.body?.match(/codacy-issue-([a-f0-9]+)/g);
64-
if (matches) {
65-
matches.forEach(m => existingIds.add(m.replace("codacy-issue-", "")));
66-
}
67-
}
68-
69-
console.log(`Found ${existingIds.size} existing Codacy issues in GitHub`);
70-
71-
for (const [issueId, issues] of Object.entries(grouped)) {
72-
if (existingIds.has(issueId)) {
73-
console.log(`Skipping duplicate Codacy issueId ${issueId}`);
74-
continue;
75-
}
76-
77-
const key = `codacy-issue-${issueId}`;
78-
const first = issues[0];
79-
const title = `[Codacy] ${first.patternInfo.severityLevel} issue(s) in ${first.filePath}`;
80-
81-
let body = `Codacy detected **${issues.length}** occurrence(s) of rule \`${first.patternInfo.id}\`:\n\n`;
82-
for (const issue of issues) {
83-
body += `- **${issue.patternInfo.severityLevel}** at \`${issue.filePath}:${issue.lineNumber}\` → ${issue.message}\n`;
84-
}
85-
body += `\nSee full details in [Codacy Report](https://app.codacy.com/gh/${context.repo.owner}/${context.repo.repo}/issues)\n\n`;
86-
body += `Unique ID: \`${key}\``;
87-
88-
if (dryRun) {
89-
console.log(`[DRY RUN] Would create issue: ${title}`);
90-
} else {
91-
await github.rest.issues.create({
92-
owner: context.repo.owner,
93-
repo: context.repo.repo,
94-
title,
95-
body,
96-
labels: ["codacy"]
97-
});
98-
console.log(`✅ Created GitHub issue for Codacy issueId ${issueId}`);
99-
await new Promise(resolve => setTimeout(resolve, 2000));
100-
}
101-
}
102-
103-
- name: Close Resolved GitHub Issues
104-
uses: actions/github-script@v7
105-
with:
106-
script: |
107-
const fs = require('fs');
108-
const dryRun = "${{ github.event.inputs.dry_run }}" === "true";
109-
const rawIssues = JSON.parse(fs.readFileSync('filtered_issues.json', 'utf8'));
110-
111-
// Build current Codacy set (only *active* issues, not ignored)
112-
const currentCodacyIds = new Set(
113-
rawIssues.filter(i => !i.ignored).map(i => i.issueId)
114-
);
115-
116-
// Build ignored Codacy set
117-
const ignoredCodacyIds = new Set(
118-
rawIssues.filter(i => i.ignored).map(i => i.issueId)
119-
);
120-
121-
// Fetch ALL GitHub issues with codacy label
122-
const allIssues = await github.paginate(
123-
github.rest.issues.listForRepo,
124-
{
125-
owner: context.repo.owner,
126-
repo: context.repo.repo,
127-
state: "all",
128-
labels: ["codacy"]
129-
}
130-
);
131-
132-
for (const ghIssue of allIssues) {
133-
const matches = ghIssue.body?.match(/codacy-issue-([a-f0-9]+)/g);
134-
if (!matches) continue;
43+
-o codacy_issues.json
13544
136-
for (const match of matches) {
137-
const issueId = match.replace("codacy-issue-", "");
138-
139-
// Close if not active OR explicitly ignored
140-
if ((!currentCodacyIds.has(issueId) || ignoredCodacyIds.has(issueId))
141-
&& ghIssue.state === "open") {
142-
if (dryRun) {
143-
console.log(`[DRY RUN] Would close issue #${ghIssue.number} (Codacy issueId ${issueId})`);
144-
} else {
145-
// Add comment before closing
146-
const reason = ignoredCodacyIds.has(issueId)
147-
? "Auto closed because Codacy issue is marked as *ignored*"
148-
: "Auto closed as not found in last analysis";
149-
150-
await github.rest.issues.createComment({
151-
owner: context.repo.owner,
152-
repo: context.repo.repo,
153-
issue_number: ghIssue.number,
154-
body: reason
155-
});
156-
157-
await github.rest.issues.update({
158-
owner: context.repo.owner,
159-
repo: context.repo.repo,
160-
issue_number: ghIssue.number,
161-
state: "closed"
162-
});
163-
console.log(`❌ Closed GitHub issue #${ghIssue.number} for Codacy issueId ${issueId}`);
164-
}
165-
}
166-
}
167-
}
168-
169-
- name: Close Duplicate Codacy Issues
170-
uses: actions/github-script@v7
171-
with:
172-
script: |
173-
const dryRun = "${{ github.event.inputs.dry_run }}" === "true";
174-
175-
// Fetch all issues with the codacy label (open + closed)
176-
const allIssues = await github.paginate(
177-
github.rest.issues.listForRepo,
178-
{
179-
owner: context.repo.owner,
180-
repo: context.repo.repo,
181-
state: "all",
182-
labels: ["codacy"]
183-
}
184-
);
185-
186-
const grouped = {};
187-
188-
for (const issue of allIssues) {
189-
const matches = issue.body?.match(/codacy-issue-([a-f0-9]+)/g);
190-
if (!matches) continue;
191-
192-
for (const match of matches) {
193-
const issueId = match.replace("codacy-issue-", "");
194-
if (!grouped[issueId]) grouped[issueId] = [];
195-
grouped[issueId].push(issue);
196-
}
197-
}
198-
199-
for (const [issueId, issues] of Object.entries(grouped)) {
200-
if (issues.length <= 1) continue; // No duplicates
201-
202-
// Sort by creation date descending (newest first)
203-
issues.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
204-
205-
const [latest, ...duplicates] = issues;
206-
console.log(`Found ${issues.length} duplicates for Codacy issueId ${issueId}. Keeping #${latest.number}.`);
207-
208-
for (const dup of duplicates) {
209-
if (dup.state === "closed") continue;
210-
211-
if (dryRun) {
212-
console.log(`[DRY RUN] Would close duplicate issue #${dup.number} (Codacy issueId ${issueId})`);
213-
} else {
214-
await github.rest.issues.createComment({
215-
owner: context.repo.owner,
216-
repo: context.repo.repo,
217-
issue_number: dup.number,
218-
body: `Auto-closing as duplicate of newer Codacy issue #${latest.number} for ID \`${issueId}\`.`
219-
});
220-
221-
await github.rest.issues.update({
222-
owner: context.repo.owner,
223-
repo: context.repo.repo,
224-
issue_number: dup.number,
225-
state: "closed"
226-
});
227-
228-
console.log(`❌ Closed duplicate issue #${dup.number} (Codacy issueId ${issueId})`);
229-
}
230-
}
231-
}
45+
# 🔹 STEP 2: Ensure standard labels exist with your colors
46+
- name: Ensure standard labels exist
47+
env:
48+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
49+
run: |
50+
declare -A LABELS=(
51+
["task"]="Tasks and work items|#aefcde"
52+
["refactor"]="Code cleanup and best practices|#eb69a2"
53+
["bug"]="Bugs and error-prone code|#b60205"
54+
["security"]="Security-related issues|#d93f0b"
55+
["performance"]="Performance optimization issues|#e57504"
56+
)
57+
for label in "${!LABELS[@]}"; do
58+
desc=${LABELS[$label]%%|*}
59+
color=${LABELS[$label]##*|}
60+
echo "Creating/updating label '$label'"
61+
gh label create "$label" --description "$desc" --color "$color" --repo ${{ github.repository }} --force
62+
done
63+
64+
# 🔹 STEP 3: Sync Codacy Issues (create/update)
65+
- name: Sync Codacy Issues
66+
env:
67+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
68+
run: |
69+
dry_run="${{ github.event.inputs.dry_run }}"
70+
current_issues=$(gh issue list --repo ${{ github.repository }} --state open --json number,title,body,labels)
71+
72+
jq -c '.data[]' codacy_issues.json | while read -r issue; do
73+
id=$(echo "$issue" | jq -r '.issueId')
74+
file=$(echo "$issue" | jq -r '.filePath')
75+
line=$(echo "$issue" | jq -r '.lineNumber')
76+
message=$(echo "$issue" | jq -r '.message')
77+
line_text=$(echo "$issue" | jq -r '.lineText // empty')
78+
category=$(echo "$issue" | jq -r '.patternInfo.category')
79+
severity=$(echo "$issue" | jq -r '.patternInfo.severityLevel')
80+
sha=$(echo "$issue" | jq -r '.commitInfo.sha')
81+
82+
title="[$category] $message"
83+
84+
# Prepare the code snippet inline
85+
if [ -n "$line_text" ]; then
86+
# Replace literal \n from JSON and real newlines with space
87+
clean_line_text=$(echo "$line_text" | tr -d '\r' | tr '\n' ' ' | sed 's/\\n/ /g')
88+
code_snippet="\`\`\`
89+
$clean_line_text
90+
\`\`\`"
91+
else
92+
code_snippet=""
93+
fi
94+
95+
# Build the full body with Codacy ID at the top
96+
body="**Codacy ID:** \`$id\`
23297

233-
98+
**File:** \`$file\`
99+
**Line:** $line
100+
**Rule:** $category ($severity)
101+
**Commit:** \`$sha\`"
234102

103+
# Append code snippet if present
104+
if [ -n "$code_snippet" ]; then
105+
body+="
106+
107+
**Code Snippet:**
108+
$code_snippet"
109+
fi
110+
111+
# Determine labels based on message/category
112+
labels=()
113+
if echo "$message" | grep -iq "todo"; then
114+
labels+=("task")
115+
elif echo "$category" | grep -Eiq "best[_ ]?practice|code[_ ]?style|complexity"; then
116+
labels+=("refactor")
117+
elif echo "$message" | grep -iq "security"; then
118+
labels+=("task" "security")
119+
elif echo "$category" | grep -iq "performance"; then
120+
labels+=("task" "performance")
121+
elif echo "$message" | grep -Eiq "error[_ ]?prone"; then
122+
labels+=("bug")
123+
fi
124+
125+
label_string=$(IFS=,; echo "${labels[*]}")
126+
127+
# Check if issue exists
128+
existing_number=$(echo "$current_issues" | jq -r --arg id "$id" '.[] | select(.body | contains($id)) | .number')
129+
130+
if [ -n "$existing_number" ]; then
131+
echo "Updating existing issue #$existing_number ($id)"
132+
if [ "$dry_run" != "true" ]; then
133+
gh issue edit "$existing_number" \
134+
--body "$body" \
135+
--add-label "$label_string" \
136+
--repo ${{ github.repository }}
137+
else
138+
echo "[DRY RUN] Would update issue #$existing_number with labels: $label_string"
139+
fi
140+
else
141+
echo "Creating new issue for Codacy ID $id"
142+
if [ "$dry_run" != "true" ]; then
143+
gh issue create \
144+
--title "$title" \
145+
--body "$body" \
146+
--label "$label_string" \
147+
--repo ${{ github.repository }}
148+
else
149+
echo "[DRY RUN] Would create issue '$title' with labels: $label_string"
150+
fi
151+
fi
152+
done
153+
154+
155+
# 🔹 STEP 4: Close resolved issues
156+
- name: Close resolved issues
157+
env:
158+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
159+
run: |
160+
current_ids=$(jq -r '.data[].issueId' codacy_issues.json)
161+
gh issue list --repo ${{ github.repository }} --state open --label codacy --json number,body | jq -c '.[]' | while read -r issue; do
162+
number=$(echo "$issue" | jq -r '.number')
163+
body=$(echo "$issue" | jq -r '.body')
164+
id=$(echo "$body" | grep -oE 'Codacy ID: [a-f0-9]+' | awk '{print $3}')
165+
if [ -n "$id" ] && ! echo "$current_ids" | grep -q "$id"; then
166+
echo "Closing stale Codacy issue #$number ($id)"
167+
if [ "$dry_run" != "true" ]; then
168+
gh issue close "$number" --comment "This issue no longer appears in Codacy reports and has been auto-closed." --repo ${{ github.repository }}
169+
else
170+
echo "[DRY RUN] Would close issue #$number"
171+
fi
172+
fi
173+
done

0 commit comments

Comments
 (0)