Skip to content
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

fix: correct versions with prereleases #495

Merged
merged 4 commits into from
Dec 11, 2024
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
7 changes: 2 additions & 5 deletions lib/release/release-please.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
const RP = require('release-please')
const { DefaultVersioningStrategy } = require('release-please/build/src/versioning-strategies/default.js')
const { PrereleaseVersioningStrategy } = require('release-please/build/src/versioning-strategies/prerelease.js')
const { ROOT_PROJECT_PATH } = require('release-please/build/src/manifest.js')
const { CheckpointLogger, logger } = require('release-please/build/src/util/logger.js')
const assert = require('assert')
Expand All @@ -9,6 +7,7 @@ const omit = require('just-omit')
const ChangelogNotes = require('./changelog.js')
const NodeWorkspaceFormat = require('./node-workspace-format.js')
const { getPublishTag, noop } = require('./util.js')
const { SemverVersioningStrategy } = require('./semver-versioning-strategy.js')

/* istanbul ignore next: TODO fix flaky tests and enable coverage */
class ReleasePlease {
Expand Down Expand Up @@ -52,9 +51,7 @@ class ReleasePlease {

async init() {
RP.registerChangelogNotes('default', ({ github, ...o }) => new ChangelogNotes(github, o))
RP.registerVersioningStrategy('default', o =>
o.prerelease ? new PrereleaseVersioningStrategy(o) : new DefaultVersioningStrategy(o),
)
RP.registerVersioningStrategy('default', o => new SemverVersioningStrategy(o))
RP.registerPlugin(
'node-workspace-format',
({ github, targetBranch, repositoryConfig, ...o }) =>
Expand Down
70 changes: 70 additions & 0 deletions lib/release/semver-versioning-strategy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
const semver = require('semver')
const { Version } = require('release-please/build/src/version.js')

const inc = (version, release, _preid) => {
const parsed = new semver.SemVer(version)
const implicitPreid = parsed.prerelease.length > 1 ? parsed.prerelease[0]?.toString() : undefined
const preid = _preid || implicitPreid
const next = new semver.SemVer(version).inc(release, preid)
if (!parsed.prerelease.length) {
return next.format()
}
const isFreshMajor = parsed.minor === 0 && parsed.patch === 0
const isFreshMinor = parsed.patch === 0
const shouldPrerelease =
(release === 'premajor' && isFreshMajor) || (release === 'preminor' && isFreshMinor) || release === 'prepatch'
if (shouldPrerelease) {
return semver.inc(version, 'prerelease', preid)
}
return next.format()
}

const parseCommits = commits => {
let release = 'patch'
for (const commit of commits) {
if (commit.breaking) {
release = 'major'
break
} else if (['feat', 'feature'].includes(commit.type)) {
release = 'minor'
}
}
return release
}

class SemverVersioningStrategyNested {
constructor(options, version, commits) {
this.options = options
this.commits = commits
this.version = version
}

bump() {
return new SemverVersioningStrategy(this.options).bump(this.version, this.commits)
}
}

class SemverVersioningStrategy {
constructor(options) {
this.options = options
}

determineReleaseType(version, commits) {
return new SemverVersioningStrategyNested(this.options, version, commits)
}

bump(currentVersion, commits) {
const prerelease = this.options.prerelease
const tag = this.options.prereleaseType
const releaseType = parseCommits(commits)
const addPreIfNeeded = prerelease ? `pre${releaseType}` : releaseType
const version = inc(currentVersion.toString(), addPreIfNeeded, tag)
/* istanbul ignore next */
if (!version) {
throw new Error('Could not bump version')
}
return Version.parse(version)
}
}

module.exports = { SemverVersioningStrategy }
90 changes: 90 additions & 0 deletions test/release/version.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
const t = require('tap')
const { Version } = require('release-please/build/src/version.js')
const { SemverVersioningStrategy } = require('../../lib/release/semver-versioning-strategy')

const commit = {
type: 'chore',
breaking: false,
notes: [],
references: [],
scope: '',
bareMessage: '',
sha: '',
message: '',
}

const g = v => ({ ...commit, ...v })

const COMMITS = {
major: [{ type: 'feat' }, {}, {}, { breaking: true }].map(g),
minor: [{}, {}, { type: 'feat' }].map(g),
patch: [{}, { type: 'chore' }, { type: 'fix' }].map(g),
}

const throws = 'THROWS'

const checks = [
// Normal releases
['2.0.0', 'major', false, undefined, '3.0.0'],
['2.0.0', 'minor', false, undefined, '2.1.0'],
['2.0.0', 'patch', false, undefined, '2.0.1'],
// premajor -> normal
['2.0.0-pre.1', 'major', false, undefined, '2.0.0'],
['2.0.0-pre.5', 'minor', false, undefined, '2.0.0'],
['2.0.0-pre.4', 'patch', false, undefined, '2.0.0'],
// preminor -> normal
['2.1.0-pre.1', 'major', false, undefined, '3.0.0'],
['2.1.0-pre.5', 'minor', false, undefined, '2.1.0'],
['2.1.0-pre.4', 'patch', false, undefined, '2.1.0'],
// prepatch -> normal
['2.0.1-pre.1', 'major', false, undefined, '3.0.0'],
['2.0.1-pre.5', 'minor', false, undefined, '2.1.0'],
['2.0.1-pre.4', 'patch', false, undefined, '2.0.1'],
// Prereleases
['2.0.0', 'major', true, 'pre', '3.0.0-pre.0'],
['2.0.0', 'minor', true, 'pre', '2.1.0-pre.0'],
['2.0.0', 'patch', true, 'pre', '2.0.1-pre.0'],
// premajor - prereleases
['2.0.0-pre.1', 'major', true, undefined, '2.0.0-pre.2'],
['2.0.0-pre.1', 'minor', true, undefined, '2.0.0-pre.2'],
['2.0.0-pre.1', 'patch', true, undefined, '2.0.0-pre.2'],
// preminor - prereleases
['2.1.0-pre.1', 'major', true, undefined, '3.0.0-pre.0'],
['2.1.0-pre.1', 'minor', true, undefined, '2.1.0-pre.2'],
['2.1.0-pre.1', 'patch', true, undefined, '2.1.0-pre.2'],
// prepatch - prereleases
['2.0.1-pre.1', 'major', true, undefined, '3.0.0-pre.0'],
['2.0.1-pre.1', 'minor', true, undefined, '2.1.0-pre.0'],
['2.0.1-pre.1', 'patch', true, undefined, '2.0.1-pre.2'],
// different prerelease identifiers
['2.0.0-beta.1', 'major', true, undefined, '2.0.0-beta.2'],
['2.0.0-alpha.1', 'major', true, undefined, '2.0.0-alpha.2'],
['2.0.0-rc.1', 'major', true, undefined, '2.0.0-rc.2'],
['2.0.0-0', 'major', true, undefined, '2.0.0-1'],
// leaves prerelease
['2.0.0-beta.1', 'major', false, undefined, '2.0.0'],
['2.0.0-alpha.1', 'major', false, undefined, '2.0.0'],
['2.0.0-rc.1', 'major', false, undefined, '2.0.0'],
['2.0.0-0', 'major', false, undefined, '2.0.0'],
['xxxx', 'major', false, undefined, throws],
]

t.test('SemverVersioningStrategy', async t => {
for (const [version, commits, prerelease, prereleaseType, expected] of checks) {
const name = [version, commits, prerelease, prereleaseType, expected]
const id = name.map(v => (typeof v === 'undefined' ? 'undefined' : v)).join(',')
const instance = new SemverVersioningStrategy({ prerelease, prereleaseType })

if (expected === throws) {
t.throws(() => instance.bump(Version.parse(version), COMMITS[commits]), id)
continue
}

const bump = instance.bump(Version.parse(version), COMMITS[commits])

const determine = instance.determineReleaseType(Version.parse(version), COMMITS[commits])

t.equal(bump.toString(), expected, id)
t.equal(determine.bump().toString(), expected, id)
}
})
Loading