Skip to content

Commit 96bea37

Browse files
committed
fix: normalizeTitleForNotes
1 parent 2fc9513 commit 96bea37

File tree

1 file changed

+26
-49
lines changed

1 file changed

+26
-49
lines changed

.github/scripts/release-notes.js

Lines changed: 26 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const graphqlWithAuth = graphql.defaults({
2626
headers: { authorization: `token ${process.env.GITHUB_TOKEN}` },
2727
});
2828

29-
// --- Known prefixes map ---
29+
// --- Known prefixes map for classification ---
3030
const PREFIX_MAP = {
3131
bug: "🐞 Bug Fixes",
3232
feat: "✨ New Features",
@@ -43,53 +43,28 @@ const PREFIX_MAP = {
4343
discussion: "💡 Ideas & Proposals",
4444
};
4545

46-
// --- Normalize multiple prefixes to official casing ---
47-
function normalizePrefixesToOfficial(title) {
48-
const matches = [...title.matchAll(/\[([^\]]+)\]/g)];
49-
if (matches.length) {
50-
const combined = matches
51-
.map(m => m[1].split(',').map(p => p.trim().toLowerCase()).filter(Boolean)
52-
.map(p => {
53-
if (p === "ux/ui") return "UX/UI";
54-
if (PREFIX_MAP[p]) {
55-
// extract just prefix text without emoji
56-
const text = PREFIX_MAP[p].replace(/^[^\s]+\s/, "");
57-
return text.split(" ")[0];
58-
}
59-
return p.charAt(0).toUpperCase() + p.slice(1);
60-
})
61-
)
62-
.flat();
63-
return {
64-
prefix: `[${combined.join(', ')}]`,
65-
cleanTitle: title.replace(/^(\s*\[[^\]]+\]\s*)+/, '').trim(),
66-
};
67-
}
46+
// --- Normalize title for release notes (keep multi-prefix intact) ---
47+
function normalizeTitleForNotes(title) {
48+
let t = title.trim();
6849

69-
// Handle single-word prefix like "chore:" or "feat:"
70-
const singleMatch = title.match(
71-
/^([^\w]*)(bug|feat|enhancement|refactor|docs|test|chore|task|composite|ux\/ui|proposal|idea|discussion)[:\-\s]/i
50+
// Convert single-word prefixes like chore:, feat:, 🎨 chore: → [Chore]
51+
t = t.replace(
52+
/^[\s\p{Emoji_Presentation}\p{Extended_Pictographic}]*\s*(bug|feat|enhancement|refactor|docs|test|chore|task|composite|ux\/ui|proposal|idea|discussion)[:\s-]+/i,
53+
(m, p1) => {
54+
const normalized = p1.toLowerCase() === "ux/ui" ? "UX/UI" : p1.charAt(0).toUpperCase() + p1.slice(1).toLowerCase();
55+
return `[${normalized}] `;
56+
}
7257
);
73-
if (singleMatch) {
74-
const p = singleMatch[2].toLowerCase();
75-
const normalized = p === "ux/ui" ? "UX/UI" : p.charAt(0).toUpperCase() + p.slice(1);
76-
return { prefix: `[${normalized}]`, cleanTitle: title.replace(singleMatch[0], '').trim() };
77-
}
78-
79-
return { prefix: '', cleanTitle: title };
80-
}
8158

82-
// --- Normalize title ---
83-
function normalizeTitlePrefixes(title) {
84-
const { prefix, cleanTitle } = normalizePrefixesToOfficial(title);
85-
return prefix ? `${prefix} ${cleanTitle}` : cleanTitle;
59+
// Leave multi-prefix brackets intact
60+
return t;
8661
}
8762

88-
// --- Classify title ---
63+
// --- Classify title for section (by first prefix only) ---
8964
function classifyTitle(title) {
90-
const { prefix } = normalizePrefixesToOfficial(title);
91-
if (!prefix) return "Other";
92-
const firstPrefix = prefix.split(',')[0].replace(/[\[\]]/g, '').toLowerCase();
65+
const match = title.match(/\[([^\]]+)\]/);
66+
if (!match) return "Other";
67+
const firstPrefix = match[1].split(',')[0].trim().toLowerCase();
9368
return PREFIX_MAP[firstPrefix] || "Other";
9469
}
9570

@@ -154,7 +129,7 @@ function nextVersion(lastTag) {
154129

155130
// --- Main function ---
156131
async function main() {
157-
// Get last non-draft release
132+
// 1️⃣ Latest non-draft release
158133
let lastRelease = null;
159134
try {
160135
const { data } = await octokit.repos.listReleases({ owner: OWNER, repo: REPO, per_page: 20 });
@@ -166,7 +141,7 @@ async function main() {
166141
const lastTag = lastRelease?.tag_name || null;
167142
const newTag = nextVersion(lastTag);
168143

169-
// Determine target branch
144+
// 2️⃣ Target branch
170145
const branches = await octokit.repos.listBranches({ owner: OWNER, repo: REPO });
171146
const branchNames = branches.data.map(b => b.name);
172147
let targetBranch = MASTER_BRANCH;
@@ -182,10 +157,11 @@ async function main() {
182157
} catch {}
183158
}
184159

160+
// 3️⃣ Fetch merged PRs
185161
const prs = await getAllPRs({ owner: OWNER, repo: REPO, base: targetBranch });
186162
const mergedPRs = prs.filter(pr => pr.merged_at && (!since || new Date(pr.merged_at) > since));
187163

188-
// Build issue → PR map
164+
// 4️⃣ Build issue → PR map
189165
const issueMap = {};
190166
const prsWithoutIssue = [];
191167

@@ -201,7 +177,7 @@ async function main() {
201177
}
202178
}
203179

204-
// Sections
180+
// 5️⃣ Classify and organize sections
205181
const sections = {
206182
"🚀 Tasks": [],
207183
"🔧 Enhancements": [],
@@ -217,17 +193,18 @@ async function main() {
217193

218194
for (const [num, info] of Object.entries(issueMap)) {
219195
const section = classifyTitle(info.title);
220-
const title = normalizeTitlePrefixes(info.title);
196+
const title = normalizeTitleForNotes(info.title);
221197
const prsText = info.prs.sort((a, b) => a - b).map(n => `#${n}`).join(", ");
222198
sections[section].push(`#${num} ${title}\n↳ PRs: ${prsText}`);
223199
}
224200

225201
for (const pr of prsWithoutIssue) {
226202
const section = classifyTitle(pr.title);
227-
const title = normalizeTitlePrefixes(pr.title);
203+
const title = normalizeTitleForNotes(pr.title);
228204
sections[section].push(`#${pr.number} ${title}`);
229205
}
230206

207+
// 6️⃣ Build release notes text
231208
let releaseNotesText = `## Draft Release Notes\n\n`;
232209
for (const [sectionName, items] of Object.entries(sections)) {
233210
if (!items.length) continue;
@@ -239,7 +216,7 @@ async function main() {
239216

240217
console.log(releaseNotesText);
241218

242-
// Update or create draft release
219+
// 7️⃣ Update or create draft release
243220
let draftRelease = null;
244221
try {
245222
const { data: releases } = await octokit.repos.listReleases({ owner: OWNER, repo: REPO, per_page: 10 });

0 commit comments

Comments
 (0)