@@ -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 ---
3030const 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 ] * ) ( b u g | f e a t | e n h a n c e m e n t | r e f a c t o r | d o c s | t e s t | c h o r e | t a s k | c o m p o s i t e | u x \/ u i | p r o p o s a l | i d e a | d i s c u s s i o n ) [: \- \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 * ( b u g | f e a t | e n h a n c e m e n t | r e f a c t o r | d o c s | t e s t | c h o r e | t a s k | c o m p o s i t e | u x \/ u i | p r o p o s a l | i d e a | d i s c u s s i o n ) [: \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) ---
8964function 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 ---
156131async 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