feat(whats-new): add 1.4.0 notes #74
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 Checklist | |
| # Enforce that opened PRs check the required boxes in the PR template. | |
| # Uses pull_request_target so the token is writable for fork PRs; the job only | |
| # reads the PR body from the event payload and never checks out or runs PR code. | |
| on: | |
| pull_request_target: | |
| types: [opened, edited, reopened] | |
| branches: | |
| - main | |
| - dev | |
| permissions: | |
| pull-requests: write | |
| concurrency: | |
| group: pr-checklist-${{ github.event.pull_request.number }} | |
| cancel-in-progress: true | |
| jobs: | |
| checklist: | |
| name: Required checklist | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Verify required checkboxes | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const MARKER = '<!-- pr-checklist-bot -->'; | |
| const BODY_MARKER = '<!-- pr-checklist-template -->'; | |
| // PRs opened via the API/CLI (incl. AI agents) bypass the repository | |
| // template, so the checklist never appears. Inject it so the | |
| // contributor has boxes to check; GITHUB_TOKEN body edits do not | |
| // re-fire this workflow, so the same run must also close below. | |
| const TEMPLATE = [ | |
| BODY_MARKER, | |
| '## Tested on', | |
| '- [ ] iOS [version]', | |
| '- [ ] iPadOS [version]', | |
| '- [ ] macOS [version]', | |
| '', | |
| '## Checklist', | |
| '', | |
| '- [ ] This PR was discussed with the maintainer either via GitHub issue or other means (Also check the box if this PR is small enough not to need discussion e.g. typo fix)', | |
| '- [ ] I have read `CONTRIBUTING.md`', | |
| '- [ ] Testing steps are documented above.', | |
| '- [ ] This change is not low effort and I took the time to test it', | |
| ].join('\n'); | |
| // Each requirement matches a template list item by a stable phrase and | |
| // captures its checkbox state, so light rewording still validates. | |
| const REQUIREMENTS = [ | |
| { label: 'I have read `CONTRIBUTING.md`', regex: /^\s*-\s*\[([ xX])\].*CONTRIBUTING\.md/m }, | |
| { label: 'This change is not low effort and I took the time to test it', regex: /^\s*-\s*\[([ xX])\].*not low effort/m }, | |
| ]; | |
| const pr = context.payload.pull_request; | |
| if (pr.draft) { | |
| core.info('Draft PR; skipping checklist enforcement.'); | |
| return; | |
| } | |
| const PRIVILEGED = ['OWNER', 'MEMBER', 'COLLABORATOR']; | |
| if (PRIVILEGED.includes(pr.author_association)) { | |
| core.info(`Author association ${pr.author_association} is privileged; skipping.`); | |
| return; | |
| } | |
| const body = pr.body || ''; | |
| const evaluated = REQUIREMENTS.map((req) => { | |
| const match = body.match(req.regex); | |
| return { req, present: !!match, checked: !!match && match[1] !== ' ' }; | |
| }); | |
| const allPresent = evaluated.every((e) => e.present); | |
| const missing = evaluated.filter((e) => !e.checked).map((e) => e.req); | |
| const common = { owner: context.repo.owner, repo: context.repo.repo, issue_number: pr.number }; | |
| // The checklist is partly or wholly absent (template was bypassed). | |
| // Inject it so the contributor sees the boxes, then fall through and | |
| // close. A later human checkbox edit still retriggers `edited`. | |
| if (pr.state === 'open' && !allPresent && !body.includes(BODY_MARKER)) { | |
| await github.rest.pulls.update({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: pr.number, | |
| body: body ? `${body}\n\n${TEMPLATE}` : TEMPLATE, | |
| }); | |
| core.info(`Injected checklist template into PR #${pr.number}.`); | |
| } | |
| // Was this PR previously closed by the bot? Only those are eligible for auto-reopen. | |
| const comments = await github.paginate(github.rest.issues.listComments, { ...common, per_page: 100 }); | |
| const botClosed = comments.some((c) => c.body && c.body.includes(MARKER)); | |
| if (missing.length > 0) { | |
| if (pr.state !== 'open') { | |
| core.info('Requirements unmet but PR is already closed; nothing to do.'); | |
| return; | |
| } | |
| const checklist = missing.map((req) => `- [ ] ${req.label}`).join('\n'); | |
| await github.rest.issues.createComment({ | |
| ...common, | |
| body: [ | |
| MARKER, | |
| `Hi @${pr.user.login}, thanks for the contribution! This PR is being closed automatically because the required checklist items are not checked:`, | |
| '', | |
| checklist, | |
| '', | |
| 'Please verify you have completed these items, then check the corresponding boxes in the PR description (the checklist has been added there for you). The PR will reopen automatically once the above are addressed.', | |
| ].join('\n'), | |
| }); | |
| await github.rest.pulls.update({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: pr.number, | |
| state: 'closed', | |
| }); | |
| core.setFailed(`Closed PR #${pr.number}: missing ${missing.length} required checklist item(s).`); | |
| return; | |
| } | |
| // All requirements met. Reopen if we previously closed it. | |
| if (pr.state !== 'open' && botClosed) { | |
| await github.rest.pulls.update({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: pr.number, | |
| state: 'open', | |
| }); | |
| await github.rest.issues.createComment({ | |
| ...common, | |
| body: `${MARKER}\nAll required checklist items are now checked, reopening. Thanks @${pr.user.login}!`, | |
| }); | |
| core.info(`Reopened PR #${pr.number}.`); | |
| return; | |
| } | |
| core.info('All required checklist items are checked.'); |