Skip to content

Commit 252092a

Browse files
authored
fix: correct versions with prereleases (#495)
1 parent abdc939 commit 252092a

File tree

3 files changed

+162
-5
lines changed

3 files changed

+162
-5
lines changed

lib/release/release-please.js

+2-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
const RP = require('release-please')
2-
const { DefaultVersioningStrategy } = require('release-please/build/src/versioning-strategies/default.js')
3-
const { PrereleaseVersioningStrategy } = require('release-please/build/src/versioning-strategies/prerelease.js')
42
const { ROOT_PROJECT_PATH } = require('release-please/build/src/manifest.js')
53
const { CheckpointLogger, logger } = require('release-please/build/src/util/logger.js')
64
const assert = require('assert')
@@ -9,6 +7,7 @@ const omit = require('just-omit')
97
const ChangelogNotes = require('./changelog.js')
108
const NodeWorkspaceFormat = require('./node-workspace-format.js')
119
const { getPublishTag, noop } = require('./util.js')
10+
const { SemverVersioningStrategy } = require('./semver-versioning-strategy.js')
1211

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

5352
async init() {
5453
RP.registerChangelogNotes('default', ({ github, ...o }) => new ChangelogNotes(github, o))
55-
RP.registerVersioningStrategy('default', o =>
56-
o.prerelease ? new PrereleaseVersioningStrategy(o) : new DefaultVersioningStrategy(o),
57-
)
54+
RP.registerVersioningStrategy('default', o => new SemverVersioningStrategy(o))
5855
RP.registerPlugin(
5956
'node-workspace-format',
6057
({ github, targetBranch, repositoryConfig, ...o }) =>
+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
const semver = require('semver')
2+
const { Version } = require('release-please/build/src/version.js')
3+
4+
const inc = (version, release, _preid) => {
5+
const parsed = new semver.SemVer(version)
6+
const implicitPreid = parsed.prerelease.length > 1 ? parsed.prerelease[0]?.toString() : undefined
7+
const preid = _preid || implicitPreid
8+
const next = new semver.SemVer(version).inc(release, preid)
9+
if (!parsed.prerelease.length) {
10+
return next.format()
11+
}
12+
const isFreshMajor = parsed.minor === 0 && parsed.patch === 0
13+
const isFreshMinor = parsed.patch === 0
14+
const shouldPrerelease =
15+
(release === 'premajor' && isFreshMajor) || (release === 'preminor' && isFreshMinor) || release === 'prepatch'
16+
if (shouldPrerelease) {
17+
return semver.inc(version, 'prerelease', preid)
18+
}
19+
return next.format()
20+
}
21+
22+
const parseCommits = commits => {
23+
let release = 'patch'
24+
for (const commit of commits) {
25+
if (commit.breaking) {
26+
release = 'major'
27+
break
28+
} else if (['feat', 'feature'].includes(commit.type)) {
29+
release = 'minor'
30+
}
31+
}
32+
return release
33+
}
34+
35+
class SemverVersioningStrategyNested {
36+
constructor(options, version, commits) {
37+
this.options = options
38+
this.commits = commits
39+
this.version = version
40+
}
41+
42+
bump() {
43+
return new SemverVersioningStrategy(this.options).bump(this.version, this.commits)
44+
}
45+
}
46+
47+
class SemverVersioningStrategy {
48+
constructor(options) {
49+
this.options = options
50+
}
51+
52+
determineReleaseType(version, commits) {
53+
return new SemverVersioningStrategyNested(this.options, version, commits)
54+
}
55+
56+
bump(currentVersion, commits) {
57+
const prerelease = this.options.prerelease
58+
const tag = this.options.prereleaseType
59+
const releaseType = parseCommits(commits)
60+
const addPreIfNeeded = prerelease ? `pre${releaseType}` : releaseType
61+
const version = inc(currentVersion.toString(), addPreIfNeeded, tag)
62+
/* istanbul ignore next */
63+
if (!version) {
64+
throw new Error('Could not bump version')
65+
}
66+
return Version.parse(version)
67+
}
68+
}
69+
70+
module.exports = { SemverVersioningStrategy }

test/release/version.js

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
const t = require('tap')
2+
const { Version } = require('release-please/build/src/version.js')
3+
const { SemverVersioningStrategy } = require('../../lib/release/semver-versioning-strategy')
4+
5+
const commit = {
6+
type: 'chore',
7+
breaking: false,
8+
notes: [],
9+
references: [],
10+
scope: '',
11+
bareMessage: '',
12+
sha: '',
13+
message: '',
14+
}
15+
16+
const g = v => ({ ...commit, ...v })
17+
18+
const COMMITS = {
19+
major: [{ type: 'feat' }, {}, {}, { breaking: true }].map(g),
20+
minor: [{}, {}, { type: 'feat' }].map(g),
21+
patch: [{}, { type: 'chore' }, { type: 'fix' }].map(g),
22+
}
23+
24+
const throws = 'THROWS'
25+
26+
const checks = [
27+
// Normal releases
28+
['2.0.0', 'major', false, undefined, '3.0.0'],
29+
['2.0.0', 'minor', false, undefined, '2.1.0'],
30+
['2.0.0', 'patch', false, undefined, '2.0.1'],
31+
// premajor -> normal
32+
['2.0.0-pre.1', 'major', false, undefined, '2.0.0'],
33+
['2.0.0-pre.5', 'minor', false, undefined, '2.0.0'],
34+
['2.0.0-pre.4', 'patch', false, undefined, '2.0.0'],
35+
// preminor -> normal
36+
['2.1.0-pre.1', 'major', false, undefined, '3.0.0'],
37+
['2.1.0-pre.5', 'minor', false, undefined, '2.1.0'],
38+
['2.1.0-pre.4', 'patch', false, undefined, '2.1.0'],
39+
// prepatch -> normal
40+
['2.0.1-pre.1', 'major', false, undefined, '3.0.0'],
41+
['2.0.1-pre.5', 'minor', false, undefined, '2.1.0'],
42+
['2.0.1-pre.4', 'patch', false, undefined, '2.0.1'],
43+
// Prereleases
44+
['2.0.0', 'major', true, 'pre', '3.0.0-pre.0'],
45+
['2.0.0', 'minor', true, 'pre', '2.1.0-pre.0'],
46+
['2.0.0', 'patch', true, 'pre', '2.0.1-pre.0'],
47+
// premajor - prereleases
48+
['2.0.0-pre.1', 'major', true, undefined, '2.0.0-pre.2'],
49+
['2.0.0-pre.1', 'minor', true, undefined, '2.0.0-pre.2'],
50+
['2.0.0-pre.1', 'patch', true, undefined, '2.0.0-pre.2'],
51+
// preminor - prereleases
52+
['2.1.0-pre.1', 'major', true, undefined, '3.0.0-pre.0'],
53+
['2.1.0-pre.1', 'minor', true, undefined, '2.1.0-pre.2'],
54+
['2.1.0-pre.1', 'patch', true, undefined, '2.1.0-pre.2'],
55+
// prepatch - prereleases
56+
['2.0.1-pre.1', 'major', true, undefined, '3.0.0-pre.0'],
57+
['2.0.1-pre.1', 'minor', true, undefined, '2.1.0-pre.0'],
58+
['2.0.1-pre.1', 'patch', true, undefined, '2.0.1-pre.2'],
59+
// different prerelease identifiers
60+
['2.0.0-beta.1', 'major', true, undefined, '2.0.0-beta.2'],
61+
['2.0.0-alpha.1', 'major', true, undefined, '2.0.0-alpha.2'],
62+
['2.0.0-rc.1', 'major', true, undefined, '2.0.0-rc.2'],
63+
['2.0.0-0', 'major', true, undefined, '2.0.0-1'],
64+
// leaves prerelease
65+
['2.0.0-beta.1', 'major', false, undefined, '2.0.0'],
66+
['2.0.0-alpha.1', 'major', false, undefined, '2.0.0'],
67+
['2.0.0-rc.1', 'major', false, undefined, '2.0.0'],
68+
['2.0.0-0', 'major', false, undefined, '2.0.0'],
69+
['xxxx', 'major', false, undefined, throws],
70+
]
71+
72+
t.test('SemverVersioningStrategy', async t => {
73+
for (const [version, commits, prerelease, prereleaseType, expected] of checks) {
74+
const name = [version, commits, prerelease, prereleaseType, expected]
75+
const id = name.map(v => (typeof v === 'undefined' ? 'undefined' : v)).join(',')
76+
const instance = new SemverVersioningStrategy({ prerelease, prereleaseType })
77+
78+
if (expected === throws) {
79+
t.throws(() => instance.bump(Version.parse(version), COMMITS[commits]), id)
80+
continue
81+
}
82+
83+
const bump = instance.bump(Version.parse(version), COMMITS[commits])
84+
85+
const determine = instance.determineReleaseType(Version.parse(version), COMMITS[commits])
86+
87+
t.equal(bump.toString(), expected, id)
88+
t.equal(determine.bump().toString(), expected, id)
89+
}
90+
})

0 commit comments

Comments
 (0)