|
|
@@ -0,0 +1,72 @@
|
|
|
+// 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;
|
|
|
+}
|