feat(local): highlight JSON with twinkleplop #270
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: PR Risk (Jev) | ||
|
Check warning on line 1 in .github/workflows/pr-risk-jev.yml
|
||
| on: | ||
| pull_request_target: | ||
| types: [opened, synchronize, reopened, ready_for_review, edited] | ||
| workflow_dispatch: | ||
| inputs: | ||
| pr_number: | ||
| description: Pull request number to analyze | ||
| required: true | ||
| type: number | ||
| permissions: | ||
| contents: read | ||
| pull-requests: write | ||
| concurrency: | ||
| group: pr-risk-jev-${{ github.event.pull_request.number || inputs.pr_number }} | ||
| cancel-in-progress: true | ||
| jobs: | ||
| risk: | ||
| name: PR risk (Jev) | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 10 | ||
| if: github.event_name == 'workflow_dispatch' || github.event.pull_request.draft == false | ||
| steps: | ||
| # The Action reads PR Git objects without checking out or executing PR code. | ||
| - name: Classify PR risk | ||
| id: risk | ||
| uses: getsentry/pr-risk-action@45de582ca688251eab50b4c7cea096d46756cd4d | ||
| env: | ||
| AI_GATEWAY_API_KEY: ${{ secrets.AI_GATEWAY_API_KEY }} | ||
| with: | ||
| repo: ${{ github.repository }} | ||
| pr-number: ${{ github.event.pull_request.number || inputs.pr_number }} | ||
| expected-head-sha: ${{ github.event.pull_request.head.sha }} | ||
| result-path: risk-pr-result.json | ||
| - name: Publish risk label | ||
| if: ${{ !cancelled() && steps.risk.outcome != 'skipped' }} | ||
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 | ||
| env: | ||
| PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }} | ||
| with: | ||
| script: | | ||
| // Only this trusted workflow publishes labels; PR source is never executed. | ||
| const fs = require('node:fs'); | ||
| if (!fs.existsSync('risk-pr-result.json')) { | ||
| core.warning('Risk analysis did not produce a result; no label was published.'); | ||
| return; | ||
| } | ||
| const result = JSON.parse(fs.readFileSync('risk-pr-result.json', 'utf8')); | ||
| const prNumber = Number(process.env.PR_NUMBER); | ||
| const repo = context.repo; | ||
| const colors = { low: '0E8A16', medium: 'FBCA04', high: 'B60205' }; | ||
| const riskLabels = Object.keys(colors).map(risk => `risk: ${risk}`); | ||
| if (!Number.isSafeInteger(prNumber) || prNumber <= 0 | ||
| || result.number !== prNumber || result.repo !== `${repo.owner}/${repo.repo}` | ||
| || typeof result.status !== 'string' | ||
| || (result.status === 'ok' && !Object.hasOwn(colors, result.risk_label))) { | ||
| throw new Error('Risk result does not match this PR or a valid risk class.'); | ||
| } | ||
| const snapshot = result.snapshot; | ||
| if (result.status === 'pr_changed' || !snapshot?.source_head_sha || !snapshot?.source_base_sha) { | ||
| core.warning('Risk analysis has no current revision; no label was published.'); | ||
| return; | ||
| } | ||
| const { data: pr } = await github.rest.pulls.get({ ...repo, pull_number: prNumber }); | ||
| if (pr.state !== 'open' || pr.head.sha !== snapshot.source_head_sha | ||
| || pr.base.sha !== snapshot.source_base_sha) { | ||
| core.warning('PR changed after risk analysis; no label was published.'); | ||
| return; | ||
| } | ||
| const target = result.status === 'ok' ? `risk: ${result.risk_label}` : null; | ||
| const existing = pr.labels.map(label => label.name); | ||
| if (target && !existing.includes(target)) { | ||
| try { | ||
| await github.rest.issues.getLabel({ ...repo, name: target }); | ||
| } catch (error) { | ||
| if (error.status !== 404) throw error; | ||
| try { | ||
| await github.rest.issues.createLabel({ | ||
| ...repo, name: target, color: colors[result.risk_label], | ||
| description: `PR risk score: ${result.risk_label}`, | ||
| }); | ||
| } catch (error) { | ||
| if (error.status !== 422) throw error; | ||
| // Another PR may have created the shared repository label. | ||
| await github.rest.issues.getLabel({ ...repo, name: target }); | ||
| } | ||
| } | ||
| await github.rest.issues.addLabels({ ...repo, issue_number: prNumber, labels: [target] }); | ||
| } | ||
| // A failed classification must not leave an earlier low/medium/high verdict. | ||
| for (const name of existing.filter(name => riskLabels.includes(name) && name !== target)) { | ||
| try { | ||
| await github.rest.issues.removeLabel({ ...repo, issue_number: prNumber, name }); | ||
| } catch (error) { | ||
| if (error.status !== 404) throw error; | ||
| } | ||
| } | ||
| const { data: latest } = await github.rest.pulls.get({ ...repo, pull_number: prNumber }); | ||
| if (target && (latest.state !== 'open' || latest.head.sha !== pr.head.sha | ||
| || latest.base.sha !== pr.base.sha || latest.title !== pr.title || latest.body !== pr.body)) { | ||
| await github.rest.issues.removeLabel({ ...repo, issue_number: prNumber, name: target }); | ||
| core.warning('PR changed while publishing; removed the outdated risk label.'); | ||
| return; | ||
| } | ||
| if (!target) core.warning(`PR remains unclassified: ${result.status}. Previous risk labels were cleared.`); | ||
| core.info(target ? `Published ${target}` : 'No risk label published.'); | ||
| - name: Upload risk result | ||
| if: always() | ||
| uses: actions/upload-artifact@v7 | ||
| with: | ||
| name: pr-risk-jev-${{ github.event.pull_request.number || inputs.pr_number }} | ||
| path: risk-pr-result.json | ||
| if-no-files-found: warn | ||
| retention-days: 30 | ||