Compliance Close #179
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: Compliance Close | |
| on: | |
| schedule: | |
| # Every 30 minutes; the actual close decision uses comment age, so the cron | |
| # cadence only bounds how stale the closure can get past the 24-hour mark. | |
| - cron: "*/30 * * * *" | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| issues: write | |
| pull-requests: write | |
| jobs: | |
| close-non-compliant: | |
| if: github.repository == 'zhom/donutbrowser' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Close non-compliant issues and PRs after 24 hours | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| with: | |
| script: | | |
| const { data: items } = await github.rest.issues.listForRepo({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| labels: 'needs:compliance', | |
| state: 'open', | |
| per_page: 100, | |
| }); | |
| if (items.length === 0) { | |
| core.info('No open issues/PRs with needs:compliance label'); | |
| return; | |
| } | |
| const now = Date.now(); | |
| const window_ms = 24 * 60 * 60 * 1000; | |
| for (const item of items) { | |
| const isPR = !!item.pull_request; | |
| const kind = isPR ? 'PR' : 'issue'; | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: item.number, | |
| }); | |
| // Use the OLDEST compliance sentinel as the start of the 24-hour | |
| // window so back-and-forth edits don't reset the clock. | |
| const sentinel = comments | |
| .filter(c => c.body && c.body.includes('<!-- issue-compliance -->')) | |
| .sort((a, b) => new Date(a.created_at) - new Date(b.created_at))[0]; | |
| if (!sentinel) { | |
| core.info(`${kind} #${item.number} has needs:compliance label but no compliance comment; skipping`); | |
| continue; | |
| } | |
| const age_ms = now - new Date(sentinel.created_at).getTime(); | |
| if (age_ms < window_ms) { | |
| const hours = (age_ms / (60 * 60 * 1000)).toFixed(1); | |
| core.info(`${kind} #${item.number} still within 24-hour window (${hours}h elapsed)`); | |
| continue; | |
| } | |
| const closeMessage = isPR | |
| ? 'This pull request has been automatically closed because it was not updated to meet our [contributing guidelines](../blob/main/CONTRIBUTING.md) within the 24-hour window.\n\nFeel free to open a new pull request that follows our guidelines.' | |
| : 'This issue has been automatically closed because it was not updated to meet our [contributing guidelines](../blob/main/CONTRIBUTING.md) within the 24-hour window.\n\nFeel free to open a new issue that follows our issue templates.'; | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: item.number, | |
| body: closeMessage, | |
| }); | |
| try { | |
| await github.rest.issues.removeLabel({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: item.number, | |
| name: 'needs:compliance', | |
| }); | |
| } catch (e) { | |
| core.info(`Could not remove needs:compliance label from #${item.number}: ${e.message}`); | |
| } | |
| if (isPR) { | |
| await github.rest.pulls.update({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: item.number, | |
| state: 'closed', | |
| }); | |
| } else { | |
| await github.rest.issues.update({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: item.number, | |
| state: 'closed', | |
| state_reason: 'not_planned', | |
| }); | |
| } | |
| core.info(`Closed non-compliant ${kind} #${item.number} after 24-hour window`); | |
| } |