11#!/usr/bin/env node
22
33const { resolve } = require ( 'node:path' )
4- const { writeFileSync } = require ( 'node:fs' )
4+ const { writeFileSync, readFileSync } = require ( 'node:fs' )
55
6- const semver = require ( 'semver' )
7-
8- const { readFileLinesSync, versionIsAPrerelease, getPrereleaseIdentifier, getChangelogLineIndexes } = require ( '../changelog-release-helper.js' )
6+ const { getChangelogLineIndexes } = require ( '../changelog-release-helper.js' )
97
108const config = require ( '../config.js' )
9+ const { VersionBump } = require ( '../version-bump.js' )
1110
1211if ( require . main === module ) {
1312 // npm exposes these environment variable as part of the lifecycle hooks
1413 // (https://github.com/npm/cli/blob/c97b39b1e3436cd20a67ab5f4012a5f395c538b9/workspaces/libnpmversion/lib/version.js#L100-L103)
1514 const { npm_old_version : previousVersion , npm_new_version : newVersion } =
1615 process . env
1716
18- if ( ! previousVersion || ! newVersion ) {
19- throw new Error ( 'Both previous and new version must be set to continue.' )
20- }
17+ const bump = new VersionBump ( newVersion , previousVersion )
2118
2219 console . log (
2320 `Updating changelog from version ${ previousVersion } to ${ newVersion } ...`
@@ -26,7 +23,7 @@ if (require.main === module) {
2623 // Use `resolve` to find the CHANGELOG.md file relative to the current working directory
2724 const changelogPath = resolve ( process . argv [ 2 ] ?? 'CHANGELOG.md' )
2825
29- updateChangelog ( changelogPath , newVersion , previousVersion )
26+ updateChangelog ( changelogPath , bump )
3027}
3128
3229/**
@@ -36,118 +33,60 @@ if (require.main === module) {
3633 * content
3734 *
3835 * @param {string } path - Path to the CHANGELOG file
39- * @param {string } newVersion - New version to add to the changelog
40- * @param {string } previousVersion - Previous version. Used for calculating difference
41- * in versions to build the changelog title
36+ * @param {VersionBump } bump - New version to add to the changelog
4237 */
43- function updateChangelog ( path , newVersion , previousVersion ) {
44- const validatedNewVersion = validateVersionNumber ( newVersion )
45- const validatedPreviousVersion = validateVersionNumber ( previousVersion )
46-
47- // Skip the entire function if the release version is internal eg: 5.1.0-internal.0
48- const newVersionIsAPrerelease = versionIsAPrerelease ( validatedNewVersion )
49- if ( newVersionIsAPrerelease ) {
50- const identifier = getPrereleaseIdentifier ( validatedNewVersion )
51-
52- if ( identifier === 'internal' ) {
53- console . log (
54- 'This is an internal release, intended for testing only. The changelog will therefore not be updated.'
55- )
56- return
57- }
58- }
59-
60- const changelogLines = readFileLinesSync ( path )
61- const [ startIndex ] = getChangelogLineIndexes ( changelogLines )
38+ function updateChangelog ( path , bump ) {
6239
63- const versionDiff = semver . diff ( validatedNewVersion , validatedPreviousVersion )
64- if ( ! versionDiff ) {
65- throw new Error ( `New version (${ validatedNewVersion } ) and previous version (${ validatedPreviousVersion } ) are the same` )
66- }
67- const newVersionTitle = `## ${ validatedNewVersion } (${ capitalise ( convertIncTypeWord ( versionDiff , validatedNewVersion ) ) } )`
68-
69- const newLines = [ newVersionTitle , '' ]
70- if ( newVersionIsAPrerelease ) {
71- newLines . push (
72- config . prereleaseWarning ( removePrereleaseFlag ( validatedNewVersion ) ) ,
73- ''
74- )
75- // Add content for installing pre-releases
76- newLines . push (
77- config . prereleaseInstallationInstructions ( validatedNewVersion ) ,
78- ''
79- )
80- } else {
81- // Add content on how to install the release
82- newLines . push (
83- config . installationInstructions ( ) ,
84- ''
85- )
40+ if ( ! bump . needsChangelogUpdate ) {
41+ console . log ( 'This is an internal release, intended for testing only. The changelog will therefore not be updated.' )
42+ return
8643 }
8744
88- // Inject the new lines into the CHANGELOG
89- changelogLines . splice ( startIndex + 1 , 0 , '' , ... newLines )
45+ updateFile ( path , ( content ) => {
46+ const changelogLines = content . split ( '\n' ) ;
9047
91- writeFileSync ( path , changelogLines . join ( '\n' ) )
92- }
93-
94- /**
95- * Validates the version number that it is a semantic versioned string.
96- *
97- * @param {string } version - version number
98- * @returns {string } - Validated semver of version
99- */
100- function validateVersionNumber ( version ) {
101- const validatedVersion = semver . valid ( version )
48+ const [ startIndex ] = getChangelogLineIndexes ( changelogLines )
10249
103- if ( ! validatedVersion ) {
104- throw new Error (
105- `Version number "${ version } " could not be parsed as a semantic versioned string.`
106- )
107- }
50+ const newVersionTitle = `## ${ bump . newVersion } (${ capitalise ( bump . releaseLabel ) } )`
10851
109- return validatedVersion
110- }
111-
112- /**
113- * Convert a standard SemVer increment word eg: major, minor or patch into the
114- * wording we use for release titles.
115- *
116- * @param {string } incType - SemVer increment type
117- * @param {string|null } version - SemVer version
118- * @returns {string } - The reworded increment type
119- */
120- function convertIncTypeWord ( incType , version ) {
121- let rewordedIncType = incType
122-
123- // If there's a prerelease flag e.g. 1.0.0-beta.0 use that to decide
124- const prereleaseIdentifier = getPrereleaseIdentifier ( version )
125- if ( prereleaseIdentifier ) {
126- if ( prereleaseIdentifier === 'rc' ) {
127- return 'release candidate'
52+ const newLines = [ newVersionTitle , '' ]
53+ if ( bump . toPrerelease ) {
54+ newLines . push (
55+ config . prereleaseWarning ( bump . withoutPrereleaseTag ) ,
56+ ''
57+ )
58+ // Add content for installing pre-releases
59+ newLines . push (
60+ config . prereleaseInstallationInstructions ( bump . newVersion ) ,
61+ ''
62+ )
63+ } else {
64+ // Add content on how to install the release
65+ newLines . push (
66+ config . installationInstructions ( ) ,
67+ ''
68+ )
12869 }
129- rewordedIncType = prereleaseIdentifier
130- } else if ( incType === 'major' ) {
131- rewordedIncType = 'breaking'
132- } else if ( incType === 'minor' ) {
133- rewordedIncType = 'feature'
134- } else if ( incType === 'patch' ) {
135- rewordedIncType = 'fix'
136- }
13770
138- return `${ rewordedIncType } release`
71+ // Inject the new lines into the CHANGELOG
72+ changelogLines . splice ( startIndex + 1 , 0 , '' , ...newLines )
73+
74+ return changelogLines . join ( '\n' ) ;
75+ } )
13976}
14077
14178/**
142- * Remove any pre-release flag from a version e.g. 1.0.0-alpha -> 1.0.0
143- *
144- * @param {string } version - version number
145- * @returns {string } - version number without any pre-release flag
79+ * Updates file at given path with the result from the given function
80+ *
81+ * @param {string } path - The path of the file to update
82+ * @param {string => string | null | undefined } callback - The function updating the content
14683 */
147- function removePrereleaseFlag ( version ) {
148- const parsedVersion = semver . parse ( version )
149- parsedVersion . prerelease = [ ]
150- return parsedVersion . format ( )
84+ function updateFile ( path , callback ) {
85+ const content = readFileSync ( path , { encoding : 'utf-8' } ) ;
86+ const updatedContent = callback ( content ) ;
87+ if ( updatedContent ) {
88+ writeFileSync ( path , content )
89+ }
15190}
15291
15392/**
0 commit comments