Skip to content

Commit 2c74bb7

Browse files
committed
fix: extractAndNormalizePrefixes
fix: get prefixes
1 parent 33c995e commit 2c74bb7

File tree

1 file changed

+74
-57
lines changed

1 file changed

+74
-57
lines changed

.github/scripts/release-notes.js

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

29+
// --- Known prefixes map ---
30+
const PREFIX_MAP = {
31+
bug: "🐞 Bug Fixes",
32+
feat: "✨ New Features",
33+
enhancement: "🔧 Enhancements",
34+
refactor: "🛠 Refactoring",
35+
docs: "📚 Documentation",
36+
test: "✅ Tests",
37+
chore: "⚙️ Chores",
38+
task: "🚀 Tasks",
39+
composite: "🚀 Tasks",
40+
"ux/ui": "🔧 Enhancements",
41+
proposal: "💡 Ideas & Proposals",
42+
idea: "💡 Ideas & Proposals",
43+
discussion: "💡 Ideas & Proposals",
44+
};
45+
46+
// --- Helper to capitalize prefix cleanly ---
47+
function capitalizePrefix(prefix) {
48+
return prefix
49+
.toLowerCase()
50+
.split("/")
51+
.map((p) => p.charAt(0).toUpperCase() + p.slice(1))
52+
.join("/");
53+
}
54+
55+
// --- Extract and normalize all prefixes in title (merged into one [..] if multiple) ---
56+
function extractAndNormalizePrefixes(title) {
57+
const matches = [...title.matchAll(/\[([^\]]+)\]/g)];
58+
if (matches.length) {
59+
const combined = matches
60+
.map(m => m[1].split(',').map(p => p.trim()).filter(Boolean).map(capitalizePrefix))
61+
.flat();
62+
return {
63+
prefix: `[${combined.join(', ')}]`,
64+
cleanTitle: title.replace(/^(\s*\[[^\]]+\]\s*)+/, '').trim(),
65+
};
66+
}
67+
68+
const singleMatch = title.match(
69+
/^([^\w]*)(bug|feat|enhancement|refactor|docs|test|chore|task|composite|ux\/ui|proposal|idea|discussion)[:\-\s]/i
70+
);
71+
if (singleMatch) {
72+
const normalized = capitalizePrefix(singleMatch[2]);
73+
return { prefix: `[${normalized}]`, cleanTitle: title.replace(singleMatch[0], '').trim() };
74+
}
75+
76+
return { prefix: '', cleanTitle: title };
77+
}
78+
79+
// --- Normalize title ---
80+
function normalizeTitlePrefixes(title) {
81+
const { prefix, cleanTitle } = extractAndNormalizePrefixes(title);
82+
return prefix ? `${prefix} ${cleanTitle}` : cleanTitle;
83+
}
84+
85+
// --- Classify title ---
86+
function classifyTitle(title) {
87+
const { prefix } = extractAndNormalizePrefixes(title);
88+
if (!prefix) return "Other";
89+
90+
// Берём первый префикс для классификации
91+
const firstPrefix = prefix.split(',')[0].replace(/[\[\]]/g, '').toLowerCase();
92+
return PREFIX_MAP[firstPrefix] || "Other";
93+
}
94+
2995
// --- Fetch all closed PRs ---
3096
async function getAllPRs({ owner, repo, base }) {
3197
const perPage = 100;
@@ -75,52 +141,6 @@ async function getLinkedIssues(prNumber) {
75141
}
76142
}
77143

78-
// --- Determine section from prefix ---
79-
function classifyTitle(title) {
80-
const cleaned = title.replace(/^[\s\p{Emoji_Presentation}\p{Extended_Pictographic}]+/u, "").trim();
81-
const match = cleaned.match(/^\s*\[([^\]]+)\]|^\s*([^\s:]+)\s*:?\s*/i);
82-
if (!match) return "Other";
83-
84-
const rawPrefix = (match[1] || match[2] || "").split(",")[0].trim().toLowerCase();
85-
86-
const map = {
87-
"task": "🚀 Tasks",
88-
"composite": "🚀 Tasks",
89-
"ux/ui": "🔧 Enhancements",
90-
"enhancement": "🔧 Enhancements",
91-
"bug": "🐞 Bug Fixes",
92-
"feat": "✨ New Features",
93-
"refactor": "🛠 Refactoring",
94-
"docs": "📚 Documentation",
95-
"test": "✅ Tests",
96-
"chore": "⚙️ Chores",
97-
"proposal": "💡 Ideas & Proposals",
98-
"idea": "💡 Ideas & Proposals",
99-
"discussion": "💡 Ideas & Proposals",
100-
};
101-
102-
return map[rawPrefix] || "Other";
103-
}
104-
105-
// --- Normalize title, preserving multiple prefixes ---
106-
function normalizeTitlePrefixes(title) {
107-
let cleaned = title.trim();
108-
109-
// Extract prefix part if exists
110-
const match = cleaned.match(/^\s*(?:\[([^\]]+)\]|([^\s:]+))\s*:?\s*/i);
111-
if (match) {
112-
let prefixText = match[1] || match[2] || "";
113-
// Keep multiple prefixes intact (e.g. "Feat, UX/UI")
114-
const formatted = `[${prefixText
115-
.split(",")
116-
.map(p => p.trim().replace(/^[\[\]]+/g, "").replace(/^([a-z])/, (_, c) => c.toUpperCase()))
117-
.join(", ")}]`;
118-
cleaned = cleaned.replace(match[0], `${formatted} `);
119-
}
120-
121-
return cleaned;
122-
}
123-
124144
// --- Semantic versioning ---
125145
function nextVersion(lastTag) {
126146
if (!lastTag) return "v0.1.0";
@@ -131,14 +151,13 @@ function nextVersion(lastTag) {
131151
return `v${major}.${minor}.${patch}`;
132152
}
133153

134-
// --- Main ---
154+
// --- Main function ---
135155
async function main() {
136-
// 1️⃣ Get last release
137156
let lastRelease = null;
138157
try {
139158
const { data } = await octokit.repos.listReleases({ owner: OWNER, repo: REPO, per_page: 20 });
140-
const published = data.filter(r => !r.draft);
141-
lastRelease = published.length ? published[0] : null;
159+
const publishedReleases = data.filter(r => !r.draft);
160+
lastRelease = publishedReleases.length ? publishedReleases[0] : null;
142161
} catch {}
143162

144163
const since = lastRelease ? new Date(lastRelease.created_at) : null;
@@ -161,11 +180,10 @@ async function main() {
161180
} catch {}
162181
}
163182

164-
// 3️⃣ Merged PRs since last release
165183
const prs = await getAllPRs({ owner: OWNER, repo: REPO, base: targetBranch });
166184
const mergedPRs = prs.filter(pr => pr.merged_at && (!since || new Date(pr.merged_at) > since));
167185

168-
// 4️⃣ Build issue → PR map
186+
// Build issue → PR map
169187
const issueMap = {};
170188
const prsWithoutIssue = [];
171189

@@ -181,7 +199,7 @@ async function main() {
181199
}
182200
}
183201

184-
// 5️⃣ Group by section
202+
// Sections
185203
const sections = {
186204
"🚀 Tasks": [],
187205
"🔧 Enhancements": [],
@@ -208,19 +226,17 @@ async function main() {
208226
sections[section].push(`#${pr.number} ${title}`);
209227
}
210228

211-
// 6️⃣ Build release notes
212229
let releaseNotesText = `## Draft Release Notes\n\n`;
213230
for (const [sectionName, items] of Object.entries(sections)) {
214231
if (!items.length) continue;
215232
items.sort((a, b) => parseInt(a.match(/#(\d+)/)[1]) - parseInt(b.match(/#(\d+)/)[1]));
216233
releaseNotesText += `### ${sectionName}\n`;
217-
items.forEach(i => (releaseNotesText += `- ${i}\n`));
234+
items.forEach(i => releaseNotesText += `- ${i}\n`);
218235
releaseNotesText += `\n`;
219236
}
220237

221238
console.log(releaseNotesText);
222239

223-
// 7️⃣ Update or create draft release
224240
let draftRelease = null;
225241
try {
226242
const { data: releases } = await octokit.repos.listReleases({ owner: OWNER, repo: REPO, per_page: 10 });
@@ -249,9 +265,10 @@ async function main() {
249265
console.log(`✅ Draft release created: ${newTag}`);
250266
}
251267

252-
console.log("✅ Release processing completed");
268+
console.log(`✅ Release processing completed`);
253269
}
254270

271+
// --- Run ---
255272
main().catch(err => {
256273
console.error("Error:", err);
257274
process.exit(1);

0 commit comments

Comments
 (0)