PR report comment #26
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
| # Post the "Build & analyze" report as a PR comment — including on fork PRs. | |
| # | |
| # The build workflow (pr-build.yml) runs on `pull_request`; for fork PRs GitHub hands it | |
| # a read-only token, so it cannot comment itself. It instead uploads the assembled report | |
| # as the `pr-report` artifact. This workflow is triggered by `workflow_run` when that build | |
| # completes: because `workflow_run` always runs the copy of this file on the DEFAULT BRANCH, | |
| # in the base-repo context, its requested write permissions are actually granted (unlike the | |
| # fork's read-only `pull_request` token). It downloads the artifact and posts the comment. | |
| # | |
| # Security: this job must NOT check out or execute the PR's code. Its only untrusted input | |
| # is the report artifact, which it treats strictly as text to post. | |
| # | |
| # Note: `workflow_run` workflows only ever run from the default branch, so changes here take | |
| # effect once merged to dev and can't be exercised from a PR branch. | |
| name: PR report comment | |
| on: | |
| workflow_run: | |
| workflows: ["Build & analyze"] # must match pr-build.yml's `name:` | |
| types: [completed] | |
| # Cross-run artifact download needs `actions: read`; posting the comment needs | |
| # `pull-requests: write`. An explicit block sets every unlisted scope to `none`. | |
| permissions: | |
| actions: read | |
| pull-requests: write # honored here: this runs in the base-repo context, not the fork's | |
| # Serialize comment runs for the same build so a re-run can't race the find-or-create | |
| # below into duplicate comments. workflow_run.pull_requests is empty for fork PRs, so key | |
| # on the run id (unique per triggering build) rather than the PR number. | |
| concurrency: | |
| group: pr-comment-${{ github.event.workflow_run.id }} | |
| cancel-in-progress: false | |
| jobs: | |
| comment: | |
| name: Post report comment | |
| runs-on: ubuntu-latest | |
| # Only PR builds that actually produced a report: skip push/dispatch (no PR) and | |
| # cancelled/skipped builds (no artifact — would otherwise warn spuriously below). | |
| if: >- | |
| github.event.workflow_run.event == 'pull_request' && | |
| github.event.workflow_run.conclusion != 'cancelled' && | |
| github.event.workflow_run.conclusion != 'skipped' | |
| steps: | |
| # Cross-run: pulls the artifact from the build's run, not this workflow's. | |
| # continue-on-error so a missing/expired artifact doesn't fail the job; the next | |
| # step surfaces it as a warning instead of silently dropping the comment. | |
| - name: Download report artifact | |
| id: dl | |
| continue-on-error: true | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: pr-report | |
| run-id: ${{ github.event.workflow_run.id }} | |
| github-token: ${{ github.token }} | |
| - name: Warn if report unavailable | |
| if: steps.dl.outcome != 'success' | |
| run: echo "::warning::pr-report artifact could not be downloaded (outcome=${{ steps.dl.outcome }}); no PR comment posted." | |
| - name: Post or update comment | |
| if: steps.dl.outcome == 'success' | |
| uses: actions/github-script@v9 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| if (!fs.existsSync('pr_report.md') || !fs.existsSync('pr_number.txt')) { | |
| core.warning('Report artifact incomplete; nothing to post.'); | |
| return; | |
| } | |
| const marker = '<!-- unleashed-pr-report -->'; | |
| const body = fs.readFileSync('pr_report.md', 'utf8'); | |
| const issue_number = parseInt(fs.readFileSync('pr_number.txt', 'utf8').trim(), 10); | |
| if (!Number.isInteger(issue_number)) { | |
| core.setFailed('Invalid PR number in artifact.'); | |
| return; | |
| } | |
| const { owner, repo } = context.repo; | |
| const comments = await github.paginate(github.rest.issues.listComments, | |
| { owner, repo, issue_number, per_page: 100 }); | |
| const existing = comments.find(c => c.body && c.body.includes(marker)); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body }); | |
| } else { | |
| await github.rest.issues.createComment({ owner, repo, issue_number, body }); | |
| } |