Skip to content
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
10 changes: 10 additions & 0 deletions packages/nx/schemas/nx-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1034,6 +1034,11 @@
]
}
},
"adjustSemverBumpsForZeroMajorVersion": {
"type": "boolean",
"description": "Whether to strictly follow SemVer V2 spec 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.",
"default": false
},
"versionActions": {
"type": "string",
"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.",
Expand Down Expand Up @@ -1151,6 +1156,11 @@
]
}
},
"adjustSemverBumpsForZeroMajorVersion": {
"type": "boolean",
"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.",
"default": false
},
"versionActions": {
"type": "string",
"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.",
Expand Down
146 changes: 146 additions & 0 deletions packages/nx/src/command-line/release/config/config.spec.ts

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions packages/nx/src/command-line/release/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,9 @@ export async function createNxReleaseConfig(
userConfig.version?.preserveMatchingDependencyRanges ?? true,
logUnchangedProjects: userConfig.version?.logUnchangedProjects ?? true,
updateDependents: userConfig.version?.updateDependents ?? 'always',
// TODO(v23): change the default value of this to true
adjustSemverBumpsForZeroMajorVersion:
userConfig.version?.adjustSemverBumpsForZeroMajorVersion ?? false,
} as DeepRequired<NxReleaseConfiguration['version']>,
changelog: {
git: changelogGitDefaults,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ describe('filterReleaseGroups()', () => {
},
"resolvedVersionPlans": false,
"version": {
"adjustSemverBumpsForZeroMajorVersion": undefined,
"conventionalCommits": false,
"currentVersionResolver": undefined,
"currentVersionResolverMetadata": undefined,
Expand Down Expand Up @@ -226,6 +227,7 @@ describe('filterReleaseGroups()', () => {
},
"resolvedVersionPlans": false,
"version": {
"adjustSemverBumpsForZeroMajorVersion": undefined,
"conventionalCommits": false,
"currentVersionResolver": undefined,
"currentVersionResolverMetadata": undefined,
Expand Down Expand Up @@ -267,6 +269,7 @@ describe('filterReleaseGroups()', () => {
},
"resolvedVersionPlans": false,
"version": {
"adjustSemverBumpsForZeroMajorVersion": undefined,
"conventionalCommits": false,
"currentVersionResolver": undefined,
"currentVersionResolverMetadata": undefined,
Expand Down Expand Up @@ -306,6 +309,7 @@ describe('filterReleaseGroups()', () => {
},
"resolvedVersionPlans": false,
"version": {
"adjustSemverBumpsForZeroMajorVersion": undefined,
"conventionalCommits": false,
"currentVersionResolver": undefined,
"currentVersionResolverMetadata": undefined,
Expand Down Expand Up @@ -357,6 +361,7 @@ describe('filterReleaseGroups()', () => {
logUnchangedProjects: undefined,
versionActions: undefined,
versionActionsOptions: undefined,
adjustSemverBumpsForZeroMajorVersion: undefined,
},
releaseTag: {
pattern: '',
Expand Down Expand Up @@ -481,6 +486,7 @@ describe('filterReleaseGroups()', () => {
},
"resolvedVersionPlans": false,
"version": {
"adjustSemverBumpsForZeroMajorVersion": undefined,
"conventionalCommits": false,
"currentVersionResolver": undefined,
"currentVersionResolverMetadata": undefined,
Expand Down Expand Up @@ -522,6 +528,7 @@ describe('filterReleaseGroups()', () => {
},
"resolvedVersionPlans": false,
"version": {
"adjustSemverBumpsForZeroMajorVersion": undefined,
"conventionalCommits": false,
"currentVersionResolver": undefined,
"currentVersionResolverMetadata": undefined,
Expand Down Expand Up @@ -619,6 +626,7 @@ describe('filterReleaseGroups()', () => {
},
"resolvedVersionPlans": false,
"version": {
"adjustSemverBumpsForZeroMajorVersion": undefined,
"conventionalCommits": false,
"currentVersionResolver": undefined,
"currentVersionResolverMetadata": undefined,
Expand Down Expand Up @@ -660,6 +668,7 @@ describe('filterReleaseGroups()', () => {
},
"resolvedVersionPlans": false,
"version": {
"adjustSemverBumpsForZeroMajorVersion": undefined,
"conventionalCommits": false,
"currentVersionResolver": undefined,
"currentVersionResolverMetadata": undefined,
Expand Down
13 changes: 13 additions & 0 deletions packages/nx/src/command-line/release/utils/release-graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export interface FinalConfigForProject {
versionPrefix: NxReleaseVersionConfiguration['versionPrefix'];
preserveLocalDependencyProtocols: NxReleaseVersionConfiguration['preserveLocalDependencyProtocols'];
preserveMatchingDependencyRanges: NxReleaseVersionConfiguration['preserveMatchingDependencyRanges'];
adjustSemverBumpsForZeroMajorVersion: NxReleaseVersionConfiguration['adjustSemverBumpsForZeroMajorVersion'];
versionActionsOptions: NxReleaseVersionConfiguration['versionActionsOptions'];
manifestRootsToUpdate: Array<
Exclude<
Expand Down Expand Up @@ -887,6 +888,17 @@ Valid values are: ${validReleaseVersionPrefixes
releaseGroupVersionConfig?.preserveMatchingDependencyRanges ??
true;

/**
* adjustSemverBumpsForZeroMajorVersion
*
* TODO(v23): change the default value of this to true
* This is false by default for backward compatibility.
*/
const adjustSemverBumpsForZeroMajorVersion =
projectVersionConfig?.adjustSemverBumpsForZeroMajorVersion ??
releaseGroupVersionConfig?.adjustSemverBumpsForZeroMajorVersion ??
false;

/**
* fallbackCurrentVersionResolver, defaults to disk when performing a first release, otherwise undefined
*/
Expand Down Expand Up @@ -931,6 +943,7 @@ Valid values are: ${validReleaseVersionPrefixes
versionPrefix,
preserveLocalDependencyProtocols,
preserveMatchingDependencyRanges,
adjustSemverBumpsForZeroMajorVersion,
versionActionsOptions,
manifestRootsToUpdate,
dockerOptions,
Expand Down
111 changes: 111 additions & 0 deletions packages/nx/src/command-line/release/utils/semver.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,117 @@ describe('semver', () => {
`"Invalid semver version specifier "foo" provided. Please provide either a valid semver version or a valid semver version keyword."`
);
});

describe('0.x version handling', () => {
describe('default behavior (adjustSemverBumpsForZeroMajorVersion: false)', () => {
it('should use standard semver for 0.x versions by default', () => {
// Without the option, standard semver rules apply
expect(deriveNewSemverVersion('0.1.0', 'major')).toBe('1.0.0');
expect(deriveNewSemverVersion('0.1.0', 'minor')).toBe('0.2.0');
expect(deriveNewSemverVersion('0.1.0', 'patch')).toBe('0.1.1');
expect(deriveNewSemverVersion('0.0.1', 'major')).toBe('1.0.0');
expect(deriveNewSemverVersion('0.0.1', 'minor')).toBe('0.1.0');
expect(deriveNewSemverVersion('0.0.1', 'patch')).toBe('0.0.2');
});

it('should use standard semver for prerelease specifiers on 0.x versions by default', () => {
expect(deriveNewSemverVersion('0.1.0', 'premajor')).toBe('1.0.0-0');
expect(deriveNewSemverVersion('0.1.0', 'preminor')).toBe('0.2.0-0');
expect(deriveNewSemverVersion('0.1.0', 'prepatch')).toBe('0.1.1-0');
expect(deriveNewSemverVersion('0.1.0-0', 'prerelease')).toBe(
'0.1.0-1'
);
});
});

describe('adjustSemverBumpsForZeroMajorVersion: true', () => {
const opts = { adjustSemverBumpsForZeroMajorVersion: true };

describe('0.x.y versions (shift bumps down)', () => {
it('should bump 0.1.0 + major to 0.2.0', () => {
expect(
deriveNewSemverVersion('0.1.0', 'major', undefined, opts)
).toBe('0.2.0');
});

it('should bump 0.1.0 + minor to 0.1.1', () => {
expect(
deriveNewSemverVersion('0.1.0', 'minor', undefined, opts)
).toBe('0.1.1');
});

it('should bump 0.1.0 + patch to 0.1.1', () => {
expect(
deriveNewSemverVersion('0.1.0', 'patch', undefined, opts)
).toBe('0.1.1');
});

it('should bump 0.0.1 + major to 0.1.0', () => {
expect(
deriveNewSemverVersion('0.0.1', 'major', undefined, opts)
).toBe('0.1.0');
});

it('should bump 0.0.1 + minor to 0.0.2', () => {
expect(
deriveNewSemverVersion('0.0.1', 'minor', undefined, opts)
).toBe('0.0.2');
});

it('should bump 0.0.1 + patch to 0.0.2', () => {
expect(
deriveNewSemverVersion('0.0.1', 'patch', undefined, opts)
).toBe('0.0.2');
});
});

describe('1.x.y+ versions (no change)', () => {
it('should bump 1.0.0 + major to 2.0.0', () => {
expect(
deriveNewSemverVersion('1.0.0', 'major', undefined, opts)
).toBe('2.0.0');
});

it('should bump 1.0.0 + minor to 1.1.0', () => {
expect(
deriveNewSemverVersion('1.0.0', 'minor', undefined, opts)
).toBe('1.1.0');
});

it('should bump 1.0.0 + patch to 1.0.1', () => {
expect(
deriveNewSemverVersion('1.0.0', 'patch', undefined, opts)
).toBe('1.0.1');
});
});

describe('prerelease specifiers (consistent with 0.x shift)', () => {
it('should shift premajor to preminor for 0.x versions', () => {
expect(
deriveNewSemverVersion('0.1.0', 'premajor', undefined, opts)
).toBe('0.2.0-0');
});

it('should shift preminor to prepatch for 0.x versions', () => {
expect(
deriveNewSemverVersion('0.1.0', 'preminor', undefined, opts)
).toBe('0.1.1-0');
});

it('should not adjust prepatch for 0.x versions', () => {
expect(
deriveNewSemverVersion('0.1.0', 'prepatch', undefined, opts)
).toBe('0.1.1-0');
});

it('should not adjust prerelease for 0.x versions', () => {
expect(
deriveNewSemverVersion('0.1.0-0', 'prerelease', undefined, opts)
).toBe('0.1.0-1');
});
});
});
});
});
// tests for determineSemverChange()
describe('determineSemverChange()', () => {
Expand Down
56 changes: 52 additions & 4 deletions packages/nx/src/command-line/release/utils/semver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* https://github.com/unjs/changelogen
*/

import { RELEASE_TYPES, ReleaseType, inc, valid } from 'semver';
import { major, RELEASE_TYPES, ReleaseType, inc, valid } from 'semver';
import { NxReleaseConfig } from '../config/config';
import { GitCommit } from './git';

Expand Down Expand Up @@ -63,10 +63,47 @@ export function determineSemverChange(
return semverChangePerProject;
}

/**
* For 0.x versions, shifts semver bump types down to follow
* the common convention where breaking changes bump minor, and
* new features bump patch.
*
* - 'major' -> 'minor'
* - 'premajor' -> 'preminor'
* - 'minor' -> 'patch'
* - 'preminor' -> 'prepatch'
* - 'patch' -> 'patch' (unchanged)
* - 'prepatch' -> 'prepatch' (unchanged)
* - 'prerelease' -> 'prerelease' (unchanged)
*/
function adjustSpecifierForZeroMajorVersion(
specifier: string,
currentVersion: string
): string {
// Only adjust for 0.x versions
if (major(currentVersion) !== 0) {
return specifier;
}

switch (specifier) {
case 'major':
return 'minor';
case 'premajor':
return 'preminor';
case 'minor':
return 'patch';
case 'preminor':
return 'prepatch';
default:
return specifier;
}
}

export function deriveNewSemverVersion(
currentSemverVersion: string,
semverSpecifier: string,
preid?: string
preid?: string,
options?: { adjustSemverBumpsForZeroMajorVersion?: boolean }
) {
if (!valid(currentSemverVersion)) {
throw new Error(
Expand All @@ -76,8 +113,19 @@ export function deriveNewSemverVersion(

let newVersion = semverSpecifier;
if (isRelativeVersionKeyword(semverSpecifier)) {
// Derive the new version from the current version combined with the new version specifier.
const derivedVersion = inc(currentSemverVersion, semverSpecifier, preid);
// Adjust for 0.x versions if explicitly enabled
const adjustedSpecifier = options?.adjustSemverBumpsForZeroMajorVersion
? adjustSpecifierForZeroMajorVersion(
semverSpecifier,
currentSemverVersion
)
: semverSpecifier;
// Derive the new version from the current version combined with the adjusted version specifier.
const derivedVersion = inc(
currentSemverVersion,
adjustedSpecifier as ReleaseType,
preid
);
if (!derivedVersion) {
throw new Error(
`Unable to derive new version from current version "${currentSemverVersion}" and version specifier "${semverSpecifier}"`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ export function createVersionConfig() {
logUnchangedProjects: undefined,
versionActions: undefined,
versionActionsOptions: undefined,
adjustSemverBumpsForZeroMajorVersion: undefined,
};
}
Loading
Loading