Skip to content

Commit 7365386

Browse files
Automatically label PRs based on changes made
1 parent 2300320 commit 7365386

File tree

3 files changed

+138
-0
lines changed

3 files changed

+138
-0
lines changed

.github/pr-labeler.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
target:images:
2+
- 'target:images'
3+
target:docs-only:
4+
- 'target:docs-only'
5+
target:other:
6+
- 'target:other'
7+
change:bugfix:
8+
- 'change:bugfix'
9+
change:feature:
10+
- 'change:feature'
11+
change:breaking:
12+
- 'change:breaking'
13+
change:other:
14+
- 'change:other'
15+
change:na:
16+
- 'change:na'

.github/pull_request_template.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Ref: **Insert issue URL**
2+
3+
# What approach did you choose and why?
4+
5+
# What are you changing?
6+
7+
<!-- Select one option. -->
8+
- *images* <!-- (`target:images`) -->
9+
- *docs only* <!-- (`target:docs-only`) -->
10+
- *Other* <!-- (`target:other`) -->
11+
12+
# If changing images, what kind of change is it?
13+
14+
<!-- Select one option. -->
15+
- *Bug/security fix* <!-- (`change:bugfix`) -->
16+
- *New feature* <!-- (`change:feature`) -->
17+
- *Breaking change (i.e. Change to default OS, incompatible tooling change etc)* <!-- (`change:breaking`) -->
18+
- *Other* <!-- (`change:other`) -->
19+
- *NA* <!-- (`change:na`) -->
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
name: PR Template Validation
2+
3+
on:
4+
pull_request_target:
5+
types:
6+
- opened
7+
- edited
8+
9+
permissions:
10+
contents: read
11+
pull-requests: write
12+
issues: write
13+
14+
jobs:
15+
validate-and-label:
16+
if: github.event.action == 'opened' || (github.event.action == 'edited' && github.event.changes.body != null)
17+
runs-on: ubuntu-latest
18+
steps:
19+
- name: Validate PR template selections
20+
uses: actions/github-script@v7
21+
with:
22+
script: |
23+
const {owner, repo} = context.repo;
24+
const issue_number = context.payload.pull_request.number;
25+
const body = (context.payload.pull_request.body || '').trim();
26+
const optionPattern = /- \[[ xX]\] .*?<!--\s*\(`([^`]+)`\)\s*-->/g;
27+
const groups = {
28+
'What are you changing?': ['target:images', 'target:docs-only', 'target:other'],
29+
'If changing images, what kind of change is it?': ['change:bugfix', 'change:feature', 'change:breaking', 'change:other', 'change:na']
30+
};
31+
const marker = '<!-- pr-template-validation-comment -->';
32+
33+
const selections = new Map();
34+
let match;
35+
while ((match = optionPattern.exec(body)) !== null) {
36+
const line = match[0];
37+
const tag = match[1];
38+
const isChecked = /\[[xX]\]/.test(line);
39+
selections.set(tag, isChecked);
40+
}
41+
42+
const errors = [];
43+
for (const [section, tags] of Object.entries(groups)) {
44+
const checkedCount = tags.reduce((count, tag) => count + (selections.get(tag) ? 1 : 0), 0);
45+
if (checkedCount !== 1) {
46+
errors.push(`${section} must have exactly one option selected (found ${checkedCount}).`);
47+
}
48+
}
49+
50+
const {data: comments} = await github.rest.issues.listComments({
51+
owner,
52+
repo,
53+
issue_number,
54+
per_page: 100
55+
});
56+
const existing = comments.find(comment => comment.body && comment.body.includes(marker));
57+
58+
if (errors.length) {
59+
const message = [
60+
marker,
61+
'⚠️ **PR Template Validation Failed**',
62+
'',
63+
'Please update the PR description so each section has exactly one option selected:',
64+
...errors.map(error => `- ${error}`),
65+
'',
66+
'Once you update the description, this workflow will re-run automatically.'
67+
].join('\n');
68+
69+
if (existing) {
70+
await github.rest.issues.updateComment({
71+
owner,
72+
repo,
73+
comment_id: existing.id,
74+
body: message
75+
});
76+
} else {
77+
await github.rest.issues.createComment({
78+
owner,
79+
repo,
80+
issue_number,
81+
body: message
82+
});
83+
}
84+
85+
core.setFailed(errors.join(' '));
86+
} else {
87+
if (existing) {
88+
await github.rest.issues.deleteComment({
89+
owner,
90+
repo,
91+
comment_id: existing.id
92+
});
93+
}
94+
core.info('PR template selections validated.');
95+
}
96+
- name: Apply labels from PR template markers
97+
uses: github/issue-labeler@v3.3
98+
with:
99+
repo-token: ${{ github.token }}
100+
configuration-path: .github/pr-labeler.yml
101+
include-title: 0
102+
include-body: 1
103+
sync-labels: 1

0 commit comments

Comments
 (0)