1+ /// Unified configuration for PR flavors
2+ const FLAVOR_CONFIG = [
3+ {
4+ labels : [ "feat" , "feature" ] ,
5+ changelog : "Features" ,
6+ isFeature : true
7+ } ,
8+ {
9+ labels : [ "fix" , "bug" , "bugfix" ] ,
10+ changelog : "Fixes"
11+ } ,
12+ {
13+ labels : [ "sec" , "security" ] ,
14+ changelog : "Security"
15+ } ,
16+ {
17+ labels : [ "perf" , "performance" ] ,
18+ changelog : "Performance"
19+ } ,
20+ {
21+ labels : [ "docs" , "doc" ] ,
22+ changelog : undefined // Internal documentation changes
23+ } ,
24+ {
25+ labels : [ "style" , "refactor" ] ,
26+ changelog : undefined // Internal code improvements - no changelog needed
27+ } ,
28+ {
29+ labels : [ "test" ] ,
30+ changelog : undefined // Test updates don't need changelog
31+ } ,
32+ {
33+ labels : [ "build" ] ,
34+ changelog : undefined // Build system changes
35+ } ,
36+ {
37+ labels : [ "ci" ] ,
38+ changelog : undefined // CI changes don't need changelog
39+ } ,
40+ {
41+ labels : [ "chore" ] ,
42+ changelog : undefined // General maintenance
43+ } ,
44+ {
45+ labels : [ "deps" , "dep" , "chore(deps)" , "build(deps)" ] ,
46+ changelog : undefined // Dependency updates
47+ }
48+ ] ;
49+
50+ /// Get flavor configuration for a given PR flavor
51+ function getFlavorConfig ( prFlavor ) {
52+ const normalizedFlavor = prFlavor . toLowerCase ( ) . trim ( ) ;
53+
54+ const config = FLAVOR_CONFIG . find ( config =>
55+ config . labels . includes ( normalizedFlavor )
56+ ) ;
57+
58+ return config || {
59+ changelog : "Features" // Default to Features
60+ } ;
61+ }
62+
63+ /// Find the appropriate line to insert a changelog entry
64+ function findChangelogInsertionPoint ( lines , sectionName ) {
65+ let unreleasedIndex = - 1 ;
66+ let sectionIndex = - 1 ;
67+ let insertionLine = - 1 ;
68+ let isNewSection = false ;
69+
70+ // Find the "## Unreleased" section
71+ for ( let i = 0 ; i < lines . length ; i ++ ) {
72+ if ( lines [ i ] . match ( / ^ # # \s + U n r e l e a s e d / i) ) {
73+ unreleasedIndex = i ;
74+ break ;
75+ }
76+ }
77+
78+ if ( unreleasedIndex === - 1 ) {
79+ // No "Unreleased" section found
80+ return { success : false } ;
81+ }
82+
83+ // Look for the target section under "Unreleased"
84+ for ( let i = unreleasedIndex + 1 ; i < lines . length ; i ++ ) {
85+ // Stop if we hit another ## section (next release)
86+ if ( lines [ i ] . match ( / ^ # # \s + / ) ) {
87+ break ;
88+ }
89+
90+ // Check if this is our target section
91+ if ( lines [ i ] . match ( new RegExp ( `^###\\s+${ sectionName } ` , 'i' ) ) ) {
92+ sectionIndex = i ;
93+ break ;
94+ }
95+ }
96+
97+ if ( sectionIndex !== - 1 ) {
98+ // Found the section, find where to insert within it
99+ for ( let i = sectionIndex + 1 ; i < lines . length ; i ++ ) {
100+ // Stop if we hit another section or end
101+ if ( lines [ i ] . match ( / ^ # # [ # ] ? \s + / ) ) {
102+ break ;
103+ }
104+
105+ // Find the first non-empty line after the section header
106+ if ( lines [ i ] . trim ( ) !== '' ) {
107+ // Insert before this line if it's not already a bullet point
108+ insertionLine = lines [ i ] . match ( / ^ - \s + / ) ? i + 1 : i ;
109+ break ;
110+ }
111+ }
112+
113+ // If we didn't find a good spot, insert right after the section header
114+ if ( insertionLine === - 1 ) {
115+ insertionLine = sectionIndex + 2 ; // +1 for the section, +1 for empty line
116+ }
117+ } else {
118+ // Section doesn't exist, we need to create it
119+ isNewSection = true ;
120+
121+ // Find where to insert the new section
122+ // Look for the next section after Unreleased or find a good spot
123+ for ( let i = unreleasedIndex + 1 ; i < lines . length ; i ++ ) {
124+ if ( lines [ i ] . match ( / ^ # # \s + / ) ) {
125+ // Insert before the next release section
126+ insertionLine = i - 1 ;
127+ break ;
128+ } else if ( lines [ i ] . match ( / ^ # # # \s + / ) ) {
129+ // Insert before the first existing section
130+ insertionLine = i ;
131+ break ;
132+ }
133+ }
134+
135+ // If no good spot found, insert after a reasonable gap from Unreleased
136+ if ( insertionLine === - 1 ) {
137+ insertionLine = unreleasedIndex + 2 ;
138+ }
139+ }
140+
141+ return {
142+ success : true ,
143+ lineNumber : insertionLine + 1 , // Convert to 1-based line numbering
144+ isNewSection : isNewSection ,
145+ sectionHeader : isNewSection ? `### ${ sectionName } ` : null
146+ } ;
147+ }
148+
149+ /// Extract PR flavor from title or branch name
150+ function extractPRFlavor ( prTitle , prBranchRef ) {
151+ if ( prTitle ) {
152+ const parts = prTitle . split ( ":" ) ;
153+ if ( parts . length > 1 ) {
154+ return parts [ 0 ] . toLowerCase ( ) . trim ( ) ;
155+ }
156+ }
157+ if ( prBranchRef ) {
158+ const parts = prBranchRef . split ( "/" ) ;
159+ if ( parts . length > 1 ) {
160+ return parts [ 0 ] . toLowerCase ( ) ;
161+ }
162+ }
163+ return "" ;
164+ }
165+
166+ module . exports = {
167+ FLAVOR_CONFIG ,
168+ getFlavorConfig,
169+ findChangelogInsertionPoint,
170+ extractPRFlavor
171+ } ;
0 commit comments