Coverage comment #80
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: Coverage comment | |
| # Fork PRs only: same-repo PRs get the cobertura table from ci.yml directly | |
| # (scripts/coverage-pr-comment.sh). This workflow posts the PR comment using | |
| # the base repository's GITHUB_TOKEN after CI uploads the coverage-report | |
| # artifact (fork tokens are read-only on the base repo). | |
| # | |
| # Security: this workflow must NOT execute code from the PR. It checks out | |
| # only the base default branch for the comment script, downloads the CI | |
| # artifact, and comments via the GitHub API. | |
| on: | |
| workflow_run: | |
| workflows: [CI] | |
| types: [completed] | |
| permissions: | |
| actions: read # download artifacts from the CI run | |
| contents: read | |
| pull-requests: write | |
| issues: write # PR comments use the issues comments API | |
| jobs: | |
| comment: | |
| # Same-repo PRs already posted from ci.yml and do not upload coverage-report. | |
| # Only fork PRs upload the artifact (see ci.yml head.repo.full_name check). | |
| if: > | |
| github.event.workflow_run.event == 'pull_request' && | |
| github.event.workflow_run.conclusion == 'success' && | |
| github.event.workflow_run.head_repository.full_name != github.repository | |
| runs-on: ubuntu-latest | |
| steps: | |
| # Base-repo default branch only — not the PR head. | |
| - name: Checkout comment script (base repo) | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event.repository.default_branch }} | |
| sparse-checkout: | | |
| scripts/coverage-pr-comment.sh | |
| sparse-checkout-cone-mode: false | |
| - name: Download coverage artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: coverage-report | |
| run-id: ${{ github.event.workflow_run.id }} | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| path: . | |
| - name: Resolve pull request number | |
| id: pr | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| HEAD_SHA: ${{ github.event.workflow_run.head_sha }} | |
| HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }} | |
| HEAD_REPO: ${{ github.event.workflow_run.head_repository.full_name }} | |
| # Often empty for fork (cross-repo) PRs. | |
| PR_FROM_EVENT: ${{ github.event.workflow_run.pull_requests[0].number }} | |
| run: | | |
| set -euo pipefail | |
| pr="" | |
| # 1) Preferred: written by CI from github.event.pull_request.number | |
| if [ -f pr-number.txt ]; then | |
| pr="$(tr -d '[:space:]' < pr-number.txt || true)" | |
| if [[ -n "$pr" && "$pr" =~ ^[0-9]+$ ]]; then | |
| echo "Resolved PR from artifact pr-number.txt: #${pr}" | |
| else | |
| echo "Ignoring invalid pr-number.txt content: ${pr:-<empty>}" | |
| pr="" | |
| fi | |
| fi | |
| # 2) workflow_run.pull_requests (often empty for forks). | |
| if [ -z "$pr" ] && [ -n "${PR_FROM_EVENT:-}" ]; then | |
| pr="$PR_FROM_EVENT" | |
| echo "Resolved PR from workflow_run.pull_requests: #${pr}" | |
| fi | |
| # 3) head owner:branch listing. | |
| if [ -z "$pr" ] && [ -n "${HEAD_REPO:-}" ] && [ -n "${HEAD_BRANCH:-}" ]; then | |
| head_owner="${HEAD_REPO%%/*}" | |
| pr="$(gh api \ | |
| -H "Accept: application/vnd.github+json" \ | |
| "repos/${REPO}/pulls?state=open&head=${head_owner}:${HEAD_BRANCH}" \ | |
| --jq '.[0].number // empty')" | |
| if [ -n "$pr" ]; then | |
| echo "Resolved PR from head=${head_owner}:${HEAD_BRANCH}: #${pr}" | |
| fi | |
| fi | |
| # 4) commits/{sha}/pulls (often empty for fork head SHAs). | |
| if [ -z "$pr" ]; then | |
| pr="$(gh api \ | |
| -H "Accept: application/vnd.github+json" \ | |
| "repos/${REPO}/commits/${HEAD_SHA}/pulls" \ | |
| --jq '.[0].number // empty')" | |
| if [ -n "$pr" ]; then | |
| echo "Resolved PR from commits/${HEAD_SHA}/pulls: #${pr}" | |
| fi | |
| fi | |
| if [ -z "$pr" ]; then | |
| echo "No pull request found for sha=${HEAD_SHA} head=${HEAD_REPO:-?}:${HEAD_BRANCH:-?}; skipping comment." | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| if ! [[ "$pr" =~ ^[0-9]+$ ]]; then | |
| echo "Refusing non-numeric PR number: ${pr}" | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| echo "number=${pr}" >> "$GITHUB_OUTPUT" | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| echo "Commenting on PR #${pr}" | |
| - name: Coverage PR comment | |
| if: steps.pr.outputs.skip != 'true' | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GITHUB_REPOSITORY: ${{ github.repository }} | |
| PR_NUMBER: ${{ steps.pr.outputs.number }} | |
| GITHUB_SHA: ${{ github.event.workflow_run.head_sha }} | |
| COVERAGE_XML: coverage.xml | |
| MIN_COVERAGE: "75" | |
| run: | | |
| set -euo pipefail | |
| test -f coverage.xml | |
| chmod +x scripts/coverage-pr-comment.sh | |
| scripts/coverage-pr-comment.sh |