@@ -16,26 +16,17 @@ const processingErrorMessage =
1616 * - The version increments the current version by more than one possible
1717 * increment, eg: going from 3.1.0 to 5.0.0, 3.3.0 or 3.1.2
1818 *
19- * @param {string } newVersion
19+ * @param {string } newVersion - New version to validate
20+ * @param {string } previousVersion - Previous version to compare to
2021 */
21- export function validateVersion ( newVersion ) {
22- const changelogLines = getChangelogLines ( )
23- const previousReleaseLineIndex = getChangelogLineIndexes ( changelogLines ) [ 1 ]
24-
22+ export function validateVersion ( newVersion , previousVersion ) {
2523 if ( ! semver . valid ( newVersion ) ) {
2624 throw new Error (
2725 `New version number ${ newVersion } could not be processed by Semver. Please ensure you are providing a valid semantic version`
2826 )
2927 }
3028
31- // Convert the previous release heading into a processable semver
32- const previousReleaseNumber = convertVersionHeadingToSemver (
33- changelogLines [ previousReleaseLineIndex ]
34- )
35-
36- if ( ! previousReleaseNumber ) {
37- throw new Error ( processingErrorMessage )
38- }
29+ const previousReleaseNumber = validatePreviousVersionNumber ( previousVersion )
3930
4031 // Check the new version against the old version. Firstly a quick check that
4132 // the new one isn't less than the old one
@@ -86,9 +77,13 @@ export function validateVersion(newVersion) {
8677 * Inserts a new heading between the 'Unreleased' heading and the most recent
8778 * content
8879 *
89- * @param {string } newVersion
80+ * @param {string } newVersion - New version to add to the changelog. We presume
81+ * that this is a valid version as this function is always run after validateVersion
82+ * has passed.
83+ * @param {string } previousVersion - Previous version. Used for calculating difference
84+ * in versions to build the changelog title
9085 */
91- export function updateChangelog ( newVersion ) {
86+ export function updateChangelog ( newVersion , previousVersion ) {
9287 // Skip the entire function if the release version is internal eg: 5.1.0-internal.0
9388 if ( versionIsAPrerelease ( newVersion ) ) {
9489 const identifier = getPrereleaseIdentifier ( newVersion )
@@ -105,17 +100,11 @@ export function updateChangelog(newVersion) {
105100 const [ startIndex , previousReleaseLineIndex ] =
106101 getChangelogLineIndexes ( changelogLines )
107102
108- // Convert the previous release heading into a processable semver
109- const previousReleaseNumber = convertVersionHeadingToSemver (
110- changelogLines [ previousReleaseLineIndex ]
103+ const versionDiff = semver . diff (
104+ newVersion ,
105+ validatePreviousVersionNumber ( previousVersion )
111106 )
112107
113- if ( ! previousReleaseNumber ) {
114- throw new Error ( processingErrorMessage )
115- }
116-
117- const versionDiff = semver . diff ( newVersion , previousReleaseNumber )
118-
119108 if ( ! versionDiff ) {
120109 throw new Error ( processingErrorMessage )
121110 }
@@ -129,40 +118,54 @@ export function updateChangelog(newVersion) {
129118 * Generates release notes from the most recent changelog
130119 *
131120 * Creates a text file 'release-notes-body' from the content between either the
132- * first release heading (default) or the 'Unreleased' heading and the following
133- * release heading
121+ * release heading passed to it by newVersion or the 'Unreleased' heading and the
122+ * following release heading if newVersion is tagged as internal
134123 *
135- * @param {boolean } fromUnreleasedHeading
124+ * @param {string } newVersion - Version used to find start point for release notes
136125 */
137- export function generateReleaseNotes ( fromUnreleasedHeading = false ) {
126+ export function generateReleaseNotes ( newVersion ) {
127+ // Get the identifier from the version if there is one as we'll use this to
128+ // change what we pass to getChangelogLineIndexes if the version has an
129+ // 'internal' tag
130+ const identifier = versionIsAPrerelease ( newVersion )
131+ ? getPrereleaseIdentifier ( newVersion )
132+ : undefined
138133 const changelogLines = getChangelogLines ( )
139134 const [ startIndex , previousReleaseLineIndex ] = getChangelogLineIndexes (
140135 changelogLines ,
141- fromUnreleasedHeading
136+ identifier === 'internal' ? undefined : newVersion
142137 )
143138
144139 const releaseNotes = changelogLines
145140 . slice ( startIndex + 1 , previousReleaseLineIndex - 1 )
146- . filter ( ( value , index , arr ) => {
147- if ( value !== '' ) {
148- return true
149- }
150- if (
151- arr [ index + 1 ] . startsWith ( '#' ) ||
152- ( index > 0 && arr [ index - 1 ] . startsWith ( '#' ) )
153- ) {
154- return true
155- }
156- return false
157- } )
158- . map ( ( value ) => {
159- const line = value . replace ( / ^ \s + / , '' )
160- return line . startsWith ( '##' ) ? line . substring ( 1 ) : line
161- } )
141+ . map ( ( line ) =>
142+ line . replace ( / ^ \s + / , '' ) . startsWith ( '##' )
143+ ? line . replace ( / ^ \s + / , '' ) . substring ( 1 )
144+ : line
145+ )
162146
163147 writeFileSync ( './release-notes-body' , releaseNotes . join ( '\n' ) )
164148}
165149
150+ /**
151+ * Validates the previous govuk-frontend version number, presumed passed from
152+ * the govuk-frontend package.json
153+ *
154+ * @param {string } previousVersion - pervious version number
155+ * @returns {string } - Validated semver of previous version
156+ */
157+ function validatePreviousVersionNumber ( previousVersion ) {
158+ const previousReleaseNumber = semver . valid ( previousVersion )
159+
160+ if ( ! previousReleaseNumber ) {
161+ throw new Error (
162+ `Previous version number ${ previousVersion } could not be processed by Semver. Please ensure a valid version is being passed to the script via the govuk-frontend package.json package.`
163+ )
164+ }
165+
166+ return previousReleaseNumber
167+ }
168+
166169/**
167170 * Get the changelog and split it into an array separated by lines
168171 *
@@ -176,25 +179,32 @@ function getChangelogLines() {
176179 * Gets the start and end headings in the changelog for processing by the
177180 * exported functions
178181 *
179- * @param {Array<string> } changelogLines
180- * @param {boolean } fromUnreleasedHeading - Specifies if we get the first index from the 'Unreleased' heading or the first version heading we find
182+ * @param {Array<string> } changelogLines - Produced from getChangelogLines
183+ * @param {string|undefined } heading - Optional query to look for heading
184+ * where the first index is pulled from eg: 'Unreleased'
181185 * @returns {Array<number> } - Indexes in the changelog identifying start and end lines
182186 */
183- function getChangelogLineIndexes ( changelogLines , fromUnreleasedHeading = true ) {
184- const versionTitleRegex = / ^ \s * # + \s + v \d + \. \d + \. \d + ( - .+ \. \d + ) ? \s + \( .+ \) $ / i
187+ function getChangelogLineIndexes ( changelogLines , heading = undefined ) {
188+ // Build regex for finding the correct heading in the changelog
189+ // If a heading hasn't been passed to the function, use 'Unreleased'
190+ const defaultHeadingRegex = '\\d+\\.\\d+\\.\\d+(-.+\\.\\d+)?'
191+ const headingRegex = heading
192+ ? heading . replaceAll ( '.' , '\\.' ) . replace ( 'v' , '' )
193+ : 'Unreleased'
194+
185195 const startIndex = findIndexOfFirstMatchingLine (
186196 changelogLines ,
187- fromUnreleasedHeading ? / ^ \s * # + \s + U n r e l e a s e d \s * $ / i : versionTitleRegex
197+ buildHeadingRegexQuery ( headingRegex )
188198 )
189199
190- if ( ! startIndex ) {
200+ if ( startIndex === - 1 ) {
191201 throw new Error ( processingErrorMessage )
192202 }
193203
194204 const endIndex = findIndexOfFirstMatchingLine (
195205 changelogLines ,
196- versionTitleRegex ,
197- fromUnreleasedHeading ? 0 : startIndex + 1
206+ buildHeadingRegexQuery ( defaultHeadingRegex ) ,
207+ startIndex + 1
198208 )
199209
200210 if ( endIndex === - 1 ) {
@@ -204,12 +214,22 @@ function getChangelogLineIndexes(changelogLines, fromUnreleasedHeading = true) {
204214 return [ startIndex , endIndex ]
205215}
206216
217+ /**
218+ * Builds the search query for headings when getting indexes in the changelog
219+ *
220+ * @param {string } identifier - Either the semantic version or 'Unreleased'
221+ * @returns {RegExp } - Complete heading regex including hashes and release type formatting
222+ */
223+ function buildHeadingRegexQuery ( identifier ) {
224+ return new RegExp ( `^\\s*#+\\s+v?${ identifier } \\s*(\\(.+\\))?$` , 'i' )
225+ }
226+
207227/**
208228 * Get the first matching line in the changelog that matches the passed regex
209229 *
210- * @param {Array<string> } changelogLines
211- * @param {RegExp } regExp
212- * @param {number } offset
230+ * @param {Array<string> } changelogLines - Produced from getChangelogLines
231+ * @param {RegExp } regExp - Regular Expression to match against
232+ * @param {number } offset - Offset from start of the changelogLines array
213233 * @returns {number } - Index in changeLogLines or -1 if we can't locate the index
214234 */
215235function findIndexOfFirstMatchingLine ( changelogLines , regExp , offset = 0 ) {
@@ -221,23 +241,6 @@ function findIndexOfFirstMatchingLine(changelogLines, regExp, offset = 0) {
221241 return foundIndex ? foundIndex + offset : - 1
222242}
223243
224- /**
225- * Convert a release heading into a semver
226- *
227- * Presumes the heading param follows the changelog heading format of:
228- * '## v{version} ({release type})'
229- *
230- * @param {string } heading
231- * @returns {string|null } - Processed semver which we expect to have the format
232- * X.Y.Z(-{identifier}.{base})
233- */
234- function convertVersionHeadingToSemver ( heading ) {
235- const trimmedHeading = heading . trim ( )
236- return semver . valid (
237- trimmedHeading . trim ( ) . substring ( 4 , trimmedHeading . indexOf ( ' (' ) )
238- )
239- }
240-
241244/**
242245 * Checks if a version string is a pre-release or not
243246 *
@@ -280,10 +283,11 @@ function getPrereleaseIdentifier(version) {
280283 * and the new version is 6.3.0-beta.0, the generated string would be
281284 * 'Beta feature'
282285 *
283- * @param {string } incType
284- * @param {string|null } version
285- * @param {boolean } capitalise
286- * @param {string|null } lastReleaseTitle
286+ * @param {string } incType - SemVer increment type
287+ * @param {string|null } version - SemVer version
288+ * @param {boolean } capitalise - If the returned string should start with a capital
289+ * letter or not
290+ * @param {string|null } lastReleaseTitle - Previous release title
287291 * @returns {string } - The reworded increment type
288292 */
289293function convertIncTypeWord (
0 commit comments