-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
163 lines (139 loc) · 5.7 KB
/
Copy pathclose-invalid-pr-template.yml
File metadata and controls
163 lines (139 loc) · 5.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
name: Close PRs Missing Template
on:
pull_request_target:
branches: ["main"]
types: [opened, edited, reopened, synchronize, labeled]
jobs:
validate-pr-template:
if: github.repository == 'community-scripts/ProxmoxVE'
runs-on: ubuntu-latest
permissions:
pull-requests: write
issues: write
contents: read
steps:
- name: Close PR if it does not follow the PR template
uses: actions/github-script@v9
with:
script: |
const pr = context.payload.pull_request;
const prNumber = pr.number;
const author = pr.user.login;
const owner = context.repo.owner;
const repo = context.repo.repo;
const allowedBots = [
"push-app-to-main[bot]",
"push-app-to-main",
"community-scripts-pr-app",
"github-actions[bot]",
"dependabot[bot]",
];
if (allowedBots.includes(author) || author.endsWith("[bot]")) {
core.info(`PR #${prNumber} by bot "${author}" — skipping template validation.`);
return;
}
const association = pr.author_association;
const exemptAssociations = ["OWNER", "MEMBER", "COLLABORATOR"];
if (exemptAssociations.includes(association)) {
core.info(`PR #${prNumber} by ${association} "${author}" — skipping template validation.`);
return;
}
const labels = pr.labels.map((label) => label.name);
const skipLabels = ["automated pr", "keep-open"];
if (skipLabels.some((label) => labels.includes(label))) {
core.info(`PR #${prNumber} has a skip label (${labels.join(", ")}) — skipping template validation.`);
return;
}
if (pr.draft) {
core.info(`PR #${prNumber} is a draft — skipping template validation.`);
return;
}
const body = pr.body || "";
const failures = [];
const requiredSections = [
"## ✍️ Description",
"## ✅ Prerequisites",
"## 🛠️ Type of Change",
];
for (const section of requiredSections) {
if (!body.includes(section)) {
failures.push(`Missing required section: \`${section}\``);
}
}
const descriptionMatch = body.match(
/## ✍️ Description\s*\n+([\s\S]*?)(?=\n## )/i
);
const description = (descriptionMatch?.[1] || "").trim();
if (!description) {
failures.push("The **Description** section is empty.");
}
const prerequisiteCheckboxes = [
"**Self-review completed**",
"**Tested thoroughly**",
"**No security risks**",
];
for (const checkbox of prerequisiteCheckboxes) {
const escaped = checkbox.replace(/([.*+?^=!:${}()|[\]\/\\])/g, "\\$1");
const regex = new RegExp(`- \\[(x|X)\\]\\s*${escaped}`, "i");
if (!regex.test(body)) {
failures.push(`Prerequisite not checked: ${checkbox}`);
}
}
const typeOfChangeCheckboxes = [
"🐞 **Bug fix**",
"✨ **New feature**",
"💥 **Breaking change**",
"🆕 **New script**",
"🌍 **Website update**",
"🔧 **Refactoring / Code Cleanup**",
"📝 **Documentation update**",
];
const hasTypeChecked = typeOfChangeCheckboxes.some((checkbox) => {
const escaped = checkbox.replace(/([.*+?^=!:${}()|[\]\/\\])/g, "\\$1");
const regex = new RegExp(`- \\[(x|X)\\]\\s*${escaped}`, "i");
return regex.test(body);
});
if (!hasTypeChecked) {
failures.push("At least one **Type of Change** checkbox must be checked.");
}
if (failures.length === 0) {
core.info(`PR #${prNumber} follows the PR template.`);
return;
}
core.info(`Closing PR #${prNumber} — template validation failed.`);
const templateUrl =
"https://github.com/community-scripts/ProxmoxVE/blob/main/.github/pull_request_template.md";
const failureList = failures.map((item) => `- ${item}`).join("\n");
const comment = [
`👋 Hi @${author},`,
``,
`This pull request was closed because it does not follow the [PR template](${templateUrl}).`,
``,
`Please fix the following and open a new PR (or reopen this one after updating the description):`,
``,
failureList,
``,
`> Use the template sections, fill in the description, check all prerequisite boxes, and select at least one type of change.`,
``,
`Maintainers can add the \`keep-open\` label to exempt a PR from this check.`,
``,
`Thank you for contributing! 🙏`,
].join("\n");
await github.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body: comment,
});
await github.rest.pulls.update({
owner,
repo,
pull_number: prNumber,
state: "closed",
});
await github.rest.issues.addLabels({
owner,
repo,
issue_number: prNumber,
labels: ["missing pr template"],
});