|
| 1 | +# .github/workflows/require-approval.yml |
| 2 | +name: Require at least one approval |
| 3 | + |
| 4 | +on: |
| 5 | + pull_request: |
| 6 | + types: [opened, reopened, ready_for_review, synchronize] |
| 7 | + pull_request_review: |
| 8 | + types: [submitted, edited, dismissed] |
| 9 | + |
| 10 | +jobs: |
| 11 | + check-approval: |
| 12 | + runs-on: ubuntu-latest |
| 13 | + permissions: |
| 14 | + pull-requests: read |
| 15 | + contents: read |
| 16 | + steps: |
| 17 | + - name: Ensure ≥1 approving review exists |
| 18 | + uses: actions/github-script@v7 |
| 19 | + with: |
| 20 | + script: | |
| 21 | + const {owner, repo} = context.repo; |
| 22 | +
|
| 23 | + // Determine PR number for both pull_request and pull_request_review events |
| 24 | + let number; |
| 25 | + if (context.payload.pull_request?.number) { |
| 26 | + number = context.payload.pull_request.number; |
| 27 | + } else if (context.payload.review?.pull_request_url) { |
| 28 | + number = Number(context.payload.review.pull_request_url.split('/').pop()); |
| 29 | + } else { |
| 30 | + core.setFailed('Cannot determine PR number from event payload.'); |
| 31 | + return; |
| 32 | + } |
| 33 | +
|
| 34 | + // Retrieve all reviews |
| 35 | + const reviews = await github.paginate( |
| 36 | + github.rest.pulls.listReviews, |
| 37 | + { owner, repo, pull_number: number } |
| 38 | + ); |
| 39 | +
|
| 40 | + // Keep only each user's latest review |
| 41 | + const latestByUser = new Map(); |
| 42 | + for (const r of reviews) latestByUser.set(r.user.id, r.state); |
| 43 | +
|
| 44 | + // Count approvals |
| 45 | + const approvals = [...latestByUser.values()].filter(s => s === 'APPROVED').length; |
| 46 | +
|
| 47 | + if (approvals < 1) { |
| 48 | + core.setFailed(`PR #${number} requires at least one approving review before merging.`); |
| 49 | + } else { |
| 50 | + core.info(`Approvals found: ${approvals}. Check passed.`); |
| 51 | + } |
0 commit comments