Skip to content

Commit ad95e67

Browse files
committed
feat: add change default versioning strategy
1 parent ba73233 commit ad95e67

File tree

5 files changed

+161
-11
lines changed

5 files changed

+161
-11
lines changed

lib/release-please/changelog.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
const { DefaultChangelogNotes } = require('release-please/build/src/changelog-notes/default')
1+
const RP = require('release-please/build/src/changelog-notes/default')
22

3-
module.exports = class ChangelogNotes {
3+
module.exports = class DefaultChangelogNotes extends RP.DefaultChangelogNotes {
44
constructor (options) {
5-
this.defaultGenerator = new DefaultChangelogNotes(options)
5+
super(options)
66
this.github = options.github
77
}
88

99
async buildDefaultNotes (commits, options) {
1010
// The default generator has a title with the version and date
1111
// and a link to the diff between the last two versions
12-
const notes = await this.defaultGenerator.buildNotes(commits, options)
12+
const notes = await super.buildNotes(commits, options)
1313
const lines = notes.split('\n')
1414

1515
let foundBreakingHeader = false

lib/release-please/index.js

+9-7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
const { Manifest, GitHub, registerChangelogNotes, setLogger } = require('release-please')
2-
const { CheckpointLogger } = require('release-please/build/src/util/logger')
3-
const ChangelogNotes = require('./changelog')
1+
const RP = require('release-please')
2+
const logger = require('./logger.js')
3+
const ChangelogNotes = require('./changelog.js')
4+
const Version = require('./version.js')
45

5-
setLogger(new CheckpointLogger(true, true))
6-
registerChangelogNotes('default', (options) => new ChangelogNotes(options))
6+
RP.setLogger(logger)
7+
RP.registerChangelogNotes('default', (options) => new ChangelogNotes(options))
8+
RP.registerVersioningStrategy('default', (options) => new Version(options))
79

810
const main = async ({ repo: fullRepo, token, dryRun, branch }) => {
911
if (!token) {
@@ -15,8 +17,8 @@ const main = async ({ repo: fullRepo, token, dryRun, branch }) => {
1517
}
1618

1719
const [owner, repo] = fullRepo.split('/')
18-
const github = await GitHub.create({ owner, repo, token })
19-
const manifest = await Manifest.fromManifest(
20+
const github = await RP.GitHub.create({ owner, repo, token })
21+
const manifest = await RP.Manifest.fromManifest(
2022
github,
2123
branch ?? github.repository.defaultBranch
2224
)

lib/release-please/logger.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const { CheckpointLogger } = require('release-please/build/src/util/logger')
2+
3+
module.exports = new CheckpointLogger(true, true)

lib/release-please/version.js

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
const semver = require('semver')
2+
3+
// A way to compare the "level" of a release since we ignore some things during prereleases
4+
const LEVELS = new Map([['prerelease', 4], ['major', 3], ['minor', 2], ['patch', 1]]
5+
.flatMap((kv) => [kv, kv.slice().reverse()]))
6+
7+
const parseVersion = (v) => {
8+
const { prerelease, minor, patch, version } = semver.parse(v)
9+
const hasPre = prerelease.length > 0
10+
const preId = prerelease.filter(p => typeof p === 'string').join('.')
11+
const release = !patch ? (minor ? LEVELS.get('minor') : LEVELS.get('major')) : LEVELS.get('patch')
12+
return { version, release, prerelease: hasPre, preId }
13+
}
14+
15+
const parseCommits = (commits, prerelease) => {
16+
let release = LEVELS.get('patch')
17+
for (const commit of commits) {
18+
if (commit.breaking) {
19+
release = LEVELS.get('major')
20+
break
21+
} else if (['feat', 'feature'].includes(commit.type)) {
22+
release = LEVELS.get('minor')
23+
}
24+
}
25+
return { release, prerelease: !!prerelease }
26+
}
27+
28+
const preInc = ({ version, prerelease, preId }, release) => {
29+
if (!release.startsWith('pre')) {
30+
release = `pre${release}`
31+
}
32+
// `pre` is the default prerelease identifier when creating a new
33+
// prerelease version
34+
return semver.inc(version, release, prerelease ? preId : 'pre')
35+
}
36+
37+
const releasePleaseVersion = (v) => {
38+
const { major, minor, patch, prerelease } = semver.parse(v)
39+
const result = {
40+
major,
41+
minor,
42+
patch,
43+
}
44+
if (prerelease.length) {
45+
// release please wants it capitalized like this
46+
result.preRelease = prerelease.join('.')
47+
}
48+
return result
49+
}
50+
51+
// This does not account for pre v1 semantics since we don't publish those
52+
// Always 1.0.0 your initial versions!
53+
module.exports = class DefaultVersioningStrategy {
54+
constructor (options) {
55+
this.prerelease = options.prerelease
56+
}
57+
58+
bump (currentVersion, commits) {
59+
const next = parseCommits(commits, this.prerelease)
60+
const current = parseVersion(currentVersion)
61+
62+
// This is a special case where semver doesn't align exactly with what we want.
63+
// We are currently at a prerelease and our next is also a prerelease.
64+
// In this case we want to ignore the release type we got from our conventional
65+
// commits if the "level" of the next release is <= the level of the current one.
66+
//
67+
// This has the effect of only bumping the prerelease identifier and nothing else
68+
// when we are actively working (and breaking) a prerelease. For example:
69+
//
70+
// `9.0.0-pre.4` + breaking changes = `9.0.0-pre.5`
71+
// `8.5.0-pre.4` + breaking changes = `9.0.0-pre.0`
72+
// `8.5.0-pre.4` + feature or patch changes = `8.5.0-pre.5`
73+
if (current.prerelease && next.prerelease && next.release <= current.release) {
74+
next.release = LEVELS.get('prerelease')
75+
}
76+
77+
const release = LEVELS.get(next.release)
78+
const releaseVersion = next.prerelease
79+
? preInc(current, release)
80+
: semver.inc(current.version, release)
81+
return releasePleaseVersion(releaseVersion)
82+
}
83+
}

test/release-please/version.js

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
const t = require('tap')
2+
const Version = require('../../lib/release-please/version.js')
3+
4+
const COMMITS = {
5+
major: [{ type: 'feat' }, {}, {}, { breaking: true }],
6+
minor: [{}, {}, { type: 'feat' }],
7+
patch: [{}, { type: 'chore' }, { type: 'fix' }],
8+
}
9+
10+
const COMMIT_NAME = (c) => Object.entries(COMMITS).find(([, i]) => i === c)[0]
11+
12+
t.test('bumps', async (t) => {
13+
const checks = [
14+
// Normal releases
15+
['2.0.0', COMMITS.major, false, '3.0.0'],
16+
['2.0.0', COMMITS.minor, false, '2.1.0'],
17+
['2.0.0', COMMITS.patch, false, '2.0.1'],
18+
// premajor -> normal
19+
['2.0.0-pre.1', COMMITS.major, false, '2.0.0'],
20+
['2.0.0-pre.5', COMMITS.minor, false, '2.0.0'],
21+
['2.0.0-pre.4', COMMITS.patch, false, '2.0.0'],
22+
// preminor -> normal
23+
['2.1.0-pre.1', COMMITS.major, false, '3.0.0'],
24+
['2.1.0-pre.5', COMMITS.minor, false, '2.1.0'],
25+
['2.1.0-pre.4', COMMITS.patch, false, '2.1.0'],
26+
// prepatch -> normal
27+
['2.0.1-pre.1', COMMITS.major, false, '3.0.0'],
28+
['2.0.1-pre.5', COMMITS.minor, false, '2.1.0'],
29+
['2.0.1-pre.4', COMMITS.patch, false, '2.0.1'],
30+
// Prereleases
31+
['2.0.0', COMMITS.major, true, '3.0.0-pre.0'],
32+
['2.0.0', COMMITS.minor, true, '2.1.0-pre.0'],
33+
['2.0.0', COMMITS.patch, true, '2.0.1-pre.0'],
34+
// premajor - prereleases
35+
['2.0.0-pre.1', COMMITS.major, true, '2.0.0-pre.2'],
36+
['2.0.0-pre.1', COMMITS.minor, true, '2.0.0-pre.2'],
37+
['2.0.0-pre.1', COMMITS.patch, true, '2.0.0-pre.2'],
38+
// preminor - prereleases
39+
['2.1.0-pre.1', COMMITS.major, true, '3.0.0-pre.0'],
40+
['2.1.0-pre.1', COMMITS.minor, true, '2.1.0-pre.2'],
41+
['2.1.0-pre.1', COMMITS.patch, true, '2.1.0-pre.2'],
42+
// prepatch - prereleases
43+
['2.0.1-pre.1', COMMITS.major, true, '3.0.0-pre.0'],
44+
['2.0.1-pre.1', COMMITS.minor, true, '2.1.0-pre.0'],
45+
['2.0.1-pre.1', COMMITS.patch, true, '2.0.1-pre.2'],
46+
// different prerelease identifiers
47+
['2.0.0-beta.1', COMMITS.major, true, '2.0.0-beta.2'],
48+
['2.0.0-alpha.1', COMMITS.major, true, '2.0.0-alpha.2'],
49+
['2.0.0-rc.1', COMMITS.major, true, '2.0.0-rc.2'],
50+
['2.0.0-0', COMMITS.major, true, '2.0.0-1'],
51+
]
52+
53+
for (const [version, commits, prerelease, expected] of checks) {
54+
const name = [version, COMMIT_NAME(commits), prerelease ? 'pre' : 'normal', expected]
55+
const r = new Version({ prerelease }).bump(version, commits)
56+
t.equal(
57+
`${r.major}.${r.minor}.${r.patch}${r.preRelease ? `-${r.preRelease}` : ''}`,
58+
expected,
59+
name.join(' - ')
60+
)
61+
}
62+
})

0 commit comments

Comments
 (0)