72 lines
2.5 KiB
JavaScript
72 lines
2.5 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* check-pr-dedup-search.mjs
|
|
* Checks that the PR body affirms the author searched for similar PRs before
|
|
* opening this one. Looks for a checked checklist line matching the dedup
|
|
* affirmation in the PR template. Respects the same skip prefixes as the
|
|
* linked-issue gate.
|
|
*
|
|
* Export: checkDedupSearch(prBody, prTitle) → { passed, failures }
|
|
*/
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const SKIP_PREFIXES = ['docs', 'chore', 'build', 'ci', 'style', 'test', 'revert'];
|
|
|
|
// Match a markdown checkbox line whose label mentions searching for similar
|
|
// PRs. Examples that should match (checked):
|
|
// - [x] I searched for similar open/closed PRs and confirmed this is not a duplicate
|
|
// - [X] I searched the GitHub PR list (open + recently closed) for similar PRs ...
|
|
// * [x] Searched for similar PRs — not a duplicate
|
|
const DEDUP_CHECKBOX_RE =
|
|
/^\s*[-*]\s*\[\s*([ xX])\s*\][^\n]*search(?:ed)?[^\n]*(?:similar|duplicate|prior)[^\n]*\bprs?\b/im;
|
|
|
|
function parsePrefix(title) {
|
|
if (!title) return null;
|
|
const match = title.match(/^([a-z]+)(?:\([^)]*\))?:/);
|
|
return match ? match[1].toLowerCase() : null;
|
|
}
|
|
|
|
export function checkDedupSearch(body, prTitle = '') {
|
|
const prefix = parsePrefix(prTitle);
|
|
if (prefix && SKIP_PREFIXES.includes(prefix)) {
|
|
return { passed: true, failures: [] };
|
|
}
|
|
|
|
if (!body || !body.trim()) {
|
|
return {
|
|
passed: false,
|
|
failures: ['PR body is empty — please fill out the PR template'],
|
|
};
|
|
}
|
|
|
|
const match = body.match(DEDUP_CHECKBOX_RE);
|
|
if (!match) {
|
|
return {
|
|
passed: false,
|
|
failures: [
|
|
'Add the dedup-search checkbox to your PR description and check it once ' +
|
|
'you have searched the GitHub PR list for similar PRs. See the PR template ' +
|
|
'at .github/PULL_REQUEST_TEMPLATE.md and CONTRIBUTING.md → "Before You Start: Search First".',
|
|
],
|
|
};
|
|
}
|
|
|
|
const checked = match[1] === 'x' || match[1] === 'X';
|
|
return {
|
|
passed: checked,
|
|
failures: checked ? [] : [
|
|
'Please confirm you searched the GitHub PR list for similar PRs by ' +
|
|
'checking the dedup-search checkbox in your PR description ' +
|
|
'(`- [x] I searched ...`). See CONTRIBUTING.md → "Before You Start: Search First".',
|
|
],
|
|
};
|
|
}
|
|
|
|
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
const body = process.env.PR_BODY ?? '';
|
|
const title = process.env.PR_TITLE ?? '';
|
|
const result = checkDedupSearch(body, title);
|
|
console.log(JSON.stringify(result));
|
|
process.exit(result.passed ? 0 : 1);
|
|
}
|