Skip to content

Commit b070888

Browse files
authored
feat: add PrereleaseVersioningStrategy (#1981)
* feat: add PrereleaseVersioningStrategy * test: fix snapshot test * test: add test for wrapping number * test: add all versioning test cases * fix: implement logic to keep version and bump patch
1 parent b2de23b commit b070888

4 files changed

Lines changed: 498 additions & 1 deletion

File tree

__snapshots__/cli.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ Options:
196196
generated? [boolean] [default: false]
197197
--versioning-strategy strategy used for bumping versions
198198
[choices: "always-bump-major", "always-bump-minor", "always-bump-patch",
199-
"default", "service-pack"] [default: "default"]
199+
"default", "prerelease", "service-pack"] [default: "default"]
200200
--changelog-path where can the CHANGELOG be found in the
201201
project? [string] [default: "CHANGELOG.md"]
202202
--changelog-type type of changelog to build

src/factories/versioning-strategy-factory.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {AlwaysBumpMajor} from '../versioning-strategies/always-bump-major';
2020
import {ServicePackVersioningStrategy} from '../versioning-strategies/service-pack';
2121
import {GitHub} from '../github';
2222
import {ConfigurationError} from '../errors';
23+
import {PrereleaseVersioningStrategy} from '../versioning-strategies/prerelease';
2324

2425
export type VersioningStrategyType = string;
2526

@@ -40,6 +41,7 @@ const versioningTypes: Record<string, VersioningStrategyBuilder> = {
4041
'always-bump-minor': options => new AlwaysBumpMinor(options),
4142
'always-bump-major': options => new AlwaysBumpMajor(options),
4243
'service-pack': options => new ServicePackVersioningStrategy(options),
44+
prerelease: options => new PrereleaseVersioningStrategy(options),
4345
};
4446

4547
export function buildVersioningStrategy(
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
// Copyright 2023 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import {DefaultVersioningStrategy} from './default';
16+
import {Version} from '../version';
17+
import {ConventionalCommit} from '..';
18+
import {VersionUpdater, CustomVersionUpdate} from '../versioning-strategy';
19+
20+
const PRERELEASE_PATTERN = /^(?<type>[a-z]+)(?<number>\d+)$/;
21+
22+
class PrereleasePatchVersionUpdate implements VersionUpdater {
23+
/**
24+
* Returns the new bumped version
25+
*
26+
* @param {Version} version The current version
27+
* @returns {Version} The bumped version
28+
*/
29+
bump(version: Version): Version {
30+
if (version.preRelease) {
31+
const match = version.preRelease.match(PRERELEASE_PATTERN);
32+
if (match?.groups) {
33+
const numberLength = match.groups.number.length;
34+
const nextPrereleaseNumber = Number(match.groups.number) + 1;
35+
const paddedNextPrereleaseNumber = `${nextPrereleaseNumber}`.padStart(
36+
numberLength,
37+
'0'
38+
);
39+
const nextPrerelease = `${match.groups.type}${paddedNextPrereleaseNumber}`;
40+
return new Version(
41+
version.major,
42+
version.minor,
43+
version.patch,
44+
nextPrerelease,
45+
version.build
46+
);
47+
}
48+
}
49+
return new Version(
50+
version.major,
51+
version.minor,
52+
version.patch + 1,
53+
version.preRelease,
54+
version.build
55+
);
56+
}
57+
}
58+
59+
class PrereleaseMinorVersionUpdate implements VersionUpdater {
60+
/**
61+
* Returns the new bumped version
62+
*
63+
* @param {Version} version The current version
64+
* @returns {Version} The bumped version
65+
*/
66+
bump(version: Version): Version {
67+
if (version.preRelease) {
68+
const match = version.preRelease.match(PRERELEASE_PATTERN);
69+
if (match?.groups) {
70+
const numberLength = match.groups.number.length;
71+
const prereleaseNumber = Number(match.groups.number);
72+
73+
let nextPrereleaseNumber = 1;
74+
let nextMinorNumber = version.minor + 1;
75+
let nextPatchNumber = 0;
76+
if (version.patch === 0) {
77+
// this is already the next minor candidate, then bump the pre-release number
78+
nextPrereleaseNumber = prereleaseNumber + 1;
79+
nextMinorNumber = version.minor;
80+
nextPatchNumber = version.patch;
81+
}
82+
83+
const paddedNextPrereleaseNumber = `${nextPrereleaseNumber}`.padStart(
84+
numberLength,
85+
'0'
86+
);
87+
const nextPrerelease = `${match.groups.type}${paddedNextPrereleaseNumber}`;
88+
return new Version(
89+
version.major,
90+
nextMinorNumber,
91+
nextPatchNumber,
92+
nextPrerelease,
93+
version.build
94+
);
95+
}
96+
}
97+
return new Version(
98+
version.major,
99+
version.minor + 1,
100+
0,
101+
version.preRelease,
102+
version.build
103+
);
104+
}
105+
}
106+
107+
class PrereleaseMajorVersionUpdate implements VersionUpdater {
108+
/**
109+
* Returns the new bumped version
110+
*
111+
* @param {Version} version The current version
112+
* @returns {Version} The bumped version
113+
*/
114+
bump(version: Version): Version {
115+
if (version.preRelease) {
116+
const match = version.preRelease.match(PRERELEASE_PATTERN);
117+
if (match?.groups) {
118+
const numberLength = match.groups.number.length;
119+
const prereleaseNumber = Number(match.groups.number);
120+
121+
let nextPrereleaseNumber = 1;
122+
let nextMajorNumber = version.major + 1;
123+
let nextMinorNumber = 0;
124+
let nextPatchNumber = 0;
125+
if (version.patch === 0 && version.minor === 0) {
126+
// this is already the next major candidate, then bump the pre-release number
127+
nextPrereleaseNumber = prereleaseNumber + 1;
128+
nextMajorNumber = version.major;
129+
nextMinorNumber = version.minor;
130+
nextPatchNumber = version.patch;
131+
}
132+
133+
const paddedNextPrereleaseNumber = `${nextPrereleaseNumber}`.padStart(
134+
numberLength,
135+
'0'
136+
);
137+
const nextPrerelease = `${match.groups.type}${paddedNextPrereleaseNumber}`;
138+
return new Version(
139+
nextMajorNumber,
140+
nextMinorNumber,
141+
nextPatchNumber,
142+
nextPrerelease,
143+
version.build
144+
);
145+
}
146+
}
147+
return new Version(
148+
version.major + 1,
149+
0,
150+
0,
151+
version.preRelease,
152+
version.build
153+
);
154+
}
155+
}
156+
157+
/**
158+
* This versioning strategy will increment the pre-release number for patch
159+
* bumps if there is a pre-release number (preserving any leading 0s).
160+
* Example: 1.2.3-beta01 -> 1.2.3-beta02.
161+
*/
162+
export class PrereleaseVersioningStrategy extends DefaultVersioningStrategy {
163+
determineReleaseType(
164+
version: Version,
165+
commits: ConventionalCommit[]
166+
): VersionUpdater {
167+
// iterate through list of commits and find biggest commit type
168+
let breaking = 0;
169+
let features = 0;
170+
for (const commit of commits) {
171+
const releaseAs = commit.notes.find(note => note.title === 'RELEASE AS');
172+
if (releaseAs) {
173+
// commits are handled newest to oldest, so take the first one (newest) found
174+
this.logger.debug(
175+
`found Release-As: ${releaseAs.text}, forcing version`
176+
);
177+
return new CustomVersionUpdate(
178+
Version.parse(releaseAs.text).toString()
179+
);
180+
}
181+
if (commit.breaking) {
182+
breaking++;
183+
} else if (commit.type === 'feat' || commit.type === 'feature') {
184+
features++;
185+
}
186+
}
187+
188+
if (breaking > 0) {
189+
if (version.major < 1 && this.bumpMinorPreMajor) {
190+
return new PrereleaseMinorVersionUpdate();
191+
} else {
192+
return new PrereleaseMajorVersionUpdate();
193+
}
194+
} else if (features > 0) {
195+
if (version.major < 1 && this.bumpPatchForMinorPreMajor) {
196+
return new PrereleasePatchVersionUpdate();
197+
} else {
198+
return new PrereleaseMinorVersionUpdate();
199+
}
200+
}
201+
return new PrereleasePatchVersionUpdate();
202+
}
203+
}

0 commit comments

Comments
 (0)