Skip to content

Commit fdf3ceb

Browse files
Update codacy.yml
1 parent 9082566 commit fdf3ceb

File tree

1 file changed

+143
-205
lines changed

1 file changed

+143
-205
lines changed

.github/workflows/codacy.yml

Lines changed: 143 additions & 205 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
name: Sync Codacy Issues
32

43
on:
@@ -9,227 +8,166 @@ on:
98
required: false
109
default: "false"
1110
push:
12-
# branches to consider in the event; optional, defaults to all
1311
branches:
1412
- master
13+
- main
14+
1515
jobs:
1616
sync-codacy-issues:
1717
runs-on: ubuntu-latest
18+
permissions:
19+
issues: write
20+
contents: read
21+
1822
steps:
1923
- name: Checkout
2024
uses: actions/checkout@v4
2125

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
2232
- name: Fetch Codacy Issues
33+
env:
34+
CODACY_API_TOKEN: ${{ secrets.CODACY_API_TOKEN }}
2335
run: |
2436
curl --request POST \
2537
--url "https://app.codacy.com/api/v3/analysis/organizations/gh/${{ github.repository_owner }}/repositories/${{ github.event.repository.name }}/issues/search" \
26-
--header "api-token: ${{ secrets.CODACY_API_TOKEN }}" \
38+
--header "api-token: $CODACY_API_TOKEN" \
2739
--header "content-type: application/json" \
2840
--data '{"levels":["Error","Warning","High"]}' \
2941
--silent \
3042
--fail \
31-
-o issues.json
32-
33-
- name: Extract issues
34-
run: jq '.data' issues.json > filtered_issues.json
35-
36-
- name: Create GitHub Issues
37-
uses: actions/github-script@v7
38-
with:
39-
script: |
40-
const fs = require('fs');
41-
const dryRun = "${{ github.event.inputs.dry_run }}" === "true";
42-
43-
const rawIssues = JSON.parse(fs.readFileSync('filtered_issues.json', 'utf8'));
44-
45-
const grouped = {};
46-
for (const issue of rawIssues) {
47-
grouped[issue.issueId] = grouped[issue.issueId] || [];
48-
grouped[issue.issueId].push(issue);
49-
}
50-
51-
const openIssues = await github.paginate(
52-
github.rest.issues.listForRepo,
53-
{
54-
owner: context.repo.owner,
55-
repo: context.repo.repo,
56-
state: "open"
57-
}
58-
);
59-
60-
console.log(`Fetched ${openIssues.length} open issues from GitHub`);
61-
62-
const existingIds = new Set();
63-
for (const ghIssue of openIssues) {
64-
const matches = ghIssue.body?.match(/codacy-issue-([a-f0-9]+)/g);
65-
if (matches) {
66-
matches.forEach(m => existingIds.add(m.replace("codacy-issue-", "")));
67-
}
68-
}
69-
70-
console.log(`Found ${existingIds.size} existing Codacy issues in GitHub`);
71-
72-
for (const [issueId, issues] of Object.entries(grouped)) {
73-
if (existingIds.has(issueId)) {
74-
console.log(`Skipping duplicate Codacy issueId ${issueId}`);
75-
continue;
76-
}
77-
78-
const key = `codacy-issue-${issueId}`;
79-
const first = issues[0];
80-
const title = `[Codacy] ${first.patternInfo.severityLevel} issue(s) in ${first.filePath}`;
81-
82-
let body = `Codacy detected **${issues.length}** occurrence(s) of rule \`${first.patternInfo.id}\`:\n\n`;
83-
for (const issue of issues) {
84-
body += `- **${issue.patternInfo.severityLevel}** at \`${issue.filePath}:${issue.lineNumber}\` → ${issue.message}\n`;
85-
}
86-
body += `\nSee full details in [Codacy Report](https://app.codacy.com/gh/${context.repo.owner}/${context.repo.repo}/issues)\n\n`;
87-
body += `Unique ID: \`${key}\``;
88-
89-
if (dryRun) {
90-
console.log(`[DRY RUN] Would create issue: ${title}`);
91-
} else {
92-
await github.rest.issues.create({
93-
owner: context.repo.owner,
94-
repo: context.repo.repo,
95-
title,
96-
body,
97-
labels: ["codacy"]
98-
});
99-
console.log(`✅ Created GitHub issue for Codacy issueId ${issueId}`);
100-
await new Promise(resolve => setTimeout(resolve, 2000));
101-
}
102-
}
103-
104-
- name: Close Resolved GitHub Issues
105-
uses: actions/github-script@v7
106-
with:
107-
script: |
108-
const fs = require('fs');
109-
const dryRun = "${{ github.event.inputs.dry_run }}" === "true";
110-
const rawIssues = JSON.parse(fs.readFileSync('filtered_issues.json', 'utf8'));
111-
112-
// Build current Codacy set (only *active* issues, not ignored)
113-
const currentCodacyIds = new Set(
114-
rawIssues.filter(i => !i.ignored).map(i => i.issueId)
115-
);
116-
117-
// Build ignored Codacy set
118-
const ignoredCodacyIds = new Set(
119-
rawIssues.filter(i => i.ignored).map(i => i.issueId)
120-
);
121-
122-
// Fetch ALL GitHub issues with codacy label
123-
const allIssues = await github.paginate(
124-
github.rest.issues.listForRepo,
125-
{
126-
owner: context.repo.owner,
127-
repo: context.repo.repo,
128-
state: "all",
129-
labels: ["codacy"]
130-
}
131-
);
43+
-o codacy_issues.json
13244
133-
for (const ghIssue of allIssues) {
134-
const matches = ghIssue.body?.match(/codacy-issue-([a-f0-9]+)/g);
135-
if (!matches) continue;
136-
137-
for (const match of matches) {
138-
const issueId = match.replace("codacy-issue-", "");
139-
140-
// Close if not active OR explicitly ignored
141-
if ((!currentCodacyIds.has(issueId) || ignoredCodacyIds.has(issueId))
142-
&& ghIssue.state === "open") {
143-
if (dryRun) {
144-
console.log(`[DRY RUN] Would close issue #${ghIssue.number} (Codacy issueId ${issueId})`);
145-
} else {
146-
// Add comment before closing
147-
const reason = ignoredCodacyIds.has(issueId)
148-
? "Auto closed because Codacy issue is marked as *ignored*"
149-
: "Auto closed as not found in last analysis";
150-
151-
await github.rest.issues.createComment({
152-
owner: context.repo.owner,
153-
repo: context.repo.repo,
154-
issue_number: ghIssue.number,
155-
body: reason
156-
});
157-
158-
await github.rest.issues.update({
159-
owner: context.repo.owner,
160-
repo: context.repo.repo,
161-
issue_number: ghIssue.number,
162-
state: "closed"
163-
});
164-
console.log(`❌ Closed GitHub issue #${ghIssue.number} for Codacy issueId ${issueId}`);
165-
}
166-
}
167-
}
168-
}
169-
170-
- name: Close Duplicate Codacy Issues
171-
uses: actions/github-script@v7
172-
with:
173-
script: |
174-
const dryRun = "${{ github.event.inputs.dry_run }}" === "true";
175-
176-
// Fetch all issues with the codacy label (open + closed)
177-
const allIssues = await github.paginate(
178-
github.rest.issues.listForRepo,
179-
{
180-
owner: context.repo.owner,
181-
repo: context.repo.repo,
182-
state: "all",
183-
labels: ["codacy"]
184-
}
185-
);
186-
187-
const grouped = {};
188-
189-
for (const issue of allIssues) {
190-
const matches = issue.body?.match(/codacy-issue-([a-f0-9]+)/g);
191-
if (!matches) continue;
192-
193-
for (const match of matches) {
194-
const issueId = match.replace("codacy-issue-", "");
195-
if (!grouped[issueId]) grouped[issueId] = [];
196-
grouped[issueId].push(issue);
197-
}
198-
}
199-
200-
for (const [issueId, issues] of Object.entries(grouped)) {
201-
if (issues.length <= 1) continue; // No duplicates
202-
203-
// Sort by creation date descending (newest first)
204-
issues.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
205-
206-
const [latest, ...duplicates] = issues;
207-
console.log(`Found ${issues.length} duplicates for Codacy issueId ${issueId}. Keeping #${latest.number}.`);
208-
209-
for (const dup of duplicates) {
210-
if (dup.state === "closed") continue;
211-
212-
if (dryRun) {
213-
console.log(`[DRY RUN] Would close duplicate issue #${dup.number} (Codacy issueId ${issueId})`);
214-
} else {
215-
await github.rest.issues.createComment({
216-
owner: context.repo.owner,
217-
repo: context.repo.repo,
218-
issue_number: dup.number,
219-
body: `Auto-closing as duplicate of newer Codacy issue #${latest.number} for ID \`${issueId}\`.`
220-
});
221-
222-
await github.rest.issues.update({
223-
owner: context.repo.owner,
224-
repo: context.repo.repo,
225-
issue_number: dup.number,
226-
state: "closed"
227-
});
228-
229-
console.log(`❌ Closed duplicate issue #${dup.number} (Codacy issueId ${issueId})`);
230-
}
231-
}
232-
}
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\`
23397

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

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)