Skip to content

Commit 3027707

Browse files
authored
fix(release): apply preid to dependent patch bumps (#35381)
## Current Behavior When running \`nx release version --preid rc\` with a project filter, projects with a dependent patch bump (from another project being bumped) do not have the preid applied. semver.inc('1.0.0', 'patch', 'rc') returns '1.0.1' because semver silently ignores preid for non-prerelease specifiers. ## Expected Behavior The preid is applied to dependent patch bumps, so dependents get '0.0.2-rc.0' instead of '0.0.2'. The fix adds `applyPreidToBumpType` which converts patch to prepatch, minor to preminor, and major to premajor when a preid is set. ## Related Issue(s) Fixes #33488
1 parent 0e3b098 commit 3027707

7 files changed

Lines changed: 275 additions & 4 deletions

File tree

astro-docs/src/content/docs/guides/Nx Release/update-dependents.mdoc

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,64 @@ For example, if `project-b` is bumped to version `2.1.0`, `project-a` will be up
4747
}
4848
}
4949
```
50+
51+
### Propagating `--preid` to dependent bumps
52+
53+
Side-effectful bumps are always plain patch versions by default, even when the project that triggered them is being released as a prerelease.
54+
For example, run:
55+
56+
```shell
57+
nx release version prepatch --preid rc --projects project-b
58+
```
59+
60+
`project-b` moves from `2.0.0` to `2.0.1-rc.0`, but `project-a` still receives a stable side-effectful patch to `1.0.1`:
61+
62+
```json
63+
// project-a after (default behavior)
64+
{
65+
"name": "project-a",
66+
"version": "1.0.1",
67+
"dependencies": {
68+
"project-b": "2.0.1-rc.0"
69+
}
70+
}
71+
```
72+
73+
That default reflects a deliberate tradeoff. Consuming a prerelease build of a dependency doesn't necessarily mean the dependent itself is a prerelease, and keeping side-effectful bumps on stable versions allows them to ship without promoting every project up the graph into a prerelease.
74+
75+
To propagate the `--preid` value through to dependents, enable `version.applyPreidToDependents` in `nx.json`:
76+
77+
```json
78+
{
79+
"release": {
80+
"version": {
81+
"updateDependents": "always",
82+
"applyPreidToDependents": true
83+
}
84+
}
85+
}
86+
```
87+
88+
With this option enabled and `--preid rc` set, `project-a` receives a `prepatch` bump using the same preid:
89+
90+
```json
91+
// project-a after (applyPreidToDependents: true)
92+
{
93+
"name": "project-a",
94+
"version": "1.0.1-rc.0",
95+
"dependencies": {
96+
"project-b": "2.0.1-rc.0"
97+
}
98+
}
99+
```
100+
101+
#### When to enable it
102+
103+
Enable `applyPreidToDependents` when:
104+
105+
- You publish a set of packages that release together (even if versioned independently) and want a single `nx release ... --preid rc` invocation to produce a consistent prerelease across all of them.
106+
- Your downstream users install all affected packages as a set and would be surprised to see a stable-version dependent pick up a prerelease dependency.
107+
108+
Leave it off (the default) when a stable `1.0.1` of `project-a` depending on an `rc` of `project-b` is a valid, intentional release for you. Prerelease dependencies shouldn't automatically promote their consumers into prereleases.
109+
110+
The option can also be set per release group under `release.groups.<group>.version.applyPreidToDependents` to scope the behavior to only certain groups.

astro-docs/src/content/docs/reference/nx-json.mdoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,12 @@ Some important changes in Nx 22:
591591
- `releaseTag.strictPreid` now defaults to `true`
592592
- Fixed Release Group Release Tag Pattern now defaults to `{releaseGroupName}-v{version}`
593593

594+
#### Propagating `--preid` to dependent bumps
595+
596+
When versioning with `--preid`, the implicit patch bumps given to dependents (and to other projects in a fixed release group) are stable patches by default. That means `project-b` being bumped to `2.0.1-rc.0` still bumps its dependent `project-a` to `1.0.1`, not `1.0.1-rc.0`.
597+
598+
Set `version.applyPreidToDependents` to `true` if you want the `--preid` value to flow through to those side-effectful bumps instead. It can also be set per release group under `release.groups.<group>.version.applyPreidToDependents`. See [Update Dependents](/docs/guides/nx-release/update-dependents#propagating-preid-to-dependent-bumps) for the full walkthrough and guidance on when to enable it.
599+
594600
### Changelog
595601

596602
The `changelog` property configures the changelog phase of the release process. It is used to generate a changelog for your projects, and commit it to your repository.

packages/nx/schemas/nx-schema.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,6 +1175,11 @@
11751175
"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.",
11761176
"default": false
11771177
},
1178+
"applyPreidToDependents": {
1179+
"type": "boolean",
1180+
"description": "Whether to apply the --preid value to the implicit patch bumps given to dependents (and other projects in a fixed release group) when a dependency is versioned. When true, those dependents become prepatch bumps using the same preid (e.g. 1.0.1-rc.0 instead of 1.0.1). This is false by default for backward compatibility.",
1181+
"default": false
1182+
},
11781183
"versionActions": {
11791184
"type": "string",
11801185
"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.",
@@ -1297,6 +1302,11 @@
12971302
"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.",
12981303
"default": false
12991304
},
1305+
"applyPreidToDependents": {
1306+
"type": "boolean",
1307+
"description": "Whether to apply the --preid value to the implicit patch bumps given to dependents (and other projects in a fixed release group) when a dependency is versioned. When true, those dependents become prepatch bumps using the same preid (e.g. 1.0.1-rc.0 instead of 1.0.1). This is false by default for backward compatibility.",
1308+
"default": false
1309+
},
13001310
"versionActions": {
13011311
"type": "string",
13021312
"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/utils/release-graph.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export interface FinalConfigForProject {
4545
preserveLocalDependencyProtocols: NxReleaseVersionConfiguration['preserveLocalDependencyProtocols'];
4646
preserveMatchingDependencyRanges: NxReleaseVersionConfiguration['preserveMatchingDependencyRanges'];
4747
adjustSemverBumpsForZeroMajorVersion: NxReleaseVersionConfiguration['adjustSemverBumpsForZeroMajorVersion'];
48+
applyPreidToDependents: NxReleaseVersionConfiguration['applyPreidToDependents'];
4849
versionActionsOptions: NxReleaseVersionConfiguration['versionActionsOptions'];
4950
manifestRootsToUpdate: Array<
5051
Exclude<
@@ -918,6 +919,17 @@ Valid values are: ${validReleaseVersionPrefixes
918919
releaseGroupVersionConfig?.adjustSemverBumpsForZeroMajorVersion ??
919920
false;
920921

922+
/**
923+
* applyPreidToDependents
924+
*
925+
* Defaults to false to preserve the long-standing behavior where dependents
926+
* of a prerelease-bumped project get a stable patch bump unless opted in.
927+
*/
928+
const applyPreidToDependents =
929+
projectVersionConfig?.applyPreidToDependents ??
930+
releaseGroupVersionConfig?.applyPreidToDependents ??
931+
false;
932+
921933
/**
922934
* fallbackCurrentVersionResolver, defaults to disk when performing a first release, otherwise undefined
923935
*/
@@ -963,6 +975,7 @@ Valid values are: ${validReleaseVersionPrefixes
963975
preserveLocalDependencyProtocols,
964976
preserveMatchingDependencyRanges,
965977
adjustSemverBumpsForZeroMajorVersion,
978+
applyPreidToDependents,
966979
versionActionsOptions,
967980
manifestRootsToUpdate,
968981
dockerOptions,

packages/nx/src/command-line/release/version/release-group-processor.ts

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ export class ReleaseGroupProcessor {
406406
if (!this.bumpedProjects.has(project)) {
407407
await this.bumpVersionForProject(
408408
project,
409-
'patch',
409+
this.applyPreidToBumpType('patch', project),
410410
'OTHER_PROJECT_IN_FIXED_GROUP_WAS_BUMPED_DUE_TO_DEPENDENCY',
411411
{}
412412
);
@@ -894,7 +894,7 @@ export class ReleaseGroupProcessor {
894894
if (!this.bumpedProjects.has(dependent)) {
895895
await this.bumpVersionForProject(
896896
dependent,
897-
'patch',
897+
this.applyPreidToBumpType('patch', dependent),
898898
'DEPENDENCY_WAS_BUMPED',
899899
{}
900900
);
@@ -960,8 +960,44 @@ export class ReleaseGroupProcessor {
960960
releaseGroup: ReleaseGroupWithName,
961961
dependencyBumpType: SemverBumpType
962962
): SemverBumpType {
963-
const sideEffectBump = 'patch';
964-
return sideEffectBump as SemverBumpType;
963+
// Any project in the group can be used to resolve the applyPreidToDependents
964+
// setting, since it is a group/workspace level option.
965+
const anyProject = releaseGroup.projects[0];
966+
return this.applyPreidToBumpType('patch', anyProject);
967+
}
968+
969+
/**
970+
* When a preid is set (e.g. --preid rc) and the project has opted in via
971+
* `applyPreidToDependents`, convert a "patch" side-effect bump into a
972+
* "prepatch" so that semver.inc() actually applies the preid.
973+
* semver.inc('1.0.0', 'patch', 'rc') ignores preid and returns '1.0.1'.
974+
* semver.inc('1.0.0', 'prepatch', 'rc') returns '1.0.1-rc.0' as expected.
975+
*/
976+
private applyPreidToBumpType(
977+
bumpType: SemverBumpType,
978+
projectName: string
979+
): SemverBumpType {
980+
if (
981+
!this.options.preid ||
982+
bumpType === 'none' ||
983+
bumpType.startsWith('pre')
984+
) {
985+
return bumpType;
986+
}
987+
const finalConfig = this.getCachedFinalConfigForProject(projectName);
988+
if (!finalConfig.applyPreidToDependents) {
989+
return bumpType;
990+
}
991+
switch (bumpType) {
992+
case 'major':
993+
return 'premajor';
994+
case 'minor':
995+
return 'preminor';
996+
case 'patch':
997+
return 'prepatch';
998+
default:
999+
return bumpType;
1000+
}
9651001
}
9661002

9671003
private getProjectDependents(project: string): Set<string> {

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

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,140 @@ describe('releaseVersionGenerator (ported tests)', () => {
836836
`);
837837
});
838838

839+
it('should not apply preid to dependent patch bumps by default when --preid is set', async () => {
840+
const { nxReleaseConfig, projectGraph, filters } =
841+
await createNxReleaseConfigAndPopulateWorkspace(
842+
tree,
843+
`
844+
__default__ ({ "projectsRelationship": "independent" }):
845+
846+
847+
-> depends on my-lib
848+
849+
-> depends on my-lib {devDependencies}
850+
`,
851+
{
852+
version: {
853+
specifierSource: 'prompt',
854+
adjustSemverBumpsForZeroMajorVersion: true,
855+
updateDependents: 'auto',
856+
},
857+
},
858+
undefined,
859+
{
860+
projects: ['my-lib'],
861+
}
862+
);
863+
864+
// Default behavior (applyPreidToDependents unset): dependents only
865+
// get a stable patch bump even though the bumped project is a prerelease.
866+
await releaseVersionGeneratorForTest(tree, {
867+
nxReleaseConfig,
868+
filters,
869+
projectGraph,
870+
userGivenSpecifier: 'prepatch' as SemverBumpType,
871+
preid: 'rc',
872+
});
873+
874+
expect(readJson(tree, 'my-lib/package.json')).toMatchInlineSnapshot(`
875+
{
876+
"name": "my-lib",
877+
"version": "0.0.2-rc.0",
878+
}
879+
`);
880+
881+
expect(
882+
readJson(tree, 'project-with-dependency-on-my-pkg/package.json')
883+
).toMatchInlineSnapshot(`
884+
{
885+
"dependencies": {
886+
"my-lib": "0.0.2-rc.0",
887+
},
888+
"name": "project-with-dependency-on-my-pkg",
889+
"version": "0.0.2",
890+
}
891+
`);
892+
expect(
893+
readJson(tree, 'project-with-devDependency-on-my-pkg/package.json')
894+
).toMatchInlineSnapshot(`
895+
{
896+
"devDependencies": {
897+
"my-lib": "0.0.2-rc.0",
898+
},
899+
"name": "project-with-devDependency-on-my-pkg",
900+
"version": "0.0.2",
901+
}
902+
`);
903+
});
904+
905+
it('should apply preid to dependent patch bumps when applyPreidToDependents is enabled and --preid is set', async () => {
906+
const { nxReleaseConfig, projectGraph, filters } =
907+
await createNxReleaseConfigAndPopulateWorkspace(
908+
tree,
909+
`
910+
__default__ ({ "projectsRelationship": "independent" }):
911+
912+
913+
-> depends on my-lib
914+
915+
-> depends on my-lib {devDependencies}
916+
`,
917+
{
918+
version: {
919+
specifierSource: 'prompt',
920+
adjustSemverBumpsForZeroMajorVersion: true,
921+
updateDependents: 'auto',
922+
applyPreidToDependents: true,
923+
},
924+
},
925+
undefined,
926+
{
927+
projects: ['my-lib'],
928+
}
929+
);
930+
931+
// With applyPreidToDependents: true, dependents that are only
932+
// receiving a dependent patch bump are upgraded to prepatch so the
933+
// preid is actually applied.
934+
await releaseVersionGeneratorForTest(tree, {
935+
nxReleaseConfig,
936+
filters,
937+
projectGraph,
938+
userGivenSpecifier: 'prepatch' as SemverBumpType,
939+
preid: 'rc',
940+
});
941+
942+
expect(readJson(tree, 'my-lib/package.json')).toMatchInlineSnapshot(`
943+
{
944+
"name": "my-lib",
945+
"version": "0.0.2-rc.0",
946+
}
947+
`);
948+
949+
expect(
950+
readJson(tree, 'project-with-dependency-on-my-pkg/package.json')
951+
).toMatchInlineSnapshot(`
952+
{
953+
"dependencies": {
954+
"my-lib": "0.0.2-rc.0",
955+
},
956+
"name": "project-with-dependency-on-my-pkg",
957+
"version": "0.0.2-rc.0",
958+
}
959+
`);
960+
expect(
961+
readJson(tree, 'project-with-devDependency-on-my-pkg/package.json')
962+
).toMatchInlineSnapshot(`
963+
{
964+
"devDependencies": {
965+
"my-lib": "0.0.2-rc.0",
966+
},
967+
"name": "project-with-devDependency-on-my-pkg",
968+
"version": "0.0.2-rc.0",
969+
}
970+
`);
971+
});
972+
839973
it('should update dependents with a prepatch when creating a pre-release version', async () => {
840974
const { nxReleaseConfig, projectGraph, filters } =
841975
await createNxReleaseConfigAndPopulateWorkspace(

packages/nx/src/config/nx-json.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,17 @@ export interface NxReleaseVersionConfiguration {
229229
* This is false by default for backward compatibility.
230230
*/
231231
adjustSemverBumpsForZeroMajorVersion?: boolean;
232+
/**
233+
* Whether to apply the --preid value to the implicit patch bumps given to
234+
* dependents (and other projects in a fixed release group) when a dependency
235+
* is versioned. When true, those dependents are bumped as a prepatch using
236+
* the same preid, so a dependency moving to "1.2.3-rc.0" will bump its
237+
* dependents to "1.0.1-rc.0" instead of "1.0.1".
238+
*
239+
* This is false by default for backward compatibility — consuming an RC of a
240+
* dependency does not necessarily mean the consumer itself is an RC.
241+
*/
242+
applyPreidToDependents?: boolean;
232243
}
233244

234245
export interface NxReleaseChangelogConfiguration {

0 commit comments

Comments
 (0)