Skip to content

Commit f6b4cf4

Browse files
ci: Add title checker action (#587)
* Add action that checks PR title formatting (length, conventional commit format, capital letter at the beginning of description, etc.). * This action also adds the necessary labels (cc, package, breaking-change) based on the conventional commit type.
1 parent c08a855 commit f6b4cf4

1 file changed

Lines changed: 99 additions & 0 deletions

File tree

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
name: PR Title Checker
2+
3+
on:
4+
pull_request_target:
5+
types: [opened, edited, reopened]
6+
7+
jobs:
8+
check-title:
9+
runs-on: ubuntu-latest
10+
permissions:
11+
pull-requests: write
12+
steps:
13+
- name: Validate PR Title and Apply Labels
14+
uses: actions/github-script@v7
15+
with:
16+
script: |
17+
const title = context.payload.pull_request.title;
18+
const { owner, repo } = context.repo;
19+
const pr_number = context.payload.pull_request.number;
20+
21+
const regex = /^([a-z-]+)(?:\(([^)]+)\))?(!?): ([A-Z].+)$/;
22+
const match = title.match(regex);
23+
24+
if (!match) {
25+
core.setFailed("PR title does not follow format: 'type(scope)!: description'");
26+
return;
27+
}
28+
29+
const [_, type, scope, breaking, __] = match;
30+
31+
const displayTitle = `${title} (#${pr_number})`;
32+
const maxLength = 72;
33+
34+
if (displayTitle.length > maxLength) {
35+
core.setFailed(
36+
`PR title is too long by ${displayTitle.length-maxLength} characters.`
37+
);
38+
return;
39+
}
40+
41+
const repoLabels = await github.rest.issues.listLabelsForRepo({
42+
owner,
43+
repo,
44+
per_page: 100,
45+
});
46+
47+
const labelNames = repoLabels.data.map(l => l.name);
48+
const labelsToAdd = [];
49+
50+
const typeLabel = `cc: ${type}`;
51+
if (!labelNames.includes(typeLabel)) {
52+
core.setFailed(`Invalid type: "${type}". No label "${typeLabel}" found in repo.`);
53+
return;
54+
}
55+
labelsToAdd.push(typeLabel);
56+
57+
if (scope) {
58+
const scopeLabel = `package: ${scope}`;
59+
if (!labelNames.includes(scopeLabel)) {
60+
core.setFailed(`Invalid scope: "${scope}". No label "${scopeLabel}" found in repo.`);
61+
return;
62+
}
63+
labelsToAdd.push(scopeLabel);
64+
}
65+
66+
if (breaking === '!') {
67+
if (!labelNames.includes('breaking-change')) {
68+
core.setFailed('No "breaking-change" label found in repo.');
69+
return;
70+
}
71+
labelsToAdd.push('breaking-change');
72+
}
73+
74+
const prLabels = await github.rest.issues.listLabelsOnIssue({
75+
owner,
76+
repo,
77+
issue_number: pr_number,
78+
per_page: 100,
79+
});
80+
const managedPrefixes = ['cc: ', 'package: '];
81+
const labelsToRemove = prLabels.data
82+
.map(l => l.name)
83+
.filter(n => managedPrefixes.some(p => n.startsWith(p)) || n === 'breaking-change');
84+
for (const label of labelsToRemove) {
85+
await github.rest.issues.removeLabel({
86+
owner,
87+
repo,
88+
issue_number: pr_number,
89+
name: label
90+
});
91+
}
92+
93+
await github.rest.issues.addLabels({
94+
owner,
95+
repo,
96+
issue_number: pr_number,
97+
labels: labelsToAdd
98+
});
99+
console.log(`Successfully added labels: ${labelsToAdd.join(', ')}`);

0 commit comments

Comments
 (0)