mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-16 23:12:12 +00:00
148 lines
6 KiB
YAML
148 lines
6 KiB
YAML
name: PR Template Check
|
|
|
|
on:
|
|
pull_request:
|
|
types: [opened, edited, synchronize, reopened]
|
|
|
|
permissions:
|
|
pull-requests: read
|
|
|
|
jobs:
|
|
validate_pr_body:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Validate required PR sections
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const pr = context.payload.pull_request;
|
|
const body = (pr.body || "").trim();
|
|
|
|
function sectionContent(title) {
|
|
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
const re = new RegExp(`^##\\s+${escaped}\\s*$`, "m");
|
|
const match = body.match(re);
|
|
if (!match) return null;
|
|
const start = match.index + match[0].length;
|
|
const rest = body.slice(start);
|
|
const next = rest.search(/^##\s+/m);
|
|
return (next === -1 ? rest : rest.slice(0, next)).trim();
|
|
}
|
|
|
|
function cleanedContent(title) {
|
|
const content = sectionContent(title);
|
|
if (content === null) return null;
|
|
return content
|
|
.replace(/<!--[\s\S]*?-->/g, "")
|
|
.replace(/`/g, "")
|
|
.replace(/\s+/g, " ")
|
|
.trim();
|
|
}
|
|
|
|
function checkedLines(content) {
|
|
return (content || "").match(/^\s*-\s+\[[xX]\]\s+.+$/gm) || [];
|
|
}
|
|
|
|
function uncheckedLines(content) {
|
|
return (content || "").match(/^\s*-\s+\[\s\]\s+.+$/gm) || [];
|
|
}
|
|
|
|
function hasIssueReference(content) {
|
|
return /(^|\s)(#\d+|https:\/\/github\.com\/\S+\/issues\/\d+)/i.test(content || "");
|
|
}
|
|
|
|
const required = [
|
|
"Summary",
|
|
"PR type",
|
|
"Why",
|
|
"Issue or approval",
|
|
"UI / behavior impact",
|
|
"Policy check",
|
|
"Scope boundaries",
|
|
"Testing",
|
|
"Screenshots / Video",
|
|
"Breaking changes",
|
|
"Linked issues",
|
|
];
|
|
const missing = [];
|
|
const empty = [];
|
|
const failedRules = [];
|
|
|
|
for (const name of required) {
|
|
const content = sectionContent(name);
|
|
if (content === null) {
|
|
missing.push(name);
|
|
continue;
|
|
}
|
|
|
|
const cleaned = cleanedContent(name);
|
|
const normalized = cleaned.toLowerCase();
|
|
const allowsNone = name === "Breaking changes" || name === "Screenshots / Video";
|
|
|
|
if (
|
|
cleaned.length < 4 ||
|
|
(!allowsNone && ["none", "n/a", "na", "not applicable"].includes(normalized)) ||
|
|
normalized.includes("what changed in this pr") ||
|
|
normalized.includes("why this change is needed") ||
|
|
normalized.includes("what you tested") ||
|
|
normalized.includes("example: fixes #123")
|
|
) {
|
|
empty.push(name);
|
|
}
|
|
}
|
|
|
|
const prTypeContent = sectionContent("PR type") || "";
|
|
const prTypeChecked = checkedLines(prTypeContent);
|
|
if (sectionContent("PR type") !== null && prTypeChecked.length !== 1) {
|
|
failedRules.push("Check exactly one PR type.");
|
|
}
|
|
|
|
const impactContent = sectionContent("UI / behavior impact") || "";
|
|
const impactChecked = checkedLines(impactContent);
|
|
if (sectionContent("UI / behavior impact") !== null && impactChecked.length === 0) {
|
|
failedRules.push("Check at least one UI / behavior impact box.");
|
|
}
|
|
|
|
const policyContent = sectionContent("Policy check") || "";
|
|
const policyChecked = checkedLines(policyContent);
|
|
const policyUnchecked = uncheckedLines(policyContent);
|
|
if (sectionContent("Policy check") !== null && policyChecked.length === 0) {
|
|
failedRules.push("Policy check must include checked boxes.");
|
|
}
|
|
if (policyUnchecked.length) {
|
|
failedRules.push("Every Policy check box must be checked.");
|
|
}
|
|
|
|
const checkedTypeText = prTypeChecked.join(" ");
|
|
const issueRequired =
|
|
/bug fix|ui glitch|behavior bug|approved larger|approved directional/i.test(checkedTypeText);
|
|
const issueText = [
|
|
cleanedContent("Issue or approval") || "",
|
|
cleanedContent("Linked issues") || "",
|
|
].join(" ");
|
|
if (issueRequired && !hasIssueReference(issueText)) {
|
|
failedRules.push("Bug fixes, UI glitch fixes, behavior fixes, and approved changes must link an issue or approved request.");
|
|
}
|
|
|
|
const uiChanged = checkedLines(impactContent).some((line) =>
|
|
/UI changed only to fix a documented glitch\/bug|UI change has explicit maintainer approval/i.test(line)
|
|
);
|
|
const screenshotText = (cleanedContent("Screenshots / Video") || "").toLowerCase();
|
|
if (uiChanged && ["none", "n/a", "na", "not a ui change", "not applicable"].includes(screenshotText)) {
|
|
failedRules.push("UI changes must include before/after screenshots or video.");
|
|
}
|
|
|
|
if (missing.length || empty.length || failedRules.length) {
|
|
const lines = [
|
|
"PR description is missing required detail.",
|
|
"",
|
|
];
|
|
if (missing.length) lines.push(`Missing sections: ${missing.join(", ")}`);
|
|
if (empty.length) lines.push(`Incomplete sections: ${empty.join(", ")}`);
|
|
if (failedRules.length) lines.push(`Failed policy rules: ${failedRules.join(" ")}`);
|
|
lines.push("");
|
|
lines.push("Please complete the PR template and make sure the PR fits CONTRIBUTING.md before review.");
|
|
core.setFailed(lines.join("\n"));
|
|
} else {
|
|
core.info("PR template check passed.");
|
|
}
|