| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273 |
- // Checks that a pull request body has the answers the template asks for.
- // Returns a list of missing items. An empty list means the template is complete.
- // Kept separate from the workflow so it can be run locally:
- // node .github/scripts/check-pr-template.js < body.md
- function section(body, startPattern, endPattern) {
- const start = body.search(startPattern);
- if (start === -1) return null;
- const rest = body.slice(start).replace(startPattern, "");
- const end = rest.search(endPattern);
- return end === -1 ? rest : rest.slice(0, end);
- }
- function hasAnswer(text) {
- if (text === null) return false;
- const cleaned = text
- .replace(/_\(.*?\)_/gs, "") // italic placeholder from the template
- .replace(/^\s*-\s*\[[ x]\].*$/gim, "") // checkbox lines are not prose answers
- .replace(/[-*_\s]/g, "");
- return cleaned.length > 0;
- }
- function hasCheckedBox(text) {
- return text !== null && /^\s*-\s*\[x\]/im.test(text);
- }
- function checkTemplate(body) {
- const text = (body || "").replace(/\r\n/g, "\n");
- const missing = [];
- const questions = [
- { n: 1, label: "How long has the project been maintained?" },
- { n: 2, label: "How many releases has it had?" },
- { n: 4, label: "What makes it awesome?" },
- ];
- const info = section(text, /## Project Information/i, /^----|^## /m);
- if (!hasAnswer(section(info || "", /\*\*Project Name:\*\*/i, /^\s*2\./m))) {
- missing.push("Project Information: Project Name");
- }
- if (!hasAnswer(section(info || "", /\*\*Project URL:\*\*/i, /^\s*3\./m))) {
- missing.push("Project Information: Project URL");
- }
- if (!hasAnswer(section(info || "", /\*\*Description:\*\*/i, /^----|^## /m))) {
- missing.push("Project Information: Description");
- }
- const criteria = section(text, /## Criteria/i, /^## /m) || "";
- for (const q of questions) {
- const next = new RegExp(`^\\s*${q.n + 1}\\.\\s+\\*\\*|^----|^## `, "m");
- const answer = section(criteria, new RegExp(`^\\s*${q.n}\\.\\s+\\*\\*.*?\\*\\*`, "m"), next);
- if (!hasAnswer(answer)) missing.push(`Criteria ${q.n}: ${q.label}`);
- }
- const authorship = section(criteria, /^\s*3\.\s+\*\*.*?\*\*/m, /^\s*4\.\s+\*\*|^----|^## /m);
- if (!hasCheckedBox(authorship)) missing.push("Criteria 3: Are you the author? (check one box)");
- const disclosure = section(text, /## AI Disclosure/i, /^----|^## /m);
- if (!hasCheckedBox(disclosure)) missing.push("AI Disclosure (check one box)");
- const company = /^\s*-\s*\[x\].*on behalf of a company/im.test(authorship || "");
- return { missing, company };
- }
- module.exports = { checkTemplate };
- if (require.main === module) {
- const body = require("fs").readFileSync(0, "utf8");
- const result = checkTemplate(body);
- console.log(JSON.stringify(result, null, 2));
- process.exitCode = result.missing.length ? 1 : 0;
- }
|