Update codacy.yml #3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Sync Codacy Issues | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| dry_run: | |
| description: "Run without creating GitHub issues" | |
| required: false | |
| default: "false" | |
| push: | |
| # branches to consider in the event; optional, defaults to all | |
| branches: | |
| - master | |
| jobs: | |
| sync-codacy-issues: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Fetch Codacy Issues | |
| run: | | |
| curl --request POST \ | |
| --url "https://app.codacy.com/api/v3/analysis/organizations/gh/${{ github.repository_owner }}/repositories/${{ github.event.repository.name }}/issues/search" \ | |
| --header "api-token: ${{ secrets.CODACY_API_TOKEN }}" \ | |
| --header "content-type: application/json" \ | |
| --data '{"levels":["Error","Warning","High"]}' \ | |
| --silent \ | |
| --fail \ | |
| -o issues.json | |
| - name: Extract issues | |
| run: jq '.data' issues.json > filtered_issues.json | |
| - name: Create GitHub Issues | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const dryRun = "${{ github.event.inputs.dry_run }}" === "true"; | |
| const rawIssues = JSON.parse(fs.readFileSync('filtered_issues.json', 'utf8')); | |
| const grouped = {}; | |
| for (const issue of rawIssues) { | |
| grouped[issue.issueId] = grouped[issue.issueId] || []; | |
| grouped[issue.issueId].push(issue); | |
| } | |
| const openIssues = await github.paginate( | |
| github.rest.issues.listForRepo, | |
| { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| state: "open" | |
| } | |
| ); | |
| console.log(`Fetched ${openIssues.length} open issues from GitHub`); | |
| const existingIds = new Set(); | |
| for (const ghIssue of openIssues) { | |
| const matches = ghIssue.body?.match(/codacy-issue-([a-f0-9]+)/g); | |
| if (matches) { | |
| matches.forEach(m => existingIds.add(m.replace("codacy-issue-", ""))); | |
| } | |
| } | |
| console.log(`Found ${existingIds.size} existing Codacy issues in GitHub`); | |
| for (const [issueId, issues] of Object.entries(grouped)) { | |
| if (existingIds.has(issueId)) { | |
| console.log(`Skipping duplicate Codacy issueId ${issueId}`); | |
| continue; | |
| } | |
| const key = `codacy-issue-${issueId}`; | |
| const first = issues[0]; | |
| const title = `[Codacy] ${first.patternInfo.severityLevel} issue(s) in ${first.filePath}`; | |
| let body = `Codacy detected **${issues.length}** occurrence(s) of rule \`${first.patternInfo.id}\`:\n\n`; | |
| for (const issue of issues) { | |
| body += `- **${issue.patternInfo.severityLevel}** at \`${issue.filePath}:${issue.lineNumber}\` → ${issue.message}\n`; | |
| } | |
| body += `\nSee full details in [Codacy Report](https://app.codacy.com/gh/${context.repo.owner}/${context.repo.repo}/issues)\n\n`; | |
| body += `Unique ID: \`${key}\``; | |
| if (dryRun) { | |
| console.log(`[DRY RUN] Would create issue: ${title}`); | |
| } else { | |
| await github.rest.issues.create({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| title, | |
| body, | |
| labels: ["codacy"] | |
| }); | |
| console.log(`✅ Created GitHub issue for Codacy issueId ${issueId}`); | |
| await new Promise(resolve => setTimeout(resolve, 2000)); | |
| } | |
| } | |
| - name: Close Resolved GitHub Issues | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const dryRun = "${{ github.event.inputs.dry_run }}" === "true"; | |
| const rawIssues = JSON.parse(fs.readFileSync('filtered_issues.json', 'utf8')); | |
| // Build current Codacy set (only *active* issues, not ignored) | |
| const currentCodacyIds = new Set( | |
| rawIssues.filter(i => !i.ignored).map(i => i.issueId) | |
| ); | |
| // Build ignored Codacy set | |
| const ignoredCodacyIds = new Set( | |
| rawIssues.filter(i => i.ignored).map(i => i.issueId) | |
| ); | |
| // Fetch ALL GitHub issues with codacy label | |
| const allIssues = await github.paginate( | |
| github.rest.issues.listForRepo, | |
| { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| state: "all", | |
| labels: ["codacy"] | |
| } | |
| ); | |
| for (const ghIssue of allIssues) { | |
| const matches = ghIssue.body?.match(/codacy-issue-([a-f0-9]+)/g); | |
| if (!matches) continue; | |
| for (const match of matches) { | |
| const issueId = match.replace("codacy-issue-", ""); | |
| // Close if not active OR explicitly ignored | |
| if ((!currentCodacyIds.has(issueId) || ignoredCodacyIds.has(issueId)) | |
| && ghIssue.state === "open") { | |
| if (dryRun) { | |
| console.log(`[DRY RUN] Would close issue #${ghIssue.number} (Codacy issueId ${issueId})`); | |
| } else { | |
| // Add comment before closing | |
| const reason = ignoredCodacyIds.has(issueId) | |
| ? "Auto closed because Codacy issue is marked as *ignored*" | |
| : "Auto closed as not found in last analysis"; | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: ghIssue.number, | |
| body: reason | |
| }); | |
| await github.rest.issues.update({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: ghIssue.number, | |
| state: "closed" | |
| }); | |
| console.log(`❌ Closed GitHub issue #${ghIssue.number} for Codacy issueId ${issueId}`); | |
| } | |
| } | |
| } | |
| } | |
| - name: Close Duplicate Codacy Issues | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const dryRun = "${{ github.event.inputs.dry_run }}" === "true"; | |
| // Fetch all issues with the codacy label (open + closed) | |
| const allIssues = await github.paginate( | |
| github.rest.issues.listForRepo, | |
| { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| state: "all", | |
| labels: ["codacy"] | |
| } | |
| ); | |
| const grouped = {}; | |
| for (const issue of allIssues) { | |
| const matches = issue.body?.match(/codacy-issue-([a-f0-9]+)/g); | |
| if (!matches) continue; | |
| for (const match of matches) { | |
| const issueId = match.replace("codacy-issue-", ""); | |
| if (!grouped[issueId]) grouped[issueId] = []; | |
| grouped[issueId].push(issue); | |
| } | |
| } | |
| for (const [issueId, issues] of Object.entries(grouped)) { | |
| if (issues.length <= 1) continue; // No duplicates | |
| // Sort by creation date descending (newest first) | |
| issues.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); | |
| const [latest, ...duplicates] = issues; | |
| console.log(`Found ${issues.length} duplicates for Codacy issueId ${issueId}. Keeping #${latest.number}.`); | |
| for (const dup of duplicates) { | |
| if (dup.state === "closed") continue; | |
| if (dryRun) { | |
| console.log(`[DRY RUN] Would close duplicate issue #${dup.number} (Codacy issueId ${issueId})`); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: dup.number, | |
| body: `Auto-closing as duplicate of newer Codacy issue #${latest.number} for ID \`${issueId}\`.` | |
| }); | |
| await github.rest.issues.update({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: dup.number, | |
| state: "closed" | |
| }); | |
| console.log(`❌ Closed duplicate issue #${dup.number} (Codacy issueId ${issueId})`); | |
| } | |
| } | |
| } | |