chore: add PR validation workflow and update contributing guidelines (#2777)
This commit is contained in:
92
bin/check-pr-template.mjs
Normal file
92
bin/check-pr-template.mjs
Normal file
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Validate that a pull request body follows the PR template.
|
||||
*
|
||||
*/
|
||||
import { readFileSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
|
||||
const bodyFile = process.argv[2];
|
||||
|
||||
if (!bodyFile) {
|
||||
console.error('body file path is required as an argument.');
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
const body = readFileSync(resolve(bodyFile), 'utf8');
|
||||
const issues = [];
|
||||
|
||||
const MAINTAINER_ROLES = ['OWNER', 'MEMBER', 'COLLABORATOR'];
|
||||
const isMaintainer = MAINTAINER_ROLES.includes(
|
||||
process.env.AUTHOR_ASSOCIATION ?? ''
|
||||
);
|
||||
|
||||
const stripComments = (s) => {
|
||||
let result = s;
|
||||
let previous;
|
||||
do {
|
||||
previous = result;
|
||||
result = result.replace(/<!--[\s\S]*?-->/g, '');
|
||||
} while (result !== previous);
|
||||
return result;
|
||||
};
|
||||
|
||||
const stripFixesPlaceholder = (s) => s.replace(/-\s*Fixes\s*`?#XXXX`?/gi, '');
|
||||
|
||||
const descriptionMatch = body.match(/## Description\s*\n([\s\S]*?)(?=\n## |$)/);
|
||||
const descriptionContent = descriptionMatch
|
||||
? stripFixesPlaceholder(stripComments(descriptionMatch[1])).trim()
|
||||
: '';
|
||||
|
||||
if (!descriptionContent) {
|
||||
issues.push(
|
||||
'**Description** section is empty or only contains placeholder text.'
|
||||
);
|
||||
}
|
||||
|
||||
const testingMatch = body.match(
|
||||
/## How Has This Been Tested\?\s*\n([\s\S]*?)(?=\n## |$)/
|
||||
);
|
||||
const testingContent = testingMatch
|
||||
? stripComments(testingMatch[1]).trim()
|
||||
: '';
|
||||
|
||||
if (!testingContent) {
|
||||
issues.push('**How Has This Been Tested?** section is empty.');
|
||||
}
|
||||
|
||||
const checklistMatch = body.match(/## Checklist:\s*\n([\s\S]*?)$/);
|
||||
const checklistContent = checklistMatch ? checklistMatch[1] : '';
|
||||
|
||||
const totalBoxes = (checklistContent.match(/- \[[ x]\]/g) || []).length;
|
||||
const checkedBoxes = (checklistContent.match(/- \[x\]/gi) || []).length;
|
||||
|
||||
if (totalBoxes === 0) {
|
||||
issues.push('**Checklist** section is missing or has been removed.');
|
||||
} else if (checkedBoxes === 0) {
|
||||
issues.push(
|
||||
'No items in the **checklist** have been checked. Please review and check all applicable items.'
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!/- \[x\] I have read and followed the contribution/i.test(checklistContent)
|
||||
) {
|
||||
issues.push('The **contribution guidelines** checkbox has not been checked.');
|
||||
}
|
||||
|
||||
if (
|
||||
!isMaintainer &&
|
||||
!/- \[x\] Disclosed any use of AI/i.test(checklistContent)
|
||||
) {
|
||||
issues.push('The **AI disclosure** checkbox has not been checked.');
|
||||
}
|
||||
|
||||
if (/-\s*Fixes\s*`?#XXXX`?/i.test(body)) {
|
||||
issues.push(
|
||||
'The `Fixes #XXXX` placeholder has not been updated. Please link the relevant issue or remove it.'
|
||||
);
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(issues));
|
||||
process.exit(issues.length > 0 ? 1 : 0);
|
||||
Reference in New Issue
Block a user