Skip to content

chore(main): release 0.1.16 #19

chore(main): release 0.1.16

chore(main): release 0.1.16 #19

Workflow file for this run

name: Issue Linker
on:
pull_request_target:
types: [opened, edited]
permissions:
contents: read
concurrency:
group: issue-linker-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
check-links:
name: Check issue links
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
pull-requests: write
issues: read
steps:
- uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const body = context.payload.pull_request.body || '';
const prNumber = context.payload.pull_request.number;
// Skip Dependabot PRs
const author = context.payload.pull_request.user.login;
if (author === 'dependabot[bot]' || author === 'renovate[bot]') return;
// Find all #NNN references (exclude table column refs like |---|)
const allRefs = [...body.matchAll(/(?<!\w)#(\d+)/g)]
.map(m => parseInt(m[1]))
.filter(n => n !== prNumber && n > 0 && n < 100000);
const uniqueRefs = [...new Set(allRefs)];
if (uniqueRefs.length === 0) return;
// Find references with closure keywords
const closurePattern = /(?:closes?|fixes?|resolves?)\s+#(\d+)/gi;
const closedRefs = new Set(
[...body.matchAll(closurePattern)].map(m => parseInt(m[1]))
);
// Check which bare refs are open issues in this repo
const bareOpenIssues = [];
for (const num of uniqueRefs) {
if (closedRefs.has(num)) continue;
try {
const issue = await github.rest.issues.get({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: num
});
if (issue.data.state === 'open' && !issue.data.pull_request) {
bareOpenIssues.push({ number: num, title: issue.data.title });
}
} catch {
// Not a valid issue number or from another repo
}
}
const marker = '<!-- issue-linker-check -->';
// Delete previous comment if no bare open issues remain
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
per_page: 100
});
const existing = comments.find(c => c.body.includes(marker));
if (bareOpenIssues.length === 0) {
if (existing) {
await github.rest.issues.deleteComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id
});
}
return;
}
const issueList = bareOpenIssues
.map(i => `| #${i.number} | ${i.title} |`)
.join('\n');
const message = [
marker,
'### Issue Link Check',
'',
'This PR references open issues without a closure keyword',
'(`Closes`, `Fixes`, or `Resolves`):',
'',
'| Issue | Title |',
'|---|---|',
issueList,
'',
'If this PR fixes any of these, add `Closes #N` to the description',
'so they auto-close on merge. If the reference is informational,',
'no action needed.',
].join('\n');
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body: message
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: message
});
}