@@ -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 ] * ) ( 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
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 ---
3096async 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 ---
125145function 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 ---
135155async 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 ---
255272main ( ) . catch ( err => {
256273 console . error ( "Error:" , err ) ;
257274 process . exit ( 1 ) ;
0 commit comments