Merge pull request #248 from TransactionProcessing/codacy/high_fixes #2
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}`); | |
| } | |
| } | |
| } | |
| } | |