Skip to content

Commit f18ab5a

Browse files
ci: Reverse title-checker logic to format-title from labels (#590)
--------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent e16bd4a commit f18ab5a

File tree

2 files changed

+78
-99
lines changed

2 files changed

+78
-99
lines changed

.github/workflows/title-checker.yml

Lines changed: 0 additions & 99 deletions
This file was deleted.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
name: PR Title Formatter
2+
3+
on:
4+
pull_request_target:
5+
types: [opened, edited, reopened, labeled, unlabeled]
6+
7+
jobs:
8+
format-title:
9+
runs-on: ubuntu-latest
10+
permissions:
11+
pull-requests: write
12+
steps:
13+
- name: Format PR Title from Labels
14+
uses: actions/github-script@v7
15+
with:
16+
script: |
17+
const { owner, repo } = context.repo;
18+
const pr_number = context.payload.pull_request.number;
19+
const rawTitle = context.payload.pull_request.title;
20+
21+
// Step 1: Strip any existing CC prefix to get the bare description.
22+
const ccRegex = /^[a-z-]+(?:\([^)]+\))?!?: (.+)$/;
23+
const ccMatch = rawTitle.match(ccRegex);
24+
let description = ccMatch ? ccMatch[1] : rawTitle;
25+
26+
// Step 2: Capitalize first letter.
27+
description = description.charAt(0).toUpperCase() + description.slice(1);
28+
29+
// Step 3: Read PR labels and require exactly one "cc: <type>" label.
30+
const prLabels = await github.rest.issues.listLabelsOnIssue({
31+
owner,
32+
repo,
33+
issue_number: pr_number,
34+
per_page: 100,
35+
});
36+
const labelNames = prLabels.data.map(l => l.name);
37+
38+
const ccLabels = labelNames.filter(n => n.startsWith('cc: '));
39+
if (ccLabels.length === 0) {
40+
core.setFailed('No "cc: <type>" label found on this PR. Please add exactly one.');
41+
return;
42+
}
43+
if (ccLabels.length > 1) {
44+
core.setFailed(`Multiple "cc: <type>" labels found: ${ccLabels.join(', ')}. Please keep exactly one.`);
45+
return;
46+
}
47+
const type = ccLabels[0].slice(4);
48+
49+
// Step 4: Use scope from "package: <name>" label if exactly one is present.
50+
const packageLabels = labelNames.filter(n => n.startsWith('package: '));
51+
const scope = packageLabels.length === 1 ? packageLabels[0].slice(9) : null;
52+
53+
// Step 5: Check for breaking-change label.
54+
const isBreaking = labelNames.includes('breaking-change');
55+
56+
// Step 6: Build new title.
57+
let newTitle = type;
58+
if (scope) newTitle += `(${scope})`;
59+
if (isBreaking) newTitle += '!';
60+
newTitle += `: ${description}`;
61+
62+
// Step 7: Enforce 72-character limit (counting the trailing " (#NNN)").
63+
const displayTitle = `${newTitle} (#${pr_number})`;
64+
if (displayTitle.length > 72) {
65+
core.setFailed(
66+
`Formatted title is too long by ${displayTitle.length - 72} characters: "${displayTitle}"`
67+
);
68+
return;
69+
}
70+
71+
// Step 8: Update the PR title.
72+
await github.rest.pulls.update({
73+
owner,
74+
repo,
75+
pull_number: pr_number,
76+
title: newTitle,
77+
});
78+
console.log(`PR title updated to: "${newTitle}"`);

0 commit comments

Comments
 (0)