-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
169 lines (154 loc) · 6.68 KB
/
pr-category-check.yml
File metadata and controls
169 lines (154 loc) · 6.68 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
164
165
166
167
168
169
name: PR Category Check
# Ensures every PR has at least one category checkbox checked.
# Automatically applies matching GitHub labels to the PR.
# Creates labels with custom colors if they don't exist yet.
# Uses pull_request_target so it works for fork PRs too (runs in base repo context).
# This is safe because we only read the PR body from the event payload — no fork code is checked out or executed.
on:
pull_request_target:
types: [opened, edited, synchronize, reopened]
permissions:
pull-requests: write
contents: read
jobs:
check-pr-category:
name: Validate PR Category & Auto-Label
runs-on: ubuntu-latest
steps:
- name: Check categories and apply labels
uses: actions/github-script@v7
with:
script: |
const prBody = context.payload.pull_request.body || '';
const prNumber = context.payload.pull_request.number;
// Category checkboxes mapped to their GitHub label names, colors, and descriptions
const categories = [
{
label: 'bug fix',
color: 'D73A4A',
description: 'Fixes a bug or incorrect behavior',
displayName: 'Bug Fix',
pattern: /-\s*\[x\]\s*Bug Fix/i,
},
{
label: 'feature',
color: '9333EA',
description: 'Adds new functionality',
displayName: 'Feature',
pattern: /-\s*\[x\]\s*Feature/i,
},
{
label: 'performance',
color: 'F97316',
description: 'Improves performance (load time, memory, rendering)',
displayName: 'Performance',
pattern: /-\s*\[x\]\s*Performance/i,
},
{
label: 'tests',
color: '3B82F6',
description: 'Adds or updates test coverage',
displayName: 'Tests',
pattern: /-\s*\[x\]\s*Tests/i,
},
{
label: 'documentation',
color: '10B981',
description: 'Updates to docs, comments, or README',
displayName: 'Documentation',
pattern: /-\s*\[x\]\s*Documentation/i,
},
];
const checkedCategories = categories.filter(cat => cat.pattern.test(prBody));
const uncheckedCategories = categories.filter(cat => !cat.pattern.test(prBody));
// --- Step 1: Fail CI if no category is selected ---
if (checkedCategories.length === 0) {
const message = [
'## PR Category Required',
'',
'This pull request does not have any **PR Category** selected.',
'Please edit your PR description and check **at least one** category checkbox:',
'',
'| Category | Description |',
'|----------|-------------|',
'| Bug Fix | Fixes a bug or incorrect behavior |',
'| Feature | Adds new functionality |',
'| Performance | Improves performance |',
'| Tests | Adds or updates test coverage |',
'| Documentation | Updates to docs, comments, or README |',
'',
'Example: Change `- [ ] Bug Fix` to `- [x] Bug Fix`',
'',
'> **Tip:** You can select multiple categories if your PR spans several areas.',
].join('\n');
core.setFailed(message);
return;
}
// --- Step 2: Ensure labels exist with proper colors ---
const { data: existingLabels } = await github.rest.issues.listLabelsForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 100,
});
const existingLabelNames = existingLabels.map(l => l.name);
for (const cat of categories) {
if (!existingLabelNames.includes(cat.label)) {
try {
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: cat.label,
color: cat.color,
description: cat.description,
});
core.info(`Created label: "${cat.label}" with color #${cat.color}`);
} catch (error) {
core.warning(`Could not create label "${cat.label}". Error: ${error.message}`);
}
}
}
// --- Step 3: Auto-apply labels for checked categories ---
const labelsToAdd = checkedCategories.map(cat => cat.label);
const labelsToRemove = uncheckedCategories.map(cat => cat.label);
// Get current labels on the PR
const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
});
const currentLabelNames = currentLabels.map(l => l.name);
// Add labels that are checked but not yet on the PR
for (const label of labelsToAdd) {
if (!currentLabelNames.includes(label)) {
try {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
labels: [label],
});
core.info(`Added label: "${label}"`);
} catch (error) {
core.warning(`Could not add label "${label}". Error: ${error.message}`);
}
}
}
// Remove labels that are unchecked but still on the PR
for (const label of labelsToRemove) {
if (currentLabelNames.includes(label)) {
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
name: label,
});
core.info(`Removed label: "${label}"`);
} catch (error) {
core.warning(`Could not remove label "${label}". Error: ${error.message}`);
}
}
}
const selected = checkedCategories.map(c => c.displayName).join(', ');
core.info(`PR categories selected: ${selected}`);
core.info(`Labels synced: ${labelsToAdd.join(', ')}`);