nx 23.0.0-beta.21 → 23.0.0-beta.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/plugins/package-json.js +1 -1
  2. package/dist/src/adapter/angular-json.d.ts +2 -2
  3. package/dist/src/adapter/angular-json.js +0 -1
  4. package/dist/src/adapter/compat.d.ts +1 -1
  5. package/dist/src/adapter/compat.js +1 -0
  6. package/dist/src/ai/set-up-ai-agents/set-up-ai-agents.js +2 -1
  7. package/dist/src/command-line/configure-ai-agents/configure-ai-agents.js +1 -1
  8. package/dist/src/command-line/examples.js +4 -4
  9. package/dist/src/command-line/graph/graph.js +1 -1
  10. package/dist/src/command-line/init/init-v2.js +1 -1
  11. package/dist/src/command-line/migrate/agentic/prompts/generic-validation.d.ts +5 -0
  12. package/dist/src/command-line/migrate/agentic/prompts/generic-validation.js +1 -0
  13. package/dist/src/command-line/migrate/agentic/prompts/hybrid-prompt-migration.d.ts +5 -0
  14. package/dist/src/command-line/migrate/agentic/prompts/hybrid-prompt-migration.js +1 -0
  15. package/dist/src/command-line/migrate/agentic/prompts/prompt-migration.d.ts +5 -0
  16. package/dist/src/command-line/migrate/agentic/prompts/prompt-migration.js +1 -0
  17. package/dist/src/command-line/migrate/agentic/prompts/shared-rendering.d.ts +1 -0
  18. package/dist/src/command-line/migrate/agentic/prompts/shared-rendering.js +15 -0
  19. package/dist/src/command-line/migrate/agentic/run-step.d.ts +7 -0
  20. package/dist/src/command-line/migrate/agentic/run-step.js +3 -1
  21. package/dist/src/command-line/migrate/agentic/select.js +120 -32
  22. package/dist/src/command-line/migrate/command-object.d.ts +42 -0
  23. package/dist/src/command-line/migrate/command-object.js +38 -8
  24. package/dist/src/command-line/migrate/migrate-config.d.ts +27 -0
  25. package/dist/src/command-line/migrate/migrate-config.js +103 -0
  26. package/dist/src/command-line/migrate/migrate.d.ts +39 -2
  27. package/dist/src/command-line/migrate/migrate.js +203 -41
  28. package/dist/src/command-line/migrate/multi-major.js +5 -2
  29. package/dist/src/command-line/release/changelog/version-plan-filtering.d.ts +3 -1
  30. package/dist/src/command-line/release/changelog/version-plan-filtering.js +7 -3
  31. package/dist/src/command-line/release/changelog.d.ts +7 -0
  32. package/dist/src/command-line/release/changelog.js +22 -9
  33. package/dist/src/command-line/release/release.js +65 -55
  34. package/dist/src/command-line/release/utils/git.d.ts +6 -0
  35. package/dist/src/command-line/release/utils/git.js +33 -0
  36. package/dist/src/command-line/release/version/derive-specifier-from-conventional-commits.js +3 -2
  37. package/dist/src/command-line/release/version.d.ts +3 -0
  38. package/dist/src/command-line/release/version.js +13 -3
  39. package/dist/src/config/misc-interfaces.d.ts +8 -0
  40. package/dist/src/config/nx-json.d.ts +49 -0
  41. package/dist/src/core/graph/main.js +1 -1
  42. package/dist/src/daemon/server/latest-nx.js +3 -1
  43. package/dist/src/devkit-exports.d.ts +11 -11
  44. package/dist/src/devkit-exports.js +7 -4
  45. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  46. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  47. package/dist/src/plugins/js/index.d.ts +2 -2
  48. package/dist/src/plugins/js/index.js +4 -4
  49. package/dist/src/plugins/js/lock-file/lock-file.d.ts +7 -2
  50. package/dist/src/plugins/js/lock-file/lock-file.js +35 -25
  51. package/dist/src/plugins/js/project-graph/affected/lock-file-changes.d.ts +2 -4
  52. package/dist/src/plugins/js/project-graph/affected/lock-file-changes.js +121 -43
  53. package/dist/src/plugins/js/utils/register.d.ts +52 -0
  54. package/dist/src/plugins/js/utils/register.js +195 -0
  55. package/dist/src/plugins/package-json/create-nodes.d.ts +2 -2
  56. package/dist/src/plugins/package-json/create-nodes.js +2 -2
  57. package/dist/src/plugins/project-json/build-nodes/project-json.d.ts +2 -2
  58. package/dist/src/plugins/project-json/build-nodes/project-json.js +1 -1
  59. package/dist/src/project-graph/error-types.d.ts +6 -6
  60. package/dist/src/project-graph/error-types.js +1 -1
  61. package/dist/src/project-graph/file-utils.d.ts +7 -0
  62. package/dist/src/project-graph/file-utils.js +78 -10
  63. package/dist/src/project-graph/plugins/isolation/isolated-plugin.d.ts +2 -2
  64. package/dist/src/project-graph/plugins/isolation/messaging.d.ts +2 -2
  65. package/dist/src/project-graph/plugins/loaded-nx-plugin.d.ts +3 -3
  66. package/dist/src/project-graph/plugins/loaded-nx-plugin.js +6 -4
  67. package/dist/src/project-graph/plugins/public-api.d.ts +31 -12
  68. package/dist/src/project-graph/plugins/transpiler.js +11 -0
  69. package/dist/src/project-graph/plugins/utils.d.ts +3 -3
  70. package/dist/src/project-graph/utils/project-configuration-utils.d.ts +1 -1
  71. package/dist/src/project-graph/utils/project-configuration-utils.js +6 -17
  72. package/dist/src/tasks-runner/init-tasks-runner.d.ts +2 -2
  73. package/dist/src/tasks-runner/init-tasks-runner.js +6 -6
  74. package/dist/src/tasks-runner/task-orchestrator.d.ts +2 -2
  75. package/dist/src/tasks-runner/task-orchestrator.js +6 -6
  76. package/dist/src/utils/package-json.d.ts +3 -3
  77. package/dist/src/utils/package-json.js +9 -11
  78. package/dist/src/utils/package-manager.d.ts +13 -0
  79. package/dist/src/utils/package-manager.js +27 -0
  80. package/migrations.json +18 -9
  81. package/package.json +11 -11
  82. package/schemas/nx-schema.json +41 -0
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.applyNxJsonMigrateDefaults = applyNxJsonMigrateDefaults;
4
+ exports.assertCommitPrefixHasCommits = assertCommitPrefixHasCommits;
5
+ const cli_args_1 = require("./agentic/cli-args");
6
+ const command_object_1 = require("./command-object");
7
+ const MULTI_MAJOR_MODE_ENV = 'NX_MULTI_MAJOR_MODE';
8
+ /**
9
+ * Overlays `nx.json` `migrate` defaults onto the raw `nx migrate` CLI args so a
10
+ * CLI flag always wins, then `nx.json`, then the built-in default. Returns a new
11
+ * args object; the input is not mutated.
12
+ *
13
+ * Phase-aware: generate-only options (`mode`, `multiMajorMode`) are applied only
14
+ * when not running migrations; run-only options (`createCommits`,
15
+ * `commitPrefix`, `agentic`, `validate`) only when running migrations. This
16
+ * mirrors where each option is consumed and avoids tripping the "cannot be
17
+ * combined with --run-migrations" guards in `parseMigrationsOptions`.
18
+ *
19
+ * `mode` is carried as `modeFromConfig` rather than `mode` so it is never
20
+ * mistaken for an explicit `--mode`: `resolveMode` applies it only when the
21
+ * target is Nx itself, leaving `nx migrate <non-nx-pkg>` unaffected.
22
+ */
23
+ function applyNxJsonMigrateDefaults(args, migrateConfig, env = process.env) {
24
+ if (!migrateConfig) {
25
+ return args;
26
+ }
27
+ const merged = { ...args };
28
+ // `--run-migrations` with no value is normalized to '' by yargs, so a defined
29
+ // (even empty-string) value means we're in the run-migrations phase.
30
+ const isRunMigrations = merged.runMigrations !== undefined;
31
+ if (isRunMigrations) {
32
+ if (merged.createCommits === undefined &&
33
+ migrateConfig.createCommits !== undefined) {
34
+ assertType(migrateConfig.createCommits, 'boolean', 'createCommits');
35
+ merged.createCommits = migrateConfig.createCommits;
36
+ }
37
+ // `commitPrefix` carries a yargs default, so the default value is
38
+ // indistinguishable from "not provided" - treat it as not provided so
39
+ // nx.json can override it.
40
+ if ((merged.commitPrefix === undefined ||
41
+ merged.commitPrefix === command_object_1.DEFAULT_MIGRATION_COMMIT_PREFIX) &&
42
+ migrateConfig.commitPrefix !== undefined) {
43
+ assertType(migrateConfig.commitPrefix, 'string', 'commitPrefix');
44
+ merged.commitPrefix = migrateConfig.commitPrefix;
45
+ }
46
+ if (merged.agentic === undefined && migrateConfig.agentic !== undefined) {
47
+ assertValidAgentic(migrateConfig.agentic);
48
+ merged.agentic = (0, cli_args_1.coerceAgenticArg)(migrateConfig.agentic);
49
+ }
50
+ if (merged.validate === undefined && migrateConfig.validate !== undefined) {
51
+ assertType(migrateConfig.validate, 'boolean', 'validate');
52
+ merged.validate = migrateConfig.validate;
53
+ }
54
+ }
55
+ else {
56
+ if (merged.mode === undefined && migrateConfig.mode !== undefined) {
57
+ assertOneOf(migrateConfig.mode, command_object_1.MIGRATE_MODES, 'mode');
58
+ merged.modeFromConfig = migrateConfig.mode;
59
+ }
60
+ // The NX_MULTI_MAJOR_MODE env var is an established per-invocation override,
61
+ // so it takes precedence over nx.json (CLI flag > env > nx.json > default).
62
+ if (merged.multiMajorMode === undefined &&
63
+ !env[MULTI_MAJOR_MODE_ENV] &&
64
+ migrateConfig.multiMajorMode !== undefined) {
65
+ assertOneOf(migrateConfig.multiMajorMode, command_object_1.MULTI_MAJOR_MODES, 'multiMajorMode');
66
+ merged.multiMajorMode = migrateConfig.multiMajorMode;
67
+ }
68
+ }
69
+ return merged;
70
+ }
71
+ function assertOneOf(value, allowed, field) {
72
+ if (!allowed.includes(value)) {
73
+ throw new Error(`Error: Invalid nx.json migrate.${field} "${value}". Allowed: ${allowed.join(', ')}.`);
74
+ }
75
+ }
76
+ function assertType(value, type, field) {
77
+ if (typeof value !== type) {
78
+ throw new Error(`Error: Invalid nx.json migrate.${field} ${JSON.stringify(value)}. Expected a ${type}.`);
79
+ }
80
+ }
81
+ function assertValidAgentic(agentic) {
82
+ if (typeof agentic === 'boolean') {
83
+ return;
84
+ }
85
+ if (typeof agentic !== 'string' ||
86
+ !cli_args_1.AGENT_IDS.includes(agentic)) {
87
+ throw new Error(`Error: Invalid nx.json migrate.agentic "${agentic}". Allowed: ${cli_args_1.AGENT_IDS.join(', ')}, true, false.`);
88
+ }
89
+ }
90
+ /**
91
+ * The single authority for the "a custom commit prefix needs commits enabled"
92
+ * invariant. Run it against the final merged args (after
93
+ * `applyNxJsonMigrateDefaults`): the yargs `.check()` only sees the CLI args,
94
+ * but nx.json may enable commits via `createCommits` or `agentic`. The CLI
95
+ * `.check()` only fast-fails the unrescuable explicit `--no-create-commits`
96
+ * case; everything else is decided here.
97
+ */
98
+ function assertCommitPrefixHasCommits(merged) {
99
+ const { createCommits, commitPrefix, agentic } = merged;
100
+ if ((0, command_object_1.customCommitPrefixHasNoEffect)({ createCommits, commitPrefix, agentic })) {
101
+ throw new Error('Error: A custom migrate commit prefix requires commits to be enabled. Set `migrate.createCommits` to `true` in nx.json or pass `--create-commits`.');
102
+ }
103
+ }
@@ -74,6 +74,7 @@ export declare class Migrator {
74
74
  factory?: string;
75
75
  prompt?: string;
76
76
  requires?: Record<string, string>;
77
+ documentation?: string;
77
78
  }[];
78
79
  }>;
79
80
  private createMigrateJson;
@@ -118,7 +119,9 @@ export declare function resolveCanonicalNxPackage(targetVersion: string): 'nx' |
118
119
  export declare function resolveMode(mode: MigrateMode | undefined, targetPackage: string, targetVersion: string, context?: {
119
120
  hasFrom: boolean;
120
121
  hasExcludeAppliedMigrations: boolean;
121
- }): Promise<MigrateMode>;
122
+ installedMajor?: number | null;
123
+ isV23Plus?: boolean;
124
+ }, configuredMode?: MigrateMode): Promise<MigrateMode>;
122
125
  type GenerateMigrations = {
123
126
  type: 'generateMigrations';
124
127
  targetPackage: string;
@@ -172,6 +175,7 @@ type ExecutableMigration = {
172
175
  implementation?: string;
173
176
  factory?: string;
174
177
  prompt?: string;
178
+ documentation?: string;
175
179
  };
176
180
  export { isPromptOnlyMigration, isHybridMigration };
177
181
  export declare function resolveAgenticRunId(migrations: ExecutableMigration[]): string;
@@ -235,7 +239,10 @@ export declare function runNxOrAngularMigration(root: string, migration: {
235
239
  name: string;
236
240
  description?: string;
237
241
  version: string;
238
- }, isVerbose: boolean, captureGeneratorOutput?: boolean): Promise<{
242
+ }, isVerbose: boolean, captureGeneratorOutput?: boolean, resolvedCollection?: {
243
+ collection: MigrationsJson;
244
+ collectionPath: string;
245
+ }): Promise<{
239
246
  changes: FileChange[];
240
247
  nextSteps: string[];
241
248
  agentContext: string[];
@@ -258,4 +265,34 @@ export declare function getImplementationPath(collection: MigrationsJson, collec
258
265
  path: string;
259
266
  fnSymbol: string;
260
267
  };
268
+ /**
269
+ * Resolves a migration's collection once and derives everything the run loop
270
+ * needs from that single read: the implementation context (`collection` +
271
+ * `collectionPath`, handed to `runNxOrAngularMigration`) and, for agentic runs,
272
+ * the workspace-relative documentation path handed to the agent.
273
+ *
274
+ * Read fresh per migration (not cached across the loop) so a prior migration's
275
+ * reinstall is reflected, exactly as before. Error handling matches each field's
276
+ * role:
277
+ * - Migrations that run an implementation REQUIRE the collection; an unreadable
278
+ * collection throws and aborts that migration (caught by the run loop).
279
+ * - Prompt-only migrations don't run an implementation, so the collection is
280
+ * read only to resolve documentation - a failure there is non-fatal: the
281
+ * prompt still runs and the supplementary doc is skipped with a warning.
282
+ */
283
+ export declare function resolveMigrationForRun(root: string, migration: {
284
+ package: string;
285
+ name: string;
286
+ documentation?: string;
287
+ implementation?: string;
288
+ factory?: string;
289
+ prompt?: string;
290
+ }, resolveDocumentation: boolean): {
291
+ resolvedCollection?: {
292
+ collection: MigrationsJson;
293
+ collectionPath: string;
294
+ };
295
+ documentationPath?: string;
296
+ };
297
+ export declare function resolveDocumentationFileToWorkspacePath(root: string, migrationsDir: string, documentation: string): string | undefined;
261
298
  export declare function nxCliPath(nxWorkspaceRoot?: string): Promise<string>;
@@ -17,6 +17,8 @@ exports.migrate = migrate;
17
17
  exports.runMigration = runMigration;
18
18
  exports.readMigrationCollection = readMigrationCollection;
19
19
  exports.getImplementationPath = getImplementationPath;
20
+ exports.resolveMigrationForRun = resolveMigrationForRun;
21
+ exports.resolveDocumentationFileToWorkspacePath = resolveDocumentationFileToWorkspacePath;
20
22
  exports.nxCliPath = nxCliPath;
21
23
  const tslib_1 = require("tslib");
22
24
  const pc = tslib_1.__importStar(require("picocolors"));
@@ -56,6 +58,7 @@ const catalog_1 = require("../../utils/catalog");
56
58
  const multi_major_1 = require("./multi-major");
57
59
  const prompt_files_1 = require("./prompt-files");
58
60
  const command_object_1 = require("./command-object");
61
+ const migrate_config_1 = require("./migrate-config");
59
62
  const handoff_gitignore_1 = require("./agentic/handoff-gitignore");
60
63
  const migrate_commits_1 = require("./migrate-commits");
61
64
  const migrate_output_1 = require("./migrate-output");
@@ -529,28 +532,76 @@ function resolveCanonicalNxPackage(targetVersion) {
529
532
  async function resolveMode(mode, targetPackage, targetVersion, context = {
530
533
  hasFrom: false,
531
534
  hasExcludeAppliedMigrations: false,
532
- }) {
535
+ }, configuredMode) {
536
+ // The first-party/third-party split relies on migration metadata that only
537
+ // exists in Nx v23+. `first-party` needs a v22+ install migrating into a v23+
538
+ // target; `third-party` catches up deps a prior first-party migration
539
+ // deferred, so it needs the workspace already on v23+.
540
+ const installedMajor = context.installedMajor ?? null;
541
+ const firstPartyAvailable = installedMajor !== null &&
542
+ installedMajor >= 22 &&
543
+ context.isV23Plus === true;
544
+ const thirdPartyAvailable = installedMajor !== null && installedMajor >= 23;
533
545
  if (mode) {
546
+ if ((0, version_utils_1.isNxEquivalentTarget)(targetPackage, targetVersion)) {
547
+ if (mode === 'first-party' && !firstPartyAvailable) {
548
+ if (context.isV23Plus !== true) {
549
+ throw new Error(`Error: '--mode=first-party' requires migrating to Nx v23 or later.`);
550
+ }
551
+ throw new Error(`Error: '--mode=first-party' requires the workspace to be on Nx v22 or later. Migrate to the latest Nx v22 first, then to v23 or later.`);
552
+ }
553
+ // When the installed version is unknown, defer to the downstream
554
+ // "requires nx to be installed" check rather than the v23 gate.
555
+ if (mode === 'third-party' &&
556
+ installedMajor !== null &&
557
+ !thirdPartyAvailable) {
558
+ throw new Error(`Error: '--mode=third-party' requires the workspace to be on Nx v23 or later.`);
559
+ }
560
+ }
534
561
  return mode;
535
562
  }
536
563
  if (!(0, version_utils_1.isNxEquivalentTarget)(targetPackage, targetVersion)) {
537
564
  return 'all';
538
565
  }
539
- if (!process.stdin.isTTY || (0, is_ci_1.isCI)()) {
566
+ // nx.json `migrate.mode` pre-selects the prompt answer (Nx targets only; non-Nx
567
+ // returned 'all' above). Honor it only when the v23+ gate makes that mode
568
+ // available; otherwise warn and fall back to the always-valid 'all'.
569
+ if (configuredMode) {
570
+ const configuredAvailable = configuredMode === 'all' ||
571
+ (configuredMode === 'first-party' && firstPartyAvailable) ||
572
+ (configuredMode === 'third-party' && thirdPartyAvailable);
573
+ if (configuredAvailable) {
574
+ return configuredMode;
575
+ }
576
+ output_1.output.warn({
577
+ title: `The configured nx.json migrate.mode '${configuredMode}' is not available for this migration; falling back to 'all'.`,
578
+ bodyLines: [
579
+ `The 'first-party' and 'third-party' modes require Nx v23 or later.`,
580
+ ],
581
+ });
540
582
  return 'all';
541
583
  }
542
- const choices = [
543
- {
584
+ const choices = [];
585
+ if (firstPartyAvailable) {
586
+ choices.push({
544
587
  name: 'first-party',
545
588
  message: 'First-party only (Nx and its official packages)',
546
- },
547
- ];
548
- if (!context.hasFrom && !context.hasExcludeAppliedMigrations) {
589
+ });
590
+ }
591
+ if (thirdPartyAvailable &&
592
+ !context.hasFrom &&
593
+ !context.hasExcludeAppliedMigrations) {
549
594
  choices.push({
550
595
  name: 'third-party',
551
596
  message: 'Third-party only (deps managed by Nx)',
552
597
  });
553
598
  }
599
+ if (!choices.length) {
600
+ return 'all';
601
+ }
602
+ if (!process.stdin.isTTY || (0, is_ci_1.isCI)()) {
603
+ return 'all';
604
+ }
554
605
  choices.push({
555
606
  name: 'all',
556
607
  message: 'All (first-party and third-party)',
@@ -656,6 +707,13 @@ async function parseMigrationsOptions(options) {
656
707
  });
657
708
  targetVersion = multiMajorResult.chosen;
658
709
  if (mode === 'third-party') {
710
+ // `mode` can resolve to third-party via nx.json, which bypasses the early
711
+ // CLI-only check above; re-assert against the resolved mode.
712
+ assertThirdPartyModeFlagCompatibility({
713
+ mode,
714
+ from: options.from,
715
+ excludeAppliedMigrations: options.excludeAppliedMigrations,
716
+ });
659
717
  assertThirdPartyTargetBounds({
660
718
  targetPackage,
661
719
  targetVersion,
@@ -686,9 +744,11 @@ function assertThirdPartyModeFlagCompatibility(options) {
686
744
  throw new Error(`Error: '--mode=third-party' cannot be combined with '--exclude-applied-migrations'.`);
687
745
  }
688
746
  }
689
- // Defaults target package/version mode-aware (third-party → installed
690
- // canonical, otherwise nx@latest) and enforces the era gate when --mode
691
- // is explicit.
747
+ // Resolves the target package/version up front (third-party → installed
748
+ // canonical; otherwise resolves dist-tags so the v23 mode gate reads a concrete
749
+ // major), then resolves the mode and enforces the era gate when --mode is
750
+ // explicit. Bare invocations on a pre-v22 install throw rather than defaulting
751
+ // to `latest` across the major gap into the v23 era.
692
752
  async function resolveTargetAndMode(args) {
693
753
  const { positional, from, options } = args;
694
754
  let targetPackage;
@@ -698,15 +758,55 @@ async function resolveTargetAndMode(args) {
698
758
  targetPackage = normalizeSlashes(parsed.targetPackage);
699
759
  targetVersion = parsed.targetVersion;
700
760
  }
701
- // Resolve mode before defaulting target so the default can depend on the
702
- // resolved mode (third-party defaults to nx@<installed>; otherwise nx@latest).
703
- // For bare invocation, `targetPackage='nx'` and `targetVersion='latest'` are
704
- // safe sentinels: `isNxEquivalentTarget` treats the literal `'latest'` as
705
- // modern era (semver `lt('latest', '14.0.0-beta.0')` is false).
761
+ const installed = resolveInstalledCanonical();
762
+ const installedMajor = installed && (0, semver_1.valid)(installed.version) ? (0, semver_1.major)(installed.version) : null;
763
+ // `--mode=third-party` anchors the target to the installed version below, so
764
+ // it never needs a target or dist-tag resolved up front.
765
+ const isExplicitThirdParty = options.mode === 'third-party';
766
+ // Bare invocation: restore the requirement to specify a target when on a
767
+ // pre-v23 install. We can't safely default to `latest` across the major gap
768
+ // into the new era, and the first-party/third-party functionality isn't
769
+ // available there anyway.
770
+ if (!positional && !isExplicitThirdParty) {
771
+ if (installedMajor === null || installedMajor < 22) {
772
+ throw new Error(`Provide the package and version to migrate to. E.g., \`nx migrate nx@<version>\`.`);
773
+ }
774
+ targetPackage = 'nx';
775
+ targetVersion = 'latest';
776
+ }
777
+ // Explicit dist-tags arrive already resolved from
778
+ // `parseTargetPackageAndVersion`; only bare invocations and bare package
779
+ // names (`nx migrate nx`) reach here unresolved.
780
+ let isV23Plus = false;
781
+ if (!isExplicitThirdParty &&
782
+ (0, version_utils_1.isNxEquivalentTarget)(targetPackage, targetVersion)) {
783
+ if (targetVersion && (0, semver_1.valid)(targetVersion)) {
784
+ isV23Plus = (0, semver_1.major)(targetVersion) >= 23;
785
+ }
786
+ else {
787
+ // Resolving the dist-tag here (rather than only in the multi-major check
788
+ // as before) just moves the single registry round-trip earlier — the
789
+ // multi-major check short-circuits on the now-concrete version.
790
+ const tag = targetVersion;
791
+ try {
792
+ targetVersion = await (0, version_utils_1.normalizeVersionWithTagCheck)(targetPackage, tag);
793
+ isV23Plus = (0, semver_1.valid)(targetVersion) ? (0, semver_1.major)(targetVersion) >= 23 : true;
794
+ }
795
+ catch {
796
+ // Registry unavailable: assume dist-tags resolve to >= v23 (true once
797
+ // v23 is released) so the gate stays sensible. The retained sentinel
798
+ // still degrades gracefully downstream (multi-major and the cascade).
799
+ targetVersion = tag;
800
+ isV23Plus = true;
801
+ }
802
+ }
803
+ }
706
804
  const mode = await resolveMode(options.mode, targetPackage ?? 'nx', targetVersion ?? 'latest', {
707
805
  hasFrom: Object.keys(from).length > 0,
708
806
  hasExcludeAppliedMigrations: options.excludeAppliedMigrations === true,
709
- });
807
+ installedMajor,
808
+ isV23Plus,
809
+ }, options.modeFromConfig);
710
810
  let installedNxVersion;
711
811
  // For third-party, anchor `targetPackage`/`targetVersion` to the installed
712
812
  // canonical when the positional was either omitted or a bare package name
@@ -715,7 +815,6 @@ async function resolveTargetAndMode(args) {
715
815
  // the literal `'latest'` that `parseTargetPackageAndVersion` emits for bare
716
816
  // package names.
717
817
  if (mode === 'third-party' && (!positional || !(0, semver_1.valid)(targetVersion))) {
718
- const installed = resolveInstalledCanonical();
719
818
  if (!installed) {
720
819
  throw new Error(`Error: '--mode=third-party' requires 'nx' (or '@nrwl/workspace' on Nx <14) to be installed in your workspace. Install dependencies first, then re-run.`);
721
820
  }
@@ -723,15 +822,6 @@ async function resolveTargetAndMode(args) {
723
822
  targetPackage = installed.canonical;
724
823
  targetVersion = installed.version;
725
824
  }
726
- else if (!positional) {
727
- // Bare invocation: default to `nx@latest` as a literal sentinel rather
728
- // than resolving via the registry here. Multi-major resolves the dist-tag
729
- // when needed (and bails gracefully on registry failure), and the cascade
730
- // resolves it for the walk (honouring `NX_MIGRATE_SKIP_REGISTRY_FETCH`).
731
- // This matches the resilience of `nx migrate nx`.
732
- targetPackage = 'nx';
733
- targetVersion = 'latest';
734
- }
735
825
  if (options.mode && !(0, version_utils_1.isNxEquivalentTarget)(targetPackage, targetVersion)) {
736
826
  const isLegacy = (0, version_utils_1.isLegacyEra)(targetVersion);
737
827
  const validTargets = isLegacy
@@ -848,14 +938,14 @@ function createInstalledPackageVersionsResolver(root) {
848
938
  return getInstalledPackageVersion;
849
939
  }
850
940
  // testing-fetch-start
851
- function createFetcher() {
941
+ function createFetcher(pmc) {
852
942
  const migrationsCache = {};
853
943
  const resolvedVersionCache = {};
854
944
  function fetchMigrations(packageName, packageVersion, setCache) {
855
945
  if (process.env.NX_MIGRATE_SKIP_REGISTRY_FETCH === 'true') {
856
946
  // Skip registry fetch and use installation method directly
857
947
  logger_1.logger.info(`Fetching ${packageName}@${packageVersion}`);
858
- return getPackageMigrationsUsingInstall(packageName, packageVersion);
948
+ return getPackageMigrationsUsingInstall(packageName, packageVersion, pmc);
859
949
  }
860
950
  const cacheKey = packageName + '-' + packageVersion;
861
951
  return Promise.resolve(resolvedVersionCache[cacheKey])
@@ -877,7 +967,7 @@ function createFetcher() {
877
967
  .catch((e) => {
878
968
  logger_1.logger.verbose(`Failed to get migrations from registry for ${packageName}@${packageVersion}: ${e.message}. Falling back to install.`);
879
969
  logger_1.logger.info(`Fetching ${packageName}@${packageVersion}`);
880
- return getPackageMigrationsUsingInstall(packageName, packageVersion);
970
+ return getPackageMigrationsUsingInstall(packageName, packageVersion, pmc);
881
971
  });
882
972
  }
883
973
  return function nxMigrateFetcher(packageName, packageVersion) {
@@ -996,18 +1086,17 @@ function createConcurrencyLimiter(concurrency) {
996
1086
  const installConcurrencyLimit = process.env.NX_MIGRATE_INSTALL_CONCURRENCY
997
1087
  ? createConcurrencyLimiter(Math.max(1, Math.floor(Number(process.env.NX_MIGRATE_INSTALL_CONCURRENCY)) || 1))
998
1088
  : null;
999
- async function getPackageMigrationsUsingInstall(packageName, packageVersion) {
1000
- const run = () => getPackageMigrationsUsingInstallImpl(packageName, packageVersion);
1089
+ async function getPackageMigrationsUsingInstall(packageName, packageVersion, pmc) {
1090
+ const run = () => getPackageMigrationsUsingInstallImpl(packageName, packageVersion, pmc);
1001
1091
  return installConcurrencyLimit ? installConcurrencyLimit(run) : run();
1002
1092
  }
1003
- async function getPackageMigrationsUsingInstallImpl(packageName, packageVersion) {
1093
+ async function getPackageMigrationsUsingInstallImpl(packageName, packageVersion, pmc) {
1004
1094
  const { dir, cleanup } = (0, package_manager_1.createTempNpmDirectory)();
1005
1095
  let result;
1006
1096
  if ((0, provenance_1.getNxPackageGroup)().includes(packageName)) {
1007
1097
  await (0, provenance_1.ensurePackageHasProvenance)(packageName, packageVersion);
1008
1098
  }
1009
1099
  try {
1010
- const pmc = (0, package_manager_1.getPackageManagerCommand)((0, package_manager_1.detectPackageManager)(dir), dir);
1011
1100
  await execAsync(`${pmc.add} ${packageName}@${packageVersion}`, {
1012
1101
  cwd: dir,
1013
1102
  env: {
@@ -1031,7 +1120,6 @@ async function getPackageMigrationsUsingInstallImpl(packageName, packageVersion)
1031
1120
  };
1032
1121
  }
1033
1122
  catch (e) {
1034
- const pmc = (0, package_manager_1.getPackageManagerCommand)((0, package_manager_1.detectPackageManager)(dir), dir);
1035
1123
  throw new Error([
1036
1124
  `Failed to fetch migrations for ${packageName}@${packageVersion}`,
1037
1125
  formatCommandFailure(`${pmc.add} ${packageName}@${packageVersion}`, e),
@@ -1218,7 +1306,7 @@ async function generateMigrationsJsonAndUpdatePackageJson(root, opts) {
1218
1306
  }
1219
1307
  logger_1.logger.info(`Fetching meta data about packages.`);
1220
1308
  logger_1.logger.info(`It may take a few minutes.`);
1221
- const fetch = createFetcher();
1309
+ const fetch = createFetcher(pmc);
1222
1310
  let firstPartyPackages;
1223
1311
  if (mode === 'first-party' || mode === 'third-party') {
1224
1312
  // `@nx/workspace` is version-synced with `nx` and declares an
@@ -1572,9 +1660,16 @@ function resolveCreateCommits(args) {
1572
1660
  }
1573
1661
  return { effective: true, agenticHasDiffContext: true };
1574
1662
  }
1663
+ // Commits aren't enabled here. A custom prefix only reaches this path via
1664
+ // nx.json (e.g. `migrate.commitPrefix` + `migrate.agentic` when the agentic
1665
+ // flow resolves to disabled); surface that it has no effect rather than
1666
+ // dropping it silently.
1575
1667
  return {
1576
1668
  effective: createCommits === true,
1577
1669
  agenticHasDiffContext: false,
1670
+ warning: commitPrefixIsCustom && createCommits !== true
1671
+ ? 'A custom migrate commit prefix is configured, but commits are not enabled for this run, so it has no effect. Set `migrate.createCommits` to `true` (or pass `--create-commits`) to create a commit per migration.'
1672
+ : undefined,
1578
1673
  };
1579
1674
  }
1580
1675
  /**
@@ -1716,6 +1811,11 @@ async function executeMigrations(root, migrations, isVerbose, shouldCreateCommit
1716
1811
  // already-dirty shared file like `package.json`) doesn't collapse.
1717
1812
  const baselineWorkingTreeSnapshot = (0, git_utils_1.getUncommittedChangesSnapshot)(root);
1718
1813
  try {
1814
+ // Read this migration's collection once and derive everything from it:
1815
+ // the implementation context (passed to runNxOrAngularMigration) and the
1816
+ // documentation path (passed to the agent). Read fresh per iteration so a
1817
+ // prior migration's reinstall is reflected.
1818
+ const { resolvedCollection, documentationPath } = resolveMigrationForRun(root, m, !!agenticRun);
1719
1819
  let outcome;
1720
1820
  let commit = { kind: 'none' };
1721
1821
  if ((0, migration_shape_1.isPromptOnlyMigration)(m)) {
@@ -1726,6 +1826,7 @@ async function executeMigrations(root, migrations, isVerbose, shouldCreateCommit
1726
1826
  agentic: agenticRun.agentic,
1727
1827
  runDir: agenticRun.runDir,
1728
1828
  installDepsIfChanged,
1829
+ documentationPath,
1729
1830
  });
1730
1831
  commit = await attemptMigrationCommit(m);
1731
1832
  (0, migrate_output_1.logAgenticSuccessOutcome)(stepResult.ambiguous ? 'Marked complete by user' : 'Applied', commit.kind === 'landed' ? commit.sha : null, stepResult.summary);
@@ -1740,7 +1841,7 @@ async function executeMigrations(root, migrations, isVerbose, shouldCreateCommit
1740
1841
  }
1741
1842
  else if ((0, migration_shape_1.isHybridMigration)(m)) {
1742
1843
  const { changes, nextSteps, agentContext, logs, madeChanges } = await runNxOrAngularMigration(root, m, isVerbose,
1743
- /* captureGeneratorOutput: */ !!agenticRun);
1844
+ /* captureGeneratorOutput: */ !!agenticRun, resolvedCollection);
1744
1845
  migrationEmittedNextSteps.push(...nextSteps);
1745
1846
  if (agenticRun) {
1746
1847
  // Install any deps the deterministic phase added/bumped before the
@@ -1753,6 +1854,7 @@ async function executeMigrations(root, migrations, isVerbose, shouldCreateCommit
1753
1854
  agentic: agenticRun.agentic,
1754
1855
  runDir: agenticRun.runDir,
1755
1856
  installDepsIfChanged,
1857
+ documentationPath,
1756
1858
  implContext: {
1757
1859
  logs,
1758
1860
  changes,
@@ -1801,7 +1903,7 @@ async function executeMigrations(root, migrations, isVerbose, shouldCreateCommit
1801
1903
  // changes uncommitted in the working tree for the user to review.
1802
1904
  const validationRun = agenticRun && shouldRunValidation ? agenticRun : undefined;
1803
1905
  const { changes, nextSteps, agentContext, logs, madeChanges } = await runNxOrAngularMigration(root, m, isVerbose,
1804
- /* captureGeneratorOutput: */ !!validationRun);
1906
+ /* captureGeneratorOutput: */ !!validationRun, resolvedCollection);
1805
1907
  migrationEmittedNextSteps.push(...nextSteps);
1806
1908
  const canRunValidation = !!validationRun && changes.length > 0;
1807
1909
  if (canRunValidation) {
@@ -1814,6 +1916,7 @@ async function executeMigrations(root, migrations, isVerbose, shouldCreateCommit
1814
1916
  agentic: validationRun.agentic,
1815
1917
  runDir: validationRun.runDir,
1816
1918
  installDepsIfChanged,
1919
+ documentationPath,
1817
1920
  implContext: {
1818
1921
  logs,
1819
1922
  changes,
@@ -1957,8 +2060,8 @@ function logSkippedPostMigrationInstall(root) {
1957
2060
  bodyLines: [`Run "${installCommand}" to install the updated dependencies.`],
1958
2061
  });
1959
2062
  }
1960
- async function runNxOrAngularMigration(root, migration, isVerbose, captureGeneratorOutput = false) {
1961
- const { collection, collectionPath } = readMigrationCollection(migration.package, root);
2063
+ async function runNxOrAngularMigration(root, migration, isVerbose, captureGeneratorOutput = false, resolvedCollection) {
2064
+ const { collection, collectionPath } = resolvedCollection ?? readMigrationCollection(migration.package, root);
1962
2065
  let changes = [];
1963
2066
  let nextSteps = [];
1964
2067
  let agentContext = [];
@@ -2219,13 +2322,15 @@ function filterStrings(value) {
2219
2322
  async function migrate(root, args, rawArgs) {
2220
2323
  await client_1.daemonClient.stop();
2221
2324
  return (0, handle_errors_1.handleErrors)(process.env.NX_VERBOSE_LOGGING === 'true', async () => {
2222
- const opts = await parseMigrationsOptions(args);
2325
+ const mergedArgs = (0, migrate_config_1.applyNxJsonMigrateDefaults)(args, (0, configuration_1.readNxJson)().migrate);
2326
+ (0, migrate_config_1.assertCommitPrefixHasCommits)(mergedArgs);
2327
+ const opts = await parseMigrationsOptions(mergedArgs);
2223
2328
  if (opts.type === 'generateMigrations') {
2224
2329
  await generateMigrationsJsonAndUpdatePackageJson(root, opts);
2225
2330
  }
2226
2331
  else {
2227
2332
  try {
2228
- return await runMigrations(root, opts, rawArgs, args['verbose'], args['createCommits'], args['commitPrefix'], args['skipInstall']);
2333
+ return await runMigrations(root, opts, rawArgs, mergedArgs['verbose'], mergedArgs['createCommits'], mergedArgs['commitPrefix'] ?? command_object_1.DEFAULT_MIGRATION_COMMIT_PREFIX, mergedArgs['skipInstall']);
2229
2334
  }
2230
2335
  catch (e) {
2231
2336
  // The remediation guidance is already logged by `runInstall`; swallow
@@ -2297,6 +2402,63 @@ function getImplementationPath(collection, collectionPath, name, migrationVersio
2297
2402
  }
2298
2403
  return { path: implPath, fnSymbol };
2299
2404
  }
2405
+ /**
2406
+ * Resolves a migration's collection once and derives everything the run loop
2407
+ * needs from that single read: the implementation context (`collection` +
2408
+ * `collectionPath`, handed to `runNxOrAngularMigration`) and, for agentic runs,
2409
+ * the workspace-relative documentation path handed to the agent.
2410
+ *
2411
+ * Read fresh per migration (not cached across the loop) so a prior migration's
2412
+ * reinstall is reflected, exactly as before. Error handling matches each field's
2413
+ * role:
2414
+ * - Migrations that run an implementation REQUIRE the collection; an unreadable
2415
+ * collection throws and aborts that migration (caught by the run loop).
2416
+ * - Prompt-only migrations don't run an implementation, so the collection is
2417
+ * read only to resolve documentation - a failure there is non-fatal: the
2418
+ * prompt still runs and the supplementary doc is skipped with a warning.
2419
+ */
2420
+ function resolveMigrationForRun(root, migration, resolveDocumentation) {
2421
+ let resolvedCollection;
2422
+ if (!(0, migration_shape_1.isPromptOnlyMigration)(migration)) {
2423
+ resolvedCollection = readMigrationCollection(migration.package, root);
2424
+ }
2425
+ else if (resolveDocumentation && migration.documentation) {
2426
+ try {
2427
+ resolvedCollection = readMigrationCollection(migration.package, root);
2428
+ }
2429
+ catch {
2430
+ // Non-fatal: documentation is supplementary; the warning below fires.
2431
+ }
2432
+ }
2433
+ let documentationPath;
2434
+ if (resolveDocumentation && migration.documentation) {
2435
+ documentationPath = resolvedCollection
2436
+ ? resolveDocumentationFileToWorkspacePath(root, (0, path_1.dirname)(resolvedCollection.collectionPath), migration.documentation)
2437
+ : undefined;
2438
+ if (!documentationPath) {
2439
+ logger_1.logger.warn(`Could not resolve the "documentation" file "${migration.documentation}" declared for migration "${migration.package}: ${migration.name}". It will be skipped as additional context for the AI agent.`);
2440
+ }
2441
+ }
2442
+ return { resolvedCollection, documentationPath };
2443
+ }
2444
+ // Resolves a `documentation` path (relative to the package's migrations dir) to
2445
+ // a workspace-relative path - or the absolute path when it resolves outside the
2446
+ // workspace (unusual hoisted/symlinked layouts). The agent runs with cwd =
2447
+ // workspace root, so the workspace-relative form is preferred. Returns
2448
+ // undefined when the file can't be resolved.
2449
+ function resolveDocumentationFileToWorkspacePath(root, migrationsDir, documentation) {
2450
+ let documentationFile;
2451
+ try {
2452
+ documentationFile = require.resolve(documentation, {
2453
+ paths: [migrationsDir],
2454
+ });
2455
+ }
2456
+ catch {
2457
+ return undefined;
2458
+ }
2459
+ const relativePath = (0, path_1.relative)(root, documentationFile);
2460
+ return relativePath.startsWith('..') ? documentationFile : relativePath;
2461
+ }
2300
2462
  class MigrationImplementationMissingError extends Error {
2301
2463
  constructor(baseMessage, collectionPath, migrationVersion) {
2302
2464
  super(buildMigrationMissingMessage(baseMessage, collectionPath, migrationVersion));
@@ -149,8 +149,11 @@ async function maybePromptOrWarnMultiMajorMigration(args) {
149
149
  resolveLatestStableInMajor(targetPackage, installedMajor + 1),
150
150
  ]);
151
151
  // Only suggest the current-major latest when there's at least a minor
152
- // delta — a same-minor patch bump isn't a meaningful incremental step.
153
- const showCurrent = latestInCurrent &&
152
+ // delta — a same-minor patch bump isn't a meaningful incremental step. Skip
153
+ // major 22 (last pre-v23) entirely: a 22.x step is the only sub-v23 option
154
+ // and conflicts with the mode gate already decided against the v23+ target.
155
+ const showCurrent = installedMajor !== 22 &&
156
+ latestInCurrent &&
154
157
  (0, semver_1.gt)(latestInCurrent, installed) &&
155
158
  (0, semver_1.minor)(latestInCurrent) > (0, semver_1.minor)(installed)
156
159
  ? latestInCurrent
@@ -16,7 +16,7 @@ export declare function filterVersionPlansByCommitRange(versionPlans: RawVersion
16
16
  * Resolves the "from SHA" for changelog purposes.
17
17
  * This determines the starting point for changelog generation and optional version plan filtering.
18
18
  */
19
- export declare function resolveChangelogFromSHA({ fromRef, tagPattern, tagPatternValues, checkAllBranchesWhen, preid, requireSemver, strictPreid, useAutomaticFromRef, resolveRepositoryTags, }: {
19
+ export declare function resolveChangelogFromSHA({ fromRef, tagPattern, tagPatternValues, checkAllBranchesWhen, preid, requireSemver, strictPreid, useAutomaticFromRef, resolveRepositoryTags, projectRoot, }: {
20
20
  fromRef?: string;
21
21
  tagPattern: string;
22
22
  tagPatternValues: Record<string, string>;
@@ -26,6 +26,8 @@ export declare function resolveChangelogFromSHA({ fromRef, tagPattern, tagPatter
26
26
  strictPreid: boolean;
27
27
  useAutomaticFromRef: boolean;
28
28
  resolveRepositoryTags: RepoGitTags['resolveTags'];
29
+ /** When provided, scopes the fallback to the project's first commit instead of the repo's first commit */
30
+ projectRoot?: string;
29
31
  }): Promise<string | null>;
30
32
  /**
31
33
  * Helper function for workspace-level "from SHA" resolution.