🌱 Sync workflows from kubestellar/infra #23969
Workflow file for this run
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: Hold Issue Guard | |
| on: | |
| pull_request_target: | |
| types: [opened, edited, synchronize, labeled, unlabeled] | |
| issues: | |
| types: [labeled, unlabeled] | |
| permissions: | |
| issues: read | |
| pull-requests: read | |
| jobs: | |
| check-hold-issues: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| # Run on PR events always; on issue events only when the hold label is involved | |
| if: >- | |
| (github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name == github.repository) || | |
| (github.event_name == 'issues' && github.event.label.name == 'hold') | |
| steps: | |
| - name: Check for hold-labeled referenced issues | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 | |
| with: | |
| script: | | |
| // Matches: Fixes #123, Closes: #123, Resolves #123, fix #45, close: #9, etc. | |
| const CLOSING_PATTERN = /(?:fix(?:es)?|close[sd]?|resolve[sd]?):?\s+#(\d+)/gi; | |
| // For issue events, find open PRs that reference this issue | |
| if (context.eventName === 'issues') { | |
| const issueNumber = context.payload.issue.number; | |
| const { data: pulls } = await github.rest.pulls.list({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| state: 'open', | |
| }); | |
| const hasHold = context.payload.issue.labels.some(l => l.name === 'hold'); | |
| if (!hasHold) { | |
| core.info(`Issue #${issueNumber} no longer has hold label. No action needed.`); | |
| return; | |
| } | |
| for (const pr of pulls) { | |
| const body = pr.body || ''; | |
| let match; | |
| while ((match = CLOSING_PATTERN.exec(body)) !== null) { | |
| if (parseInt(match[1], 10) === issueNumber) { | |
| core.setFailed( | |
| `PR #${pr.number} references hold-labeled issue #${issueNumber}.\n` + | |
| `Remove the closing keyword or remove the hold label first.` | |
| ); | |
| return; | |
| } | |
| } | |
| } | |
| core.info(`No open PRs reference hold-labeled issue #${issueNumber}.`); | |
| return; | |
| } | |
| // For pull_request_target events | |
| const body = context.payload.pull_request.body || ''; | |
| let match; | |
| const referencedIssues = []; | |
| while ((match = CLOSING_PATTERN.exec(body)) !== null) { | |
| referencedIssues.push(parseInt(match[1], 10)); | |
| } | |
| if (referencedIssues.length === 0) { | |
| core.info('No closing keywords found in PR body.'); | |
| return; | |
| } | |
| core.info(`Found referenced issues: ${referencedIssues.join(', ')}`); | |
| const holdIssues = []; | |
| for (const issueNumber of referencedIssues) { | |
| try { | |
| const { data: issue } = await github.rest.issues.get({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issueNumber, | |
| }); | |
| const hasHold = issue.labels.some(l => l.name === 'hold'); | |
| if (hasHold) { | |
| holdIssues.push(`#${issueNumber} (${issue.title})`); | |
| } | |
| } catch (e) { | |
| // Treat API errors (404, 403, transient) as non-blocking | |
| core.warning(`Could not fetch issue #${issueNumber}: ${e.message}. Skipping.`); | |
| } | |
| } | |
| if (holdIssues.length > 0) { | |
| core.setFailed( | |
| `This PR references hold-labeled issues that must not be auto-closed:\n` + | |
| holdIssues.map(i => ` - ${i}`).join('\n') + | |
| `\n\nRemove the "Fixes/Closes" keyword for these issues, or remove the hold label first.` | |
| ); | |
| } else { | |
| core.info('No hold-labeled issues referenced. OK.'); | |
| } |