@@ -26,48 +26,23 @@ const graphqlWithAuth = graphql.defaults({
2626 headers : { authorization : `token ${ process . env . GITHUB_TOKEN } ` } ,
2727} ) ;
2828
29- // --- Known prefixes map for classification ---
29+ // --- Prefix map for classification ---
3030const 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" ,
31+ "task" : "🚀 Tasks" ,
32+ "composite" : "🚀 Tasks" ,
4033 "ux/ui" : "🔧 Enhancements" ,
41- proposal : "💡 Ideas & Proposals" ,
42- idea : "💡 Ideas & Proposals" ,
43- discussion : "💡 Ideas & Proposals" ,
34+ "enhancement" : "🔧 Enhancements" ,
35+ "bug" : "🐞 Bug Fixes" ,
36+ "feat" : "✨ New Features" ,
37+ "refactor" : "🛠 Refactoring" ,
38+ "docs" : "📚 Documentation" ,
39+ "test" : "✅ Tests" ,
40+ "chore" : "⚙️ Chores" ,
41+ "proposal" : "💡 Ideas & Proposals" ,
42+ "idea" : "💡 Ideas & Proposals" ,
43+ "discussion" : "💡 Ideas & Proposals" ,
4444} ;
4545
46- // --- Normalize title for release notes (keep multi-prefix intact) ---
47- function normalizeTitleForNotes ( title ) {
48- let t = title . trim ( ) ;
49-
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- }
57- ) ;
58-
59- // Leave multi-prefix brackets intact
60- return t ;
61- }
62-
63- // --- Classify title for section (by first prefix only) ---
64- function classifyTitle ( title ) {
65- const match = title . match ( / \[ ( [ ^ \] ] + ) \] / ) ;
66- if ( ! match ) return "Other" ;
67- const firstPrefix = match [ 1 ] . split ( ',' ) [ 0 ] . trim ( ) . toLowerCase ( ) ;
68- return PREFIX_MAP [ firstPrefix ] || "Other" ;
69- }
70-
7146// --- Fetch all closed PRs ---
7247async function getAllPRs ( { owner, repo, base } ) {
7348 const perPage = 100 ;
@@ -117,6 +92,42 @@ async function getLinkedIssues(prNumber) {
11792 }
11893}
11994
95+ // --- Classify title by first prefix ---
96+ function classifyTitle ( title ) {
97+ const t = title . trim ( ) ;
98+
99+ // 1️⃣ Bracket prefix [Feat], [Feat, UX/UI], etc.
100+ let match = t . match ( / \[ ( [ ^ \] ] + ) \] / ) ;
101+ if ( match ) {
102+ const firstPrefix = match [ 1 ] . split ( ',' ) [ 0 ] . trim ( ) . toLowerCase ( ) ;
103+ return PREFIX_MAP [ firstPrefix ] || "Other" ;
104+ }
105+
106+ // 2️⃣ Single-word prefix like chore:, feat:, 🎨 chore:
107+ match = t . match ( / ^ [ \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) ;
108+ if ( match ) {
109+ const prefix = match [ 1 ] . toLowerCase ( ) ;
110+ return PREFIX_MAP [ prefix ] || "Other" ;
111+ }
112+
113+ // 3️⃣ Fallback
114+ return "Other" ;
115+ }
116+
117+ // --- Normalize title prefixes (for display) ---
118+ function normalizeTitleForNotes ( title ) {
119+ let t = title . trim ( ) ;
120+
121+ // Convert single-word prefixes to [Title] style, keep bracketed titles as-is
122+ const match = t . match ( / ^ [ \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) ;
123+ if ( match ) {
124+ const prefix = match [ 1 ] ;
125+ t = t . replace ( match [ 0 ] , `[${ prefix . charAt ( 0 ) . toUpperCase ( ) + prefix . slice ( 1 ) . toLowerCase ( ) } ] ` ) ;
126+ }
127+
128+ return t ;
129+ }
130+
120131// --- Semantic versioning ---
121132function nextVersion ( lastTag ) {
122133 if ( ! lastTag ) return "v0.1.0" ;
@@ -133,15 +144,15 @@ async function main() {
133144 let lastRelease = null ;
134145 try {
135146 const { data } = await octokit . repos . listReleases ( { owner : OWNER , repo : REPO , per_page : 20 } ) ;
136- const publishedReleases = data . filter ( r => ! r . draft ) ;
137- lastRelease = publishedReleases . length ? publishedReleases [ 0 ] : null ;
147+ const published = data . filter ( r => ! r . draft ) ;
148+ lastRelease = published . length ? published [ 0 ] : null ;
138149 } catch { }
139150
140151 const since = lastRelease ? new Date ( lastRelease . created_at ) : null ;
141152 const lastTag = lastRelease ?. tag_name || null ;
142153 const newTag = nextVersion ( lastTag ) ;
143154
144- // 2️⃣ Target branch
155+ // 2️⃣ Determine target branch
145156 const branches = await octokit . repos . listBranches ( { owner : OWNER , repo : REPO } ) ;
146157 const branchNames = branches . data . map ( b => b . name ) ;
147158 let targetBranch = MASTER_BRANCH ;
@@ -177,7 +188,7 @@ async function main() {
177188 }
178189 }
179190
180- // 5️⃣ Classify and organize sections
191+ // 5️⃣ Build sections
181192 const sections = {
182193 "🚀 Tasks" : [ ] ,
183194 "🔧 Enhancements" : [ ] ,
@@ -191,16 +202,18 @@ async function main() {
191202 Other : [ ] ,
192203 } ;
193204
205+ // PRs linked to issues
194206 for ( const [ num , info ] of Object . entries ( issueMap ) ) {
195207 const section = classifyTitle ( info . title ) ;
196- const title = normalizeTitleForNotes ( info . title ) ;
208+ const title = info . title ; // keep original title with all prefixes
197209 const prsText = info . prs . sort ( ( a , b ) => a - b ) . map ( n => `#${ n } ` ) . join ( ", " ) ;
198210 sections [ section ] . push ( `#${ num } ${ title } \n↳ PRs: ${ prsText } ` ) ;
199211 }
200212
213+ // PRs without issues
201214 for ( const pr of prsWithoutIssue ) {
202- const section = classifyTitle ( pr . title ) ;
203215 const title = normalizeTitleForNotes ( pr . title ) ;
216+ const section = classifyTitle ( title ) ;
204217 sections [ section ] . push ( `#${ pr . number } ${ title } ` ) ;
205218 }
206219
@@ -216,7 +229,7 @@ async function main() {
216229
217230 console . log ( releaseNotesText ) ;
218231
219- // 7️⃣ Update or create draft release
232+ // 7️⃣ Find or create draft release
220233 let draftRelease = null ;
221234 try {
222235 const { data : releases } = await octokit . repos . listReleases ( { owner : OWNER , repo : REPO , per_page : 10 } ) ;
0 commit comments