fix: safe fixes from September 7 issue triage #432
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: Require Linked Issue | |
| env: | |
| JCODE_CI: "1" | |
| # Ensure every pull request is linked to a REAL GitHub issue in this repo. | |
| # A PR passes when it links an issue via any of: | |
| # * GitHub's "Development" sidebar (closing issue reference), or | |
| # * a closing keyword in the body/title (e.g. "Closes #123"), or | |
| # * a plain mention (#123 / owner/repo#123) or full issue URL. | |
| # Any referenced number is verified against the API: it must resolve to an | |
| # existing issue (not a pull request) in this repository. | |
| on: | |
| pull_request: | |
| types: [opened, edited, reopened, synchronize, ready_for_review] | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| issues: read | |
| concurrency: | |
| group: require-issue-${{ github.event.pull_request.number }} | |
| cancel-in-progress: true | |
| jobs: | |
| require-issue: | |
| name: Require Linked Issue | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Check for a linked, existing issue | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const pr = context.payload.pull_request; | |
| if (!pr) { | |
| core.info('No pull_request payload; skipping.'); | |
| return; | |
| } | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const text = `${pr.title || ''}\n\n${pr.body || ''}`; | |
| // Collect candidate issue numbers that belong to THIS repo. | |
| const candidates = new Set(); | |
| // Full issue URLs: https://github.com/owner/repo/issues/123 | |
| const urlRe = /https?:\/\/github\.com\/([\w.-]+)\/([\w.-]+)\/issues\/(\d+)/gi; | |
| for (const m of text.matchAll(urlRe)) { | |
| if (m[1].toLowerCase() === owner.toLowerCase() && | |
| m[2].toLowerCase() === repo.toLowerCase()) { | |
| candidates.add(Number(m[3])); | |
| } | |
| } | |
| // owner/repo#123 or bare #123 (bare assumed to be this repo). | |
| const refRe = /(?:([\w.-]+)\/([\w.-]+))?#(\d+)/g; | |
| for (const m of text.matchAll(refRe)) { | |
| const refOwner = m[1]; | |
| const refRepo = m[2]; | |
| const num = Number(m[3]); | |
| if (!refOwner && !refRepo) { | |
| candidates.add(num); // bare #123 -> this repo | |
| } else if (refOwner?.toLowerCase() === owner.toLowerCase() && | |
| refRepo?.toLowerCase() === repo.toLowerCase()) { | |
| candidates.add(num); | |
| } | |
| } | |
| // Issues linked via the Development sidebar (closing references). | |
| try { | |
| const query = `query($owner:String!, $repo:String!, $number:Int!) { | |
| repository(owner:$owner, name:$repo) { | |
| pullRequest(number:$number) { | |
| closingIssuesReferences(first: 10) { nodes { number } } | |
| } | |
| } | |
| }`; | |
| const result = await github.graphql(query, { owner, repo, number: pr.number }); | |
| const nodes = result?.repository?.pullRequest?.closingIssuesReferences?.nodes || []; | |
| for (const n of nodes) candidates.add(Number(n.number)); | |
| } catch (err) { | |
| core.warning(`Could not query closing issue references: ${err}`); | |
| } | |
| // Verify at least one candidate is a real issue (not a PR, not this PR). | |
| let linkedIssue = null; | |
| for (const num of candidates) { | |
| if (num === pr.number) continue; | |
| try { | |
| const { data } = await github.rest.issues.get({ owner, repo, issue_number: num }); | |
| if (!data.pull_request) { // issues have no pull_request field | |
| linkedIssue = num; | |
| break; | |
| } | |
| } catch (err) { | |
| // 404 -> number doesn't exist; ignore and keep checking. | |
| } | |
| } | |
| if (!linkedIssue) { | |
| core.setFailed( | |
| 'This PR must be linked to an existing GitHub issue in this repository. ' + | |
| 'Add a closing keyword (e.g. "Closes #123") to the PR description, link an ' + | |
| 'issue via the Development sidebar, or reference an existing issue number ' + | |
| '(e.g. "#123"). If no issue exists yet, please open one first.' | |
| ); | |
| return; | |
| } | |
| core.info(`Linked to existing issue #${linkedIssue}. ✅`); |