Skip to content

fix(bump): harden prerelease and releaseAs behaviour #101

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 45 additions & 5 deletions lib/lifecycles/bump.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,46 @@ async function Bump (args, version) {
configsToUpdate = {}

if (args.skip.bump) return version

if (args.releaseAs && !(['major', 'minor', 'patch'].includes(args.releaseAs.toLowerCase()) || semver.valid(args.releaseAs))) {
throw new Error("releaseAs must be one of 'major', 'minor' or 'patch', or a valid semvar version.")
}

let newVersion = version
await runLifecycleScript(args, 'prerelease')
const stdout = await runLifecycleScript(args, 'prebump')
if (stdout && stdout.trim().length) args.releaseAs = stdout.trim()
const release = await bumpVersion(args.releaseAs, version, args)
if (stdout?.trim().length) {
const prebumpString = stdout.trim()
if (semver.valid(prebumpString)) args.releaseAs = prebumpString
}
if (!args.firstRelease) {
const releaseType = getReleaseType(args.prerelease, release.releaseType, version)
const releaseTypeAsVersion = releaseType === 'pre' + release.releaseType ? semver.valid(release.releaseType + '-' + args.prerelease + '.0') : semver.valid(releaseType)
if (semver.valid(args.releaseAs)) {
const releaseAs = new semver.SemVer(args.releaseAs)
if (isString(args.prerelease) && releaseAs.prerelease.length && releaseAs.prerelease.slice(0, -1).join('.') !== args.prerelease) {
// If both releaseAs and the prerelease identifier are supplied, they must match. The behavior
// for a mismatch is undefined, so error out instead.
throw new Error('releaseAs and prerelease have conflicting prerelease identifiers')
} else if (isString(args.prerelease) && releaseAs.prerelease.length) {
newVersion = releaseAs.version
} else if (isString(args.prerelease)) {
newVersion = `${releaseAs.major}.${releaseAs.minor}.${releaseAs.patch}-${args.prerelease}.0`
} else {
newVersion = releaseAs.version
}

// Check if the previous version is the same version and prerelease, and increment if so
if (isString(args.prerelease) && ['prerelease', null].includes(semver.diff(version, newVersion)) && semver.lte(newVersion, version)) {
newVersion = semver.inc(version, 'prerelease', args.prerelease)
}

newVersion = releaseTypeAsVersion || semver.inc(version, releaseType, args.prerelease)
// Append any build info from releaseAs
newVersion = semvarToVersionStr(newVersion, releaseAs.build)
} else {
const release = await bumpVersion(args.releaseAs, version, args)
const releaseType = getReleaseType(args.prerelease, release.releaseType, version)

newVersion = semver.inc(version, releaseType, args.prerelease)
}
updateConfigs(args, newVersion)
} else {
checkpoint(args, 'skip version bump on first release', [], chalk.red(figures.cross))
Expand All @@ -42,6 +72,16 @@ Bump.getUpdatedConfigs = function () {
return configsToUpdate
}

/**
* Convert a semver object to a full version string including build metadata
* @param {string} semverVersion The semvar version string
* @param {string[]} semverBuild An array of the build metadata elements, to be joined with '.'
* @returns {string}
*/
function semvarToVersionStr(semverVersion, semverBuild) {
return [semverVersion, semverBuild.join('.')].filter(Boolean).join('+')
}

function getReleaseType (prerelease, expectedReleaseType, currentVersion) {
if (isString(prerelease)) {
if (isInPrerelease(currentVersion)) {
Expand Down
22 changes: 22 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
},
"devDependencies": {
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"eslint": "^8.16.0",
"eslint-config-standard": "^17.0.0",
"eslint-plugin-import": "^2.26.0",
Expand Down
13 changes: 5 additions & 8 deletions test/config-files.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ const { Readable } = require('stream')
const mockery = require('mockery')
const stdMocks = require('std-mocks')

require('chai').should()
const chai = require('chai')
const expect = chai.expect
chai.use(require('chai-as-promised'))

function exec () {
const cli = require('../command')
Expand Down Expand Up @@ -168,12 +170,7 @@ describe('config files', () => {
it('throws an error when a non-object is returned from .versionrc.js', async function () {
mock({ bump: 'minor' })
fs.writeFileSync('.versionrc.js', 'module.exports = 3', 'utf-8')
try {
await exec()
/* istanbul ignore next */
throw new Error('Unexpected success')
} catch (error) {
error.message.should.match(/Invalid configuration/)
}

expect(exec).to.throw(/Invalid configuration/)
})
})
Loading