Skip to content

Commit 33913ee

Browse files
cgattTimothyJones
andauthored
fix(bump): harden prerelease and releaseAs behaviour (#101)
* test(bump): add test cases for prebump version override * fix: allow bump task to handle versions with build metadata fix: handle invalid versions passed to releaseAs fix: only bump prerelease version if rest of version matches * chore: cleanup unused code * fix: correct error syntax Co-authored-by: Timothy Jones <timothy.l.jones@gmail.com> * chore: rewrite tests to use expect().to.throw * chore: rewrite releaseAs logic as elif chain * chore: convert remaining tests to expect() --------- Co-authored-by: Timothy Jones <timothy.l.jones@gmail.com>
1 parent 5eef72e commit 33913ee

File tree

6 files changed

+222
-104
lines changed

6 files changed

+222
-104
lines changed

lib/lifecycles/bump.js

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,46 @@ async function Bump (args, version) {
2020
configsToUpdate = {}
2121

2222
if (args.skip.bump) return version
23+
24+
if (args.releaseAs && !(['major', 'minor', 'patch'].includes(args.releaseAs.toLowerCase()) || semver.valid(args.releaseAs))) {
25+
throw new Error("releaseAs must be one of 'major', 'minor' or 'patch', or a valid semvar version.")
26+
}
27+
2328
let newVersion = version
2429
await runLifecycleScript(args, 'prerelease')
2530
const stdout = await runLifecycleScript(args, 'prebump')
26-
if (stdout && stdout.trim().length) args.releaseAs = stdout.trim()
27-
const release = await bumpVersion(args.releaseAs, version, args)
31+
if (stdout?.trim().length) {
32+
const prebumpString = stdout.trim()
33+
if (semver.valid(prebumpString)) args.releaseAs = prebumpString
34+
}
2835
if (!args.firstRelease) {
29-
const releaseType = getReleaseType(args.prerelease, release.releaseType, version)
30-
const releaseTypeAsVersion = releaseType === 'pre' + release.releaseType ? semver.valid(release.releaseType + '-' + args.prerelease + '.0') : semver.valid(releaseType)
36+
if (semver.valid(args.releaseAs)) {
37+
const releaseAs = new semver.SemVer(args.releaseAs)
38+
if (isString(args.prerelease) && releaseAs.prerelease.length && releaseAs.prerelease.slice(0, -1).join('.') !== args.prerelease) {
39+
// If both releaseAs and the prerelease identifier are supplied, they must match. The behavior
40+
// for a mismatch is undefined, so error out instead.
41+
throw new Error('releaseAs and prerelease have conflicting prerelease identifiers')
42+
} else if (isString(args.prerelease) && releaseAs.prerelease.length) {
43+
newVersion = releaseAs.version
44+
} else if (isString(args.prerelease)) {
45+
newVersion = `${releaseAs.major}.${releaseAs.minor}.${releaseAs.patch}-${args.prerelease}.0`
46+
} else {
47+
newVersion = releaseAs.version
48+
}
49+
50+
// Check if the previous version is the same version and prerelease, and increment if so
51+
if (isString(args.prerelease) && ['prerelease', null].includes(semver.diff(version, newVersion)) && semver.lte(newVersion, version)) {
52+
newVersion = semver.inc(version, 'prerelease', args.prerelease)
53+
}
3154

32-
newVersion = releaseTypeAsVersion || semver.inc(version, releaseType, args.prerelease)
55+
// Append any build info from releaseAs
56+
newVersion = semvarToVersionStr(newVersion, releaseAs.build)
57+
} else {
58+
const release = await bumpVersion(args.releaseAs, version, args)
59+
const releaseType = getReleaseType(args.prerelease, release.releaseType, version)
60+
61+
newVersion = semver.inc(version, releaseType, args.prerelease)
62+
}
3363
updateConfigs(args, newVersion)
3464
} else {
3565
checkpoint(args, 'skip version bump on first release', [], chalk.red(figures.cross))
@@ -42,6 +72,16 @@ Bump.getUpdatedConfigs = function () {
4272
return configsToUpdate
4373
}
4474

75+
/**
76+
* Convert a semver object to a full version string including build metadata
77+
* @param {string} semverVersion The semvar version string
78+
* @param {string[]} semverBuild An array of the build metadata elements, to be joined with '.'
79+
* @returns {string}
80+
*/
81+
function semvarToVersionStr(semverVersion, semverBuild) {
82+
return [semverVersion, semverBuild.join('.')].filter(Boolean).join('+')
83+
}
84+
4585
function getReleaseType (prerelease, expectedReleaseType, currentVersion) {
4686
if (isString(prerelease)) {
4787
if (isInPrerelease(currentVersion)) {

package-lock.json

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
},
5757
"devDependencies": {
5858
"chai": "^4.2.0",
59+
"chai-as-promised": "^7.1.1",
5960
"eslint": "^8.16.0",
6061
"eslint-config-standard": "^17.0.0",
6162
"eslint-plugin-import": "^2.26.0",

test/config-files.spec.js

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ const { Readable } = require('stream')
88
const mockery = require('mockery')
99
const stdMocks = require('std-mocks')
1010

11-
require('chai').should()
11+
const chai = require('chai')
12+
const expect = chai.expect
13+
chai.use(require('chai-as-promised'))
1214

1315
function exec () {
1416
const cli = require('../command')
@@ -168,12 +170,7 @@ describe('config files', () => {
168170
it('throws an error when a non-object is returned from .versionrc.js', async function () {
169171
mock({ bump: 'minor' })
170172
fs.writeFileSync('.versionrc.js', 'module.exports = 3', 'utf-8')
171-
try {
172-
await exec()
173-
/* istanbul ignore next */
174-
throw new Error('Unexpected success')
175-
} catch (error) {
176-
error.message.should.match(/Invalid configuration/)
177-
}
173+
174+
expect(exec).to.throw(/Invalid configuration/)
178175
})
179176
})

0 commit comments

Comments
 (0)