@@ -26,6 +26,76 @@ 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 ---
56+ function extractPrefixes ( title ) {
57+ const bracketMatch = title . match ( / ^ \s * \[ ( [ ^ \] ] + ) \] / ) ;
58+ if ( bracketMatch ) {
59+ const raw = bracketMatch [ 1 ]
60+ . split ( / [ , ] + / )
61+ . map ( ( p ) => p . trim ( ) )
62+ . filter ( Boolean ) ;
63+ return raw . map ( ( p ) => `[${ capitalizePrefix ( p ) } ]` ) ;
64+ }
65+
66+ const match = title . match (
67+ / ^ ( [ ^ \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
68+ ) ;
69+ if ( match ) {
70+ return [ `[${ capitalizePrefix ( match [ 2 ] ) } ]` ] ;
71+ }
72+
73+ return [ ] ;
74+ }
75+
76+ // --- Normalize title: make sure prefix becomes [Prefix] ---
77+ function normalizeTitlePrefixes ( title ) {
78+ const prefixes = extractPrefixes ( title ) ;
79+ if ( ! prefixes . length ) return title . trim ( ) ;
80+
81+ let clean = title
82+ . replace ( / ^ \s * (?: \[ ( [ ^ \] ] + ) \] | ( [ ^ \s : ] + ) ) \s * : ? \s * / i, "" )
83+ . trim ( ) ;
84+ return `${ prefixes . join ( ", " ) } ${ clean } ` ;
85+ }
86+
87+ // --- Classify title ---
88+ function classifyTitle ( title ) {
89+ const prefixes = extractPrefixes ( title ) ;
90+ if ( ! prefixes . length ) return "Other" ;
91+
92+ for ( const prefix of prefixes ) {
93+ const clean = prefix . replace ( / [ \[ \] ] / g, "" ) . toLowerCase ( ) ;
94+ if ( PREFIX_MAP [ clean ] ) return PREFIX_MAP [ clean ] ;
95+ }
96+ return "Other" ;
97+ }
98+
2999// --- Fetch all closed PRs ---
30100async function getAllPRs ( { owner, repo, base } ) {
31101 const perPage = 100 ;
@@ -75,52 +145,6 @@ async function getLinkedIssues(prNumber) {
75145 }
76146}
77147
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-
124148// --- Semantic versioning ---
125149function nextVersion ( lastTag ) {
126150 if ( ! lastTag ) return "v0.1.0" ;
@@ -131,14 +155,13 @@ function nextVersion(lastTag) {
131155 return `v${ major } .${ minor } .${ patch } ` ;
132156}
133157
134- // --- Main ---
158+ // --- Main function ---
135159async function main ( ) {
136- // 1️⃣ Get last release
137160 let lastRelease = null ;
138161 try {
139162 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 ;
163+ const publishedReleases = data . filter ( r => ! r . draft ) ;
164+ lastRelease = publishedReleases . length ? publishedReleases [ 0 ] : null ;
142165 } catch { }
143166
144167 const since = lastRelease ? new Date ( lastRelease . created_at ) : null ;
@@ -161,11 +184,10 @@ async function main() {
161184 } catch { }
162185 }
163186
164- // 3️⃣ Merged PRs since last release
165187 const prs = await getAllPRs ( { owner : OWNER , repo : REPO , base : targetBranch } ) ;
166188 const mergedPRs = prs . filter ( pr => pr . merged_at && ( ! since || new Date ( pr . merged_at ) > since ) ) ;
167189
168- // 4️⃣ Build issue → PR map
190+ // Build issue → PR map
169191 const issueMap = { } ;
170192 const prsWithoutIssue = [ ] ;
171193
@@ -181,7 +203,7 @@ async function main() {
181203 }
182204 }
183205
184- // 5️⃣ Group by section
206+ // Sections
185207 const sections = {
186208 "🚀 Tasks" : [ ] ,
187209 "🔧 Enhancements" : [ ] ,
@@ -208,19 +230,17 @@ async function main() {
208230 sections [ section ] . push ( `#${ pr . number } ${ title } ` ) ;
209231 }
210232
211- // 6️⃣ Build release notes
212233 let releaseNotesText = `## Draft Release Notes\n\n` ;
213234 for ( const [ sectionName , items ] of Object . entries ( sections ) ) {
214235 if ( ! items . length ) continue ;
215236 items . sort ( ( a , b ) => parseInt ( a . match ( / # ( \d + ) / ) [ 1 ] ) - parseInt ( b . match ( / # ( \d + ) / ) [ 1 ] ) ) ;
216237 releaseNotesText += `### ${ sectionName } \n` ;
217- items . forEach ( i => ( releaseNotesText += `- ${ i } \n` ) ) ;
238+ items . forEach ( i => releaseNotesText += `- ${ i } \n` ) ;
218239 releaseNotesText += `\n` ;
219240 }
220241
221242 console . log ( releaseNotesText ) ;
222243
223- // 7️⃣ Update or create draft release
224244 let draftRelease = null ;
225245 try {
226246 const { data : releases } = await octokit . repos . listReleases ( { owner : OWNER , repo : REPO , per_page : 10 } ) ;
@@ -249,9 +269,10 @@ async function main() {
249269 console . log ( `✅ Draft release created: ${ newTag } ` ) ;
250270 }
251271
252- console . log ( " ✅ Release processing completed" ) ;
272+ console . log ( ` ✅ Release processing completed` ) ;
253273}
254274
275+ // --- Run ---
255276main ( ) . catch ( err => {
256277 console . error ( "Error:" , err ) ;
257278 process . exit ( 1 ) ;
0 commit comments