139 lines
4.9 KiB
YAML
139 lines
4.9 KiB
YAML
name: PR Requirements Backfill
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
|
|
jobs:
|
|
check-all-open-prs:
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
pull-requests: write
|
|
issues: write
|
|
|
|
steps:
|
|
- name: Check all open PRs
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const { data: pullRequests } = await github.rest.pulls.list({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
state: 'open',
|
|
per_page: 100,
|
|
});
|
|
|
|
console.log(`Found ${pullRequests.length} open PRs`);
|
|
|
|
for (const pr of pullRequests) {
|
|
const prNumber = pr.number;
|
|
const prBody = pr.body || '';
|
|
const prTitle = pr.title || '';
|
|
const prAuthor = pr.user.login;
|
|
|
|
console.log(`\nChecking PR #${prNumber}: ${prTitle}`);
|
|
|
|
// Extract issue numbers from body and title
|
|
const issuePattern = /(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)?\s*#(\d+)/gi;
|
|
const allText = `${prTitle} ${prBody}`;
|
|
const matches = [...allText.matchAll(issuePattern)];
|
|
const issueNumbers = [...new Set(matches.map(m => parseInt(m[1], 10)))];
|
|
|
|
console.log(` Found issue references: ${issueNumbers.length > 0 ? issueNumbers.join(', ') : 'none'}`);
|
|
|
|
if (issueNumbers.length === 0) {
|
|
console.log(` ❌ No linked issue - closing PR`);
|
|
|
|
const message = `## PR Closed - Requirements Not Met
|
|
|
|
This PR has been automatically closed because it doesn't meet the requirements.
|
|
|
|
**Missing:** No linked issue found.
|
|
|
|
**To fix:**
|
|
1. Create or find an existing issue for this work
|
|
2. Assign yourself to the issue
|
|
3. Re-open this PR and add \`Fixes #123\` in the description`;
|
|
|
|
await github.rest.issues.createComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: prNumber,
|
|
body: message,
|
|
});
|
|
|
|
await github.rest.pulls.update({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
pull_number: prNumber,
|
|
state: 'closed',
|
|
});
|
|
|
|
continue;
|
|
}
|
|
|
|
// Check if any linked issue has the PR author as assignee
|
|
let issueWithAuthorAssigned = null;
|
|
let issuesWithoutAuthor = [];
|
|
|
|
for (const issueNum of issueNumbers) {
|
|
try {
|
|
const { data: issue } = await github.rest.issues.get({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: issueNum,
|
|
});
|
|
|
|
const assigneeLogins = (issue.assignees || []).map(a => a.login);
|
|
if (assigneeLogins.includes(prAuthor)) {
|
|
issueWithAuthorAssigned = issueNum;
|
|
break;
|
|
} else {
|
|
issuesWithoutAuthor.push({
|
|
number: issueNum,
|
|
assignees: assigneeLogins
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.log(` Issue #${issueNum} not found or inaccessible`);
|
|
}
|
|
}
|
|
|
|
if (!issueWithAuthorAssigned) {
|
|
const issueList = issuesWithoutAuthor.map(i =>
|
|
`#${i.number} (assignees: ${i.assignees.length > 0 ? i.assignees.join(', ') : 'none'})`
|
|
).join(', ');
|
|
|
|
console.log(` ❌ PR author not assigned to any linked issue - closing PR`);
|
|
|
|
const message = `## PR Closed - Requirements Not Met
|
|
|
|
This PR has been automatically closed because it doesn't meet the requirements.
|
|
|
|
**PR Author:** @${prAuthor}
|
|
**Found issues:** ${issueList}
|
|
**Problem:** The PR author must be assigned to the linked issue.
|
|
|
|
**To fix:**
|
|
1. Assign yourself (@${prAuthor}) to one of the linked issues
|
|
2. Re-open this PR`;
|
|
|
|
await github.rest.issues.createComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: prNumber,
|
|
body: message,
|
|
});
|
|
|
|
await github.rest.pulls.update({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
pull_number: prNumber,
|
|
state: 'closed',
|
|
});
|
|
} else {
|
|
console.log(` ✅ PR requirements met! Issue #${issueWithAuthorAssigned} has ${prAuthor} as assignee.`);
|
|
}
|
|
}
|
|
|
|
console.log('\nBackfill complete!');
|