Skip to content

Commit 4108bfc

Browse files
authored
feat(release): add nx release plan:check command to ensure relevant version plans exist (#27343)
<!-- Please make sure you have read the submission guidelines before posting an PR --> <!-- https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr --> <!-- Please make sure that your commit message follows our format --> <!-- Example: `fix(nx): must begin with lowercase` --> <!-- If this is a particularly complex change or feature addition, you can request a dedicated Nx release for this pull request branch. Mention someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they will confirm if the PR warrants its own release for testing purposes, and generate it for you if appropriate. --> ## Current Behavior <!-- This is the behavior we have today --> When using version plans as a versioning strategy with `nx release`, there is no way to enforce that version plan files are created when changing files. ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> There is a new `nx release plan:check` subcommand, intended to be run in CI (and/or potentially a git hook) which will return with exit code 1 if touched projects are not represented in at least one version plan file on disk. What constitutes a touched file is shared with our `affected` logic in other commands, with the additionally capability to be able to ignore file patterns from consideration. This would be useful for not requiring version plans when only documentation or spec files change, for example. ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #
1 parent add5a67 commit 4108bfc

18 files changed

+1982
-157
lines changed

e2e/release/src/version-plans-check.test.ts

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

packages/js/src/generators/release-version/release-version.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,8 @@ To fix this you will either need to add a package.json file at that location, or
463463

464464
if (options.releaseGroup.projectsRelationship === 'independent') {
465465
specifier = (
466-
options.releaseGroup.versionPlans as ProjectsVersionPlan[]
466+
options.releaseGroup
467+
.resolvedVersionPlans as ProjectsVersionPlan[]
467468
).reduce((spec: ReleaseType, plan: ProjectsVersionPlan) => {
468469
if (!spec) {
469470
return plan.projectVersionBumps[projectName];
@@ -482,7 +483,7 @@ To fix this you will either need to add a package.json file at that location, or
482483
}, null);
483484
} else {
484485
specifier = (
485-
options.releaseGroup.versionPlans as GroupVersionPlan[]
486+
options.releaseGroup.resolvedVersionPlans as GroupVersionPlan[]
486487
).reduce((spec: ReleaseType, plan: GroupVersionPlan) => {
487488
if (!spec) {
488489
return plan.groupVersionBump;
@@ -522,7 +523,7 @@ To fix this you will either need to add a package.json file at that location, or
522523
}
523524

524525
if (options.deleteVersionPlans) {
525-
options.releaseGroup.versionPlans.forEach((p) => {
526+
(options.releaseGroup.resolvedVersionPlans || []).forEach((p) => {
526527
deleteVersionPlanCallbacks.push(async (dryRun?: boolean) => {
527528
if (!dryRun) {
528529
await remove(p.absolutePath);
@@ -599,14 +600,14 @@ To fix this you will either need to add a package.json file at that location, or
599600

600601
// For version-plans, we don't just need to consider the current batch of projects, but also the ones that are actually being updated as part of the plan file(s)
601602
if (isInCurrentBatch && options.specifierSource === 'version-plans') {
602-
isInCurrentBatch = (options.releaseGroup.versionPlans || []).some(
603-
(plan) => {
604-
if ('projectVersionBumps' in plan) {
605-
return plan.projectVersionBumps[dependentProject.source];
606-
}
607-
return true;
603+
isInCurrentBatch = (
604+
options.releaseGroup.resolvedVersionPlans || []
605+
).some((plan) => {
606+
if ('projectVersionBumps' in plan) {
607+
return plan.projectVersionBumps[dependentProject.source];
608608
}
609-
);
609+
return true;
610+
});
610611
}
611612

612613
if (!isInCurrentBatch) {

packages/nx/schemas/nx-schema.json

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,15 @@
186186
"type": "string"
187187
},
188188
"versionPlans": {
189-
"type": "boolean",
190-
"description": "Enables using version plans as a specifier source for versioning and to determine changes for changelog generation."
189+
"oneOf": [
190+
{
191+
"$ref": "#/definitions/NxReleaseVersionPlansConfiguration"
192+
},
193+
{
194+
"type": "boolean",
195+
"description": "Enables using version plans as a specifier source for versioning and to determine changes for changelog generation."
196+
}
197+
]
191198
}
192199
},
193200
"required": ["projects"]
@@ -239,8 +246,15 @@
239246
"$ref": "#/definitions/NxReleaseVersionConfiguration"
240247
},
241248
"versionPlans": {
242-
"type": "boolean",
243-
"description": "Enables using version plans as a specifier source for versioning and to determine changes for changelog generation."
249+
"oneOf": [
250+
{
251+
"$ref": "#/definitions/NxReleaseVersionPlansConfiguration"
252+
},
253+
{
254+
"type": "boolean",
255+
"description": "Enables using version plans as a specifier source for versioning and to determine changes for changelog generation."
256+
}
257+
]
244258
},
245259
"releaseTagPattern": {
246260
"type": "string"
@@ -698,6 +712,18 @@
698712
}
699713
}
700714
},
715+
"NxReleaseVersionPlansConfiguration": {
716+
"type": "object",
717+
"properties": {
718+
"ignorePatternsForPlanCheck": {
719+
"type": "array",
720+
"items": {
721+
"type": "string"
722+
},
723+
"description": "Changes to files matching any of these optional patterns will be excluded from the affected project logic within the `nx release plan:check` command. This is useful for ignoring files that are not relevant to the versioning process, such as documentation or configuration files."
724+
}
725+
}
726+
},
701727
"ChangelogRenderOptions": {
702728
"type": "object",
703729
"additionalProperties": true

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

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import {
4343
GroupVersionPlan,
4444
ProjectsVersionPlan,
4545
readRawVersionPlans,
46-
setVersionPlansOnGroups,
46+
setResolvedVersionPlansOnGroups,
4747
} from './config/version-plans';
4848
import {
4949
GitCommit,
@@ -179,7 +179,7 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) {
179179
process.exit(1);
180180
}
181181
const rawVersionPlans = await readRawVersionPlans();
182-
setVersionPlansOnGroups(
182+
setResolvedVersionPlansOnGroups(
183183
rawVersionPlans,
184184
releaseGroups,
185185
Object.keys(projectGraph.nodes)
@@ -274,12 +274,13 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) {
274274

275275
// If there are multiple release groups, we'll just skip the workspace changelog anyway.
276276
const versionPlansEnabledForWorkspaceChangelog =
277-
releaseGroups[0].versionPlans;
277+
releaseGroups[0].resolvedVersionPlans;
278278
if (versionPlansEnabledForWorkspaceChangelog) {
279279
if (releaseGroups.length === 1) {
280280
const releaseGroup = releaseGroups[0];
281281
if (releaseGroup.projectsRelationship === 'fixed') {
282-
const versionPlans = releaseGroup.versionPlans as GroupVersionPlan[];
282+
const versionPlans =
283+
releaseGroup.resolvedVersionPlans as GroupVersionPlan[];
283284
workspaceChangelogChanges = versionPlans
284285
.flatMap((vp) => {
285286
const releaseType = versionPlanSemverReleaseTypeToChangelogType(
@@ -490,8 +491,10 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) {
490491
// TODO: remove this after the changelog renderer is refactored to remove coupling with git commits
491492
let commits: GitCommit[];
492493

493-
if (releaseGroup.versionPlans) {
494-
changes = (releaseGroup.versionPlans as ProjectsVersionPlan[])
494+
if (releaseGroup.resolvedVersionPlans) {
495+
changes = (
496+
releaseGroup.resolvedVersionPlans as ProjectsVersionPlan[]
497+
)
495498
.map((vp) => {
496499
const bumpForProject = vp.projectVersionBumps[project.name];
497500
if (!bumpForProject) {
@@ -637,8 +640,8 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) {
637640
let changes: ChangelogChange[] = [];
638641
// TODO: remove this after the changelog renderer is refactored to remove coupling with git commits
639642
let commits: GitCommit[] = [];
640-
if (releaseGroup.versionPlans) {
641-
changes = (releaseGroup.versionPlans as GroupVersionPlan[])
643+
if (releaseGroup.resolvedVersionPlans) {
644+
changes = (releaseGroup.resolvedVersionPlans as GroupVersionPlan[])
642645
.flatMap((vp) => {
643646
const releaseType = versionPlanSemverReleaseTypeToChangelogType(
644647
vp.groupVersionBump
@@ -918,8 +921,8 @@ async function applyChangesAndExit(
918921
if (args.deleteVersionPlans && !args.dryRun) {
919922
const planFiles = new Set<string>();
920923
releaseGroups.forEach((group) => {
921-
if (group.versionPlans) {
922-
group.versionPlans.forEach((plan) => {
924+
if (group.resolvedVersionPlans) {
925+
group.resolvedVersionPlans.forEach((plan) => {
923926
removeSync(plan.absolutePath);
924927
planFiles.add(plan.relativePath);
925928
});

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

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,28 @@
11
import { Argv, CommandModule, showHelp } from 'yargs';
22
import { readNxJson } from '../../config/nx-json';
3+
import { readParallelFromArgsAndEnv } from '../../utils/command-line-utils';
34
import { logger } from '../../utils/logger';
45
import {
56
OutputStyle,
67
RunManyOptions,
78
parseCSV,
9+
withAffectedOptions,
810
withOutputStyleOption,
911
withOverrides,
1012
withRunManyOptions,
1113
} from '../yargs-utils/shared-options';
1214
import { VersionData } from './utils/shared';
13-
import { readParallelFromArgsAndEnv } from '../../utils/command-line-utils';
1415

15-
export interface NxReleaseArgs {
16+
// Implemented by every command and subcommand
17+
export interface BaseNxReleaseArgs {
18+
verbose?: boolean;
19+
printConfig?: boolean | 'debug';
20+
}
21+
22+
export interface NxReleaseArgs extends BaseNxReleaseArgs {
1623
groups?: string[];
1724
projects?: string[];
1825
dryRun?: boolean;
19-
verbose?: boolean;
20-
printConfig?: boolean | 'debug';
2126
}
2227

2328
interface GitCommitAndTagOptions {
@@ -66,6 +71,14 @@ export type PlanOptions = NxReleaseArgs & {
6671
message?: string;
6772
};
6873

74+
export type PlanCheckOptions = BaseNxReleaseArgs & {
75+
base?: string;
76+
head?: string;
77+
files?: string;
78+
uncommitted?: boolean;
79+
untracked?: boolean;
80+
};
81+
6982
export type ReleaseOptions = NxReleaseArgs &
7083
FirstReleaseArgs & {
7184
yes?: boolean;
@@ -94,6 +107,7 @@ export const yargsReleaseCommand: CommandModule<
94107
.command(changelogCommand)
95108
.command(publishCommand)
96109
.command(planCommand)
110+
.command(planCheckCommand)
97111
.demandCommand()
98112
// Error on typos/mistyped CLI args, there is no reason to support arbitrary unknown args for these commands
99113
.strictOptions()
@@ -337,6 +351,7 @@ const publishCommand: CommandModule<NxReleaseArgs, PublishOptions> = {
337351
const planCommand: CommandModule<NxReleaseArgs, PlanOptions> = {
338352
command: 'plan [bump]',
339353
aliases: ['pl'],
354+
// TODO: Remove this when docs are added
340355
// Create a plan to pick a new version and generate a changelog entry.
341356
// Hidden for now until the feature is more stable
342357
describe: false,
@@ -371,6 +386,20 @@ const planCommand: CommandModule<NxReleaseArgs, PlanOptions> = {
371386
},
372387
};
373388

389+
const planCheckCommand: CommandModule<NxReleaseArgs, PlanCheckOptions> = {
390+
command: 'plan:check',
391+
// TODO: Remove this when docs are added
392+
// Create a plan to pick a new version and generate a changelog entry.
393+
// Hidden for now until the feature is more stable
394+
describe: false,
395+
builder: (yargs) => withAffectedOptions(yargs),
396+
handler: async (args) => {
397+
const release = await import('./plan-check');
398+
const result = await release.releasePlanCheckCLIHandler(args);
399+
process.exit(result);
400+
},
401+
};
402+
374403
function coerceParallelOption(args: any) {
375404
return {
376405
...args,

0 commit comments

Comments
 (0)