Skip to content

Commit 6639fc1

Browse files
committed
feat(release): add adjustSemverBumpsForZeroMajorVersion config option
Add a new configuration option `adjustSemverBumpsForZeroMajorVersion` that controls whether semver bumps are adjusted for 0.x versions. When enabled: - 'major' bumps become 'minor' bumps for 0.x versions - 'minor' bumps become 'patch' bumps for 0.x versions - 'premajor' becomes 'preminor' for 0.x versions - 'preminor' becomes 'prepatch' for 0.x versions This option defaults to `false` to maintain backward compatibility with the existing behavior. A TODO comment marks this for switching to `true` by default in v23.
1 parent 31ef5d2 commit 6639fc1

File tree

9 files changed

+314
-48
lines changed

9 files changed

+314
-48
lines changed

packages/nx/schemas/nx-schema.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,6 +1034,11 @@
10341034
]
10351035
}
10361036
},
1037+
"adjustSemverBumpsForZeroMajorVersion": {
1038+
"type": "boolean",
1039+
"description": "Whether to apply the common convention for 0.x versions where breaking changes bump the minor version (instead of major), and new features bump the patch version (instead of minor). When enabled: 'major' bumps become 'minor' bumps for 0.x versions, 'minor' bumps become 'patch' bumps for 0.x versions. Versions 1.0.0 and above are unaffected. This is false by default for backward compatibility.",
1040+
"default": false
1041+
},
10371042
"versionActions": {
10381043
"type": "string",
10391044
"description": "The path to the version actions implementation to use for releasing all projects by default. This can also be overridden on the release group and project levels.",
@@ -1151,6 +1156,11 @@
11511156
]
11521157
}
11531158
},
1159+
"adjustSemverBumpsForZeroMajorVersion": {
1160+
"type": "boolean",
1161+
"description": "Whether to apply the common convention for 0.x versions where breaking changes bump the minor version (instead of major), and new features bump the patch version (instead of minor). When enabled: 'major' bumps become 'minor' bumps for 0.x versions, 'minor' bumps become 'patch' bumps for 0.x versions. Versions 1.0.0 and above are unaffected. This is false by default for backward compatibility.",
1162+
"default": false
1163+
},
11541164
"versionActions": {
11551165
"type": "string",
11561166
"description": "The path to the version actions implementation to use for releasing all projects by default. This can also be overridden on the release group and project levels.",

packages/nx/src/command-line/release/config/config.spec.ts

Lines changed: 146 additions & 0 deletions
Large diffs are not rendered by default.

packages/nx/src/command-line/release/config/config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,9 @@ export async function createNxReleaseConfig(
378378
userConfig.version?.preserveMatchingDependencyRanges ?? true,
379379
logUnchangedProjects: userConfig.version?.logUnchangedProjects ?? true,
380380
updateDependents: userConfig.version?.updateDependents ?? 'always',
381+
// TODO(v23): change the default value of this to true
382+
adjustSemverBumpsForZeroMajorVersion:
383+
userConfig.version?.adjustSemverBumpsForZeroMajorVersion ?? false,
381384
} as DeepRequired<NxReleaseConfiguration['version']>,
382385
changelog: {
383386
git: changelogGitDefaults,

packages/nx/src/command-line/release/utils/release-graph.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export interface FinalConfigForProject {
3535
versionPrefix: NxReleaseVersionConfiguration['versionPrefix'];
3636
preserveLocalDependencyProtocols: NxReleaseVersionConfiguration['preserveLocalDependencyProtocols'];
3737
preserveMatchingDependencyRanges: NxReleaseVersionConfiguration['preserveMatchingDependencyRanges'];
38+
adjustSemverBumpsForZeroMajorVersion: NxReleaseVersionConfiguration['adjustSemverBumpsForZeroMajorVersion'];
3839
versionActionsOptions: NxReleaseVersionConfiguration['versionActionsOptions'];
3940
manifestRootsToUpdate: Array<
4041
Exclude<
@@ -887,6 +888,17 @@ Valid values are: ${validReleaseVersionPrefixes
887888
releaseGroupVersionConfig?.preserveMatchingDependencyRanges ??
888889
true;
889890

891+
/**
892+
* adjustSemverBumpsForZeroMajorVersion
893+
*
894+
* TODO(v23): change the default value of this to true
895+
* This is false by default for backward compatibility.
896+
*/
897+
const adjustSemverBumpsForZeroMajorVersion =
898+
projectVersionConfig?.adjustSemverBumpsForZeroMajorVersion ??
899+
releaseGroupVersionConfig?.adjustSemverBumpsForZeroMajorVersion ??
900+
false;
901+
890902
/**
891903
* fallbackCurrentVersionResolver, defaults to disk when performing a first release, otherwise undefined
892904
*/
@@ -931,6 +943,7 @@ Valid values are: ${validReleaseVersionPrefixes
931943
versionPrefix,
932944
preserveLocalDependencyProtocols,
933945
preserveMatchingDependencyRanges,
946+
adjustSemverBumpsForZeroMajorVersion,
934947
versionActionsOptions,
935948
manifestRootsToUpdate,
936949
dockerOptions,

packages/nx/src/command-line/release/utils/semver.spec.ts

Lines changed: 90 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -77,63 +77,112 @@ describe('semver', () => {
7777
});
7878

7979
describe('0.x version handling', () => {
80-
describe('0.x.y versions (shift bumps down)', () => {
81-
it('should bump 0.1.0 + major to 0.2.0', () => {
82-
expect(deriveNewSemverVersion('0.1.0', 'major')).toBe('0.2.0');
80+
describe('default behavior (adjustSemverBumpsForZeroMajorVersion: false)', () => {
81+
it('should use standard semver for 0.x versions by default', () => {
82+
// Without the option, standard semver rules apply
83+
expect(deriveNewSemverVersion('0.1.0', 'major')).toBe('1.0.0');
84+
expect(deriveNewSemverVersion('0.1.0', 'minor')).toBe('0.2.0');
85+
expect(deriveNewSemverVersion('0.1.0', 'patch')).toBe('0.1.1');
86+
expect(deriveNewSemverVersion('0.0.1', 'major')).toBe('1.0.0');
87+
expect(deriveNewSemverVersion('0.0.1', 'minor')).toBe('0.1.0');
88+
expect(deriveNewSemverVersion('0.0.1', 'patch')).toBe('0.0.2');
8389
});
8490

85-
it('should bump 0.1.0 + minor to 0.1.1', () => {
86-
expect(deriveNewSemverVersion('0.1.0', 'minor')).toBe('0.1.1');
91+
it('should use standard semver for prerelease specifiers on 0.x versions by default', () => {
92+
expect(deriveNewSemverVersion('0.1.0', 'premajor')).toBe('1.0.0-0');
93+
expect(deriveNewSemverVersion('0.1.0', 'preminor')).toBe('0.2.0-0');
94+
expect(deriveNewSemverVersion('0.1.0', 'prepatch')).toBe('0.1.1-0');
95+
expect(deriveNewSemverVersion('0.1.0-0', 'prerelease')).toBe(
96+
'0.1.0-1'
97+
);
8798
});
99+
});
88100

89-
it('should bump 0.1.0 + patch to 0.1.1', () => {
90-
expect(deriveNewSemverVersion('0.1.0', 'patch')).toBe('0.1.1');
91-
});
101+
describe('adjustSemverBumpsForZeroMajorVersion: true', () => {
102+
const opts = { adjustSemverBumpsForZeroMajorVersion: true };
92103

93-
it('should bump 0.0.1 + major to 0.1.0', () => {
94-
expect(deriveNewSemverVersion('0.0.1', 'major')).toBe('0.1.0');
95-
});
104+
describe('0.x.y versions (shift bumps down)', () => {
105+
it('should bump 0.1.0 + major to 0.2.0', () => {
106+
expect(
107+
deriveNewSemverVersion('0.1.0', 'major', undefined, opts)
108+
).toBe('0.2.0');
109+
});
96110

97-
it('should bump 0.0.1 + minor to 0.0.2', () => {
98-
expect(deriveNewSemverVersion('0.0.1', 'minor')).toBe('0.0.2');
99-
});
111+
it('should bump 0.1.0 + minor to 0.1.1', () => {
112+
expect(
113+
deriveNewSemverVersion('0.1.0', 'minor', undefined, opts)
114+
).toBe('0.1.1');
115+
});
100116

101-
it('should bump 0.0.1 + patch to 0.0.2', () => {
102-
expect(deriveNewSemverVersion('0.0.1', 'patch')).toBe('0.0.2');
103-
});
104-
});
117+
it('should bump 0.1.0 + patch to 0.1.1', () => {
118+
expect(
119+
deriveNewSemverVersion('0.1.0', 'patch', undefined, opts)
120+
).toBe('0.1.1');
121+
});
105122

106-
describe('1.x.y+ versions (no change)', () => {
107-
it('should bump 1.0.0 + major to 2.0.0', () => {
108-
expect(deriveNewSemverVersion('1.0.0', 'major')).toBe('2.0.0');
109-
});
123+
it('should bump 0.0.1 + major to 0.1.0', () => {
124+
expect(
125+
deriveNewSemverVersion('0.0.1', 'major', undefined, opts)
126+
).toBe('0.1.0');
127+
});
110128

111-
it('should bump 1.0.0 + minor to 1.1.0', () => {
112-
expect(deriveNewSemverVersion('1.0.0', 'minor')).toBe('1.1.0');
113-
});
129+
it('should bump 0.0.1 + minor to 0.0.2', () => {
130+
expect(
131+
deriveNewSemverVersion('0.0.1', 'minor', undefined, opts)
132+
).toBe('0.0.2');
133+
});
114134

115-
it('should bump 1.0.0 + patch to 1.0.1', () => {
116-
expect(deriveNewSemverVersion('1.0.0', 'patch')).toBe('1.0.1');
135+
it('should bump 0.0.1 + patch to 0.0.2', () => {
136+
expect(
137+
deriveNewSemverVersion('0.0.1', 'patch', undefined, opts)
138+
).toBe('0.0.2');
139+
});
117140
});
118-
});
119141

120-
describe('prerelease specifiers (consistent with 0.x shift)', () => {
121-
it('should shift premajor to preminor for 0.x versions', () => {
122-
expect(deriveNewSemverVersion('0.1.0', 'premajor')).toBe('0.2.0-0');
123-
});
142+
describe('1.x.y+ versions (no change)', () => {
143+
it('should bump 1.0.0 + major to 2.0.0', () => {
144+
expect(
145+
deriveNewSemverVersion('1.0.0', 'major', undefined, opts)
146+
).toBe('2.0.0');
147+
});
124148

125-
it('should shift preminor to prepatch for 0.x versions', () => {
126-
expect(deriveNewSemverVersion('0.1.0', 'preminor')).toBe('0.1.1-0');
127-
});
149+
it('should bump 1.0.0 + minor to 1.1.0', () => {
150+
expect(
151+
deriveNewSemverVersion('1.0.0', 'minor', undefined, opts)
152+
).toBe('1.1.0');
153+
});
128154

129-
it('should not adjust prepatch for 0.x versions', () => {
130-
expect(deriveNewSemverVersion('0.1.0', 'prepatch')).toBe('0.1.1-0');
155+
it('should bump 1.0.0 + patch to 1.0.1', () => {
156+
expect(
157+
deriveNewSemverVersion('1.0.0', 'patch', undefined, opts)
158+
).toBe('1.0.1');
159+
});
131160
});
132161

133-
it('should not adjust prerelease for 0.x versions', () => {
134-
expect(deriveNewSemverVersion('0.1.0-0', 'prerelease')).toBe(
135-
'0.1.0-1'
136-
);
162+
describe('prerelease specifiers (consistent with 0.x shift)', () => {
163+
it('should shift premajor to preminor for 0.x versions', () => {
164+
expect(
165+
deriveNewSemverVersion('0.1.0', 'premajor', undefined, opts)
166+
).toBe('0.2.0-0');
167+
});
168+
169+
it('should shift preminor to prepatch for 0.x versions', () => {
170+
expect(
171+
deriveNewSemverVersion('0.1.0', 'preminor', undefined, opts)
172+
).toBe('0.1.1-0');
173+
});
174+
175+
it('should not adjust prepatch for 0.x versions', () => {
176+
expect(
177+
deriveNewSemverVersion('0.1.0', 'prepatch', undefined, opts)
178+
).toBe('0.1.1-0');
179+
});
180+
181+
it('should not adjust prerelease for 0.x versions', () => {
182+
expect(
183+
deriveNewSemverVersion('0.1.0-0', 'prerelease', undefined, opts)
184+
).toBe('0.1.0-1');
185+
});
137186
});
138187
});
139188
});

packages/nx/src/command-line/release/utils/semver.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ function adjustSpecifierForZeroMajorVersion(
102102
export function deriveNewSemverVersion(
103103
currentSemverVersion: string,
104104
semverSpecifier: string,
105-
preid?: string
105+
preid?: string,
106+
options?: { adjustSemverBumpsForZeroMajorVersion?: boolean }
106107
) {
107108
if (!valid(currentSemverVersion)) {
108109
throw new Error(
@@ -112,11 +113,13 @@ export function deriveNewSemverVersion(
112113

113114
let newVersion = semverSpecifier;
114115
if (isRelativeVersionKeyword(semverSpecifier)) {
115-
// Adjust for 0.x versions
116-
const adjustedSpecifier = adjustSpecifierForZeroMajorVersion(
117-
semverSpecifier,
118-
currentSemverVersion
119-
);
116+
// Adjust for 0.x versions if explicitly enabled
117+
const adjustedSpecifier = options?.adjustSemverBumpsForZeroMajorVersion
118+
? adjustSpecifierForZeroMajorVersion(
119+
semverSpecifier,
120+
currentSemverVersion
121+
)
122+
: semverSpecifier;
120123
// Derive the new version from the current version combined with the adjusted version specifier.
121124
const derivedVersion = inc(
122125
currentSemverVersion,

0 commit comments

Comments
 (0)