PR comment - subsystem rollup #8
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 comment - subsystem rollup | |
| # Posts the diagnostic subsystem-size rollup as a PR comment after the | |
| # Build workflow finishes. Lives in a separate workflow on purpose: | |
| # `workflow_run` runs in the base-repo trusted context, so this is the | |
| # only place pull-requests:write is granted. The Build job stays | |
| # read-only, which keeps a compromised toolchain or build-script step | |
| # from minting comments on attacker-supplied PRs. | |
| # | |
| # Fork PRs work too: workflow_run.pull_requests is empty for forks, so | |
| # the resolver falls back to a head-SHA search. | |
| on: | |
| workflow_run: | |
| workflows: [Build] | |
| types: [completed] | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| actions: read | |
| jobs: | |
| comment: | |
| name: Upsert subsystem rollup comment | |
| runs-on: ubuntu-24.04 | |
| if: | | |
| github.event.workflow_run.event == 'pull_request' | |
| && github.event.workflow_run.conclusion == 'success' | |
| steps: | |
| - name: Download subsystem rollup artifact | |
| uses: actions/download-artifact@v6 | |
| with: | |
| name: subsystem-rollup | |
| path: rollup | |
| run-id: ${{ github.event.workflow_run.id }} | |
| github-token: ${{ github.token }} | |
| - name: Resolve PR number | |
| id: pr | |
| uses: actions/github-script@v9 | |
| with: | |
| script: | | |
| const run = context.payload.workflow_run; | |
| // workflow_run.pull_requests is populated for same-repo | |
| // PRs and empty for forks; fall back to a head-SHA search | |
| // so fork PRs still get the comment. | |
| const direct = run.pull_requests || []; | |
| if (direct.length > 0) { | |
| core.setOutput('number', String(direct[0].number)); | |
| return; | |
| } | |
| const { owner, repo } = context.repo; | |
| const { data } = await github.rest.search.issuesAndPullRequests({ | |
| q: `repo:${owner}/${repo} is:pr is:open ${run.head_sha}`, | |
| }); | |
| if (data.items.length === 0) { | |
| core.warning(`no open PR matched head SHA ${run.head_sha}`); | |
| core.setOutput('number', ''); | |
| return; | |
| } | |
| core.setOutput('number', String(data.items[0].number)); | |
| - name: Upsert comment | |
| if: steps.pr.outputs.number != '' | |
| uses: actions/github-script@v9 | |
| env: | |
| # Pass the PR number through the environment instead of | |
| # interpolating it directly into the script body. Template | |
| # substitution happens before the JS engine sees the source, | |
| # so any future change that lets a non-numeric value reach | |
| # this output would otherwise be a script-injection vector. | |
| PR_NUMBER: ${{ steps.pr.outputs.number }} | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const candidates = [ | |
| // download-artifact extracts the common parent of the | |
| // uploaded files, so the current artifact lands flat at | |
| // the destination root. | |
| 'rollup/subsystem-rollup.md', | |
| // Keep accepting the older nested layout in case the | |
| // upload path changes again. | |
| 'rollup/profiles/kernel-pgo/none/subsystem-rollup.md', | |
| ]; | |
| const bodyPath = candidates.find(candidate => | |
| fs.existsSync(candidate) | |
| ); | |
| if (!bodyPath) { | |
| throw new Error( | |
| `subsystem rollup markdown not found; checked: ${candidates.join(', ')}` | |
| ); | |
| } | |
| const body = fs.readFileSync(bodyPath, 'utf8'); | |
| const marker = '<!-- subsystem-rollup-comment -->'; | |
| const { owner, repo } = context.repo; | |
| const issue_number = Number(process.env.PR_NUMBER); | |
| const comments = await github.paginate( | |
| github.rest.issues.listComments, | |
| { owner, repo, issue_number, per_page: 100 }, | |
| ); | |
| // Strict login match: github-actions[bot] is the only | |
| // identity this workflow ever posts under. A broader | |
| // user.type === 'Bot' filter would let a third-party bot's | |
| // comment shadow ours and silently break the upsert. | |
| const matches = comments.filter(c => | |
| c.user?.login === 'github-actions[bot]' | |
| && typeof c.body === 'string' | |
| && c.body.includes(marker), | |
| ); | |
| // Update the most recent match and delete older | |
| // duplicates so a crashed prior run cannot leave stale | |
| // gate state visible on the PR. | |
| matches.sort((a, b) => | |
| new Date(b.updated_at) - new Date(a.updated_at) | |
| ); | |
| if (matches.length > 0) { | |
| await github.rest.issues.updateComment({ | |
| owner, repo, comment_id: matches[0].id, body, | |
| }); | |
| for (const stale of matches.slice(1)) { | |
| await github.rest.issues.deleteComment({ | |
| owner, repo, comment_id: stale.id, | |
| }); | |
| } | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner, repo, issue_number, body, | |
| }); | |
| } |