nx 23.0.0-beta.11 → 23.0.0-beta.12

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.
@@ -68,7 +68,7 @@ function withMigrationOptions(yargs) {
68
68
  default: false,
69
69
  })
70
70
  .option('mode', {
71
- describe: "Restrict which packages to migrate. Only applies when migrating Nx itself. 'first-party' processes only Nx and its plugins (the target package plus its nx.packageGroup); 'third-party' processes only the third-party dependencies referenced by Nx packageJsonUpdates entries, catching up on any updates that may have been skipped previously; 'all' processes everything. Defaults to 'all' (or prompts in an interactive terminal when targeting Nx).",
71
+ describe: "Restrict which packages to migrate. Only applies when migrating Nx itself. 'first-party' processes only Nx and its plugins (the target package plus its nx.packageGroup); 'third-party' processes only the third-party dependencies referenced by Nx packageJsonUpdates entries, catching up on any updates that may have been skipped previously; 'all' processes everything. When targeting Nx in an interactive terminal, prompts for the value if not provided; otherwise defaults to 'all'.",
72
72
  type: 'string',
73
73
  choices: ['first-party', 'third-party', 'all'],
74
74
  })
@@ -2,11 +2,14 @@ import { MigrationsJson, PackageJsonUpdateForPackage as PackageUpdate } from '..
2
2
  import { NxJsonConfiguration } from '../../config/nx-json';
3
3
  import { FileChange } from '../../generators/tree';
4
4
  import { ArrayPackageGroup, PackageJson } from '../../utils/package-json';
5
+ import { type MultiMajorMode } from './multi-major';
5
6
  import { filterDowngradedUpdates } from './update-filters';
6
7
  import { normalizeVersion } from './version-utils';
7
8
  export { normalizeVersion };
8
9
  export interface ResolvedMigrationConfiguration extends MigrationsJson {
9
10
  packageGroup?: ArrayPackageGroup;
11
+ /** Prompt file contents keyed by the `prompt` value as it appears on the migration entry. */
12
+ resolvedPromptFiles?: Record<string, string>;
10
13
  }
11
14
  type CommandFailure = {
12
15
  message?: string;
@@ -14,6 +17,7 @@ type CommandFailure = {
14
17
  stdout?: string | Buffer;
15
18
  };
16
19
  export declare function formatCommandFailure(command: string, error: CommandFailure): string;
20
+ export type MigrateMode = 'first-party' | 'third-party' | 'all';
17
21
  export interface MigratorOptions {
18
22
  packageJson?: PackageJson;
19
23
  nxInstallation?: NxJsonConfiguration['installation'];
@@ -33,7 +37,7 @@ export interface MigratorOptions {
33
37
  * - 'third-party' keeps only packages NOT in `firstPartyPackages`
34
38
  * - 'all' / undefined keeps all packages (no filtering)
35
39
  */
36
- mode?: 'first-party' | 'third-party' | 'all';
40
+ mode?: MigrateMode;
37
41
  /** First-party package names used by `mode` for filtering. */
38
42
  firstPartyPackages?: ReadonlySet<string>;
39
43
  }
@@ -55,6 +59,8 @@ export declare class Migrator {
55
59
  constructor(opts: MigratorOptions);
56
60
  private fetchMigrationConfig;
57
61
  migrate(targetPackage: string, targetVersion: string): Promise<{
62
+ minVersionWithSkippedUpdates: string;
63
+ promptContents?: Record<string, string>;
58
64
  packageUpdates: Record<string, PackageUpdate>;
59
65
  migrations: {
60
66
  package: string;
@@ -63,9 +69,9 @@ export declare class Migrator {
63
69
  description?: string;
64
70
  implementation?: string;
65
71
  factory?: string;
72
+ prompt?: string;
66
73
  requires?: Record<string, string>;
67
74
  }[];
68
- minVersionWithSkippedUpdates: string;
69
75
  }>;
70
76
  private createMigrateJson;
71
77
  private buildPackageJsonUpdates;
@@ -106,10 +112,10 @@ export declare class Migrator {
106
112
  * walking the cascade.
107
113
  */
108
114
  export declare function resolveCanonicalNxPackage(targetVersion: string): 'nx' | '@nrwl/workspace';
109
- export declare function resolveMode(mode: 'first-party' | 'third-party' | 'all' | undefined, targetPackage: string, targetVersion: string, context?: {
115
+ export declare function resolveMode(mode: MigrateMode | undefined, targetPackage: string, targetVersion: string, context?: {
110
116
  hasFrom: boolean;
111
117
  hasExcludeAppliedMigrations: boolean;
112
- }): Promise<'first-party' | 'third-party' | 'all'>;
118
+ }): Promise<MigrateMode>;
113
119
  type GenerateMigrations = {
114
120
  type: 'generateMigrations';
115
121
  targetPackage: string;
@@ -122,7 +128,19 @@ type GenerateMigrations = {
122
128
  };
123
129
  interactive?: boolean;
124
130
  excludeAppliedMigrations?: boolean;
125
- mode: 'first-party' | 'third-party' | 'all';
131
+ mode: MigrateMode;
132
+ /**
133
+ * Set when multi-major redirected `targetVersion` to an incremental step
134
+ * (gradual mode or the interactive prompt picking a smaller jump). Holds
135
+ * the concrete resolved target so Next Steps can suggest re-running toward
136
+ * it.
137
+ */
138
+ originalTargetVersion?: string;
139
+ /**
140
+ * The `--multi-major-mode` value to propagate to a continuation command,
141
+ * or undefined to omit it. See `MultiMajorResult.gradual` for when it's set.
142
+ */
143
+ multiMajorMode?: MultiMajorMode;
126
144
  };
127
145
  type RunMigrations = {
128
146
  type: 'runMigrations';
@@ -48,6 +48,7 @@ const format_changed_files_with_prettier_if_available_1 = require("../../generat
48
48
  const provenance_1 = require("../../utils/provenance");
49
49
  const catalog_1 = require("../../utils/catalog");
50
50
  const multi_major_1 = require("./multi-major");
51
+ const prompt_files_1 = require("./prompt-files");
51
52
  const update_filters_1 = require("./update-filters");
52
53
  Object.defineProperty(exports, "filterDowngradedUpdates", { enumerable: true, get: function () { return update_filters_1.filterDowngradedUpdates; } });
53
54
  const version_utils_1 = require("./version-utils");
@@ -122,14 +123,16 @@ class Migrator {
122
123
  addToPackageJson: false,
123
124
  });
124
125
  this.applyModeFilter();
125
- const migrations = await this.createMigrateJson();
126
+ const { migrations, promptContents } = await this.createMigrateJson();
126
127
  return {
127
128
  packageUpdates: this.packageUpdates,
128
129
  migrations,
130
+ ...(Object.keys(promptContents).length > 0 ? { promptContents } : {}),
129
131
  minVersionWithSkippedUpdates: this.minVersionWithSkippedUpdates,
130
132
  };
131
133
  }
132
134
  async createMigrateJson() {
135
+ const promptContents = {};
133
136
  const migrations = await Promise.all(Object.keys(this.packageUpdates).map(async (packageName) => {
134
137
  if (this.packageUpdates[packageName].ignoreMigrations) {
135
138
  return [];
@@ -138,10 +141,15 @@ class Migrator {
138
141
  if (currentVersion === null)
139
142
  return [];
140
143
  const { version } = this.packageUpdates[packageName];
141
- const { generators } = await this.fetchMigrationConfig(packageName, version);
142
- if (!generators)
144
+ const { generators: migrationEntries, resolvedPromptFiles } = await this.fetchMigrationConfig(packageName, version);
145
+ if (!migrationEntries)
143
146
  return [];
144
- return Object.entries(generators)
147
+ if (resolvedPromptFiles) {
148
+ for (const [promptPath, content] of Object.entries(resolvedPromptFiles)) {
149
+ promptContents[(0, prompt_files_1.promptContentKey)(packageName, promptPath)] = content;
150
+ }
151
+ }
152
+ return Object.entries(migrationEntries)
145
153
  .filter(([, migration]) => migration.version &&
146
154
  this.gt(migration.version, currentVersion) &&
147
155
  this.lte(migration.version, version) &&
@@ -152,7 +160,7 @@ class Migrator {
152
160
  name: migrationName,
153
161
  }));
154
162
  }));
155
- return migrations.flat();
163
+ return { migrations: migrations.flat(), promptContents };
156
164
  }
157
165
  async buildPackageJsonUpdates(targetPackage, target) {
158
166
  const packagesToCheck = await this.populatePackageJsonUpdatesAndGetPackagesToCheck(targetPackage, target);
@@ -250,7 +258,7 @@ class Migrator {
250
258
  if (ignorePackageGroup) {
251
259
  return [];
252
260
  }
253
- const packageGroup = packageName === '@nrwl/workspace' && (0, semver_1.lt)(targetVersion, '14.0.0-beta.0')
261
+ const packageGroup = packageName === '@nrwl/workspace' && (0, version_utils_1.isLegacyEra)(targetVersion)
254
262
  ? LEGACY_NRWL_PACKAGE_GROUP
255
263
  : (migrationConfig.packageGroup ?? []);
256
264
  let packageGroupOrder = [];
@@ -503,9 +511,7 @@ function resolveFirstPartyPackages(targetPackage, packageGroup) {
503
511
  * walking the cascade.
504
512
  */
505
513
  function resolveCanonicalNxPackage(targetVersion) {
506
- return (0, semver_1.valid)(targetVersion) && (0, semver_1.lt)(targetVersion, '14.0.0-beta.0')
507
- ? '@nrwl/workspace'
508
- : 'nx';
514
+ return (0, version_utils_1.isLegacyEra)(targetVersion) ? '@nrwl/workspace' : 'nx';
509
515
  }
510
516
  async function resolveMode(mode, targetPackage, targetVersion, context = {
511
517
  hasFrom: false,
@@ -588,9 +594,9 @@ async function parseTargetPackageAndVersion(args) {
588
594
  // on the registry
589
595
  const targetVersion = await (0, version_utils_1.normalizeVersionWithTagCheck)('nx', args);
590
596
  const isDistTag = version_utils_1.DIST_TAGS.includes(args);
591
- const targetPackage = !isDistTag && (0, semver_1.lt)(targetVersion, '14.0.0-beta.0')
592
- ? '@nrwl/workspace'
593
- : 'nx';
597
+ const targetPackage = isDistTag
598
+ ? 'nx'
599
+ : resolveCanonicalNxPackage(targetVersion);
594
600
  return { targetPackage, targetVersion };
595
601
  }
596
602
  return { targetPackage: args, targetVersion: 'latest' };
@@ -602,6 +608,9 @@ async function parseMigrationsOptions(options) {
602
608
  if (options.mode && options.runMigrations) {
603
609
  throw new Error(`Error: '--mode' cannot be combined with '--run-migrations'.`);
604
610
  }
611
+ if (options.multiMajorMode && options.runMigrations) {
612
+ throw new Error(`Error: '--multi-major-mode' cannot be combined with '--run-migrations'.`);
613
+ }
605
614
  if (options.runMigrations) {
606
615
  return {
607
616
  type: 'runMigrations',
@@ -625,12 +634,13 @@ async function parseMigrationsOptions(options) {
625
634
  // Spec §10: prompt or warn when crossing more than one major boundary.
626
635
  // Each major's metadata may have pruned migrations from much-older versions,
627
636
  // so jumping multiple majors at once can silently skip migrations.
628
- targetVersion = await (0, multi_major_1.maybePromptOrWarnMultiMajorMigration)({
637
+ const multiMajorResult = await (0, multi_major_1.maybePromptOrWarnMultiMajorMigration)({
629
638
  mode,
630
639
  options,
631
640
  targetPackage,
632
641
  targetVersion,
633
642
  });
643
+ targetVersion = multiMajorResult.chosen;
634
644
  if (mode === 'third-party') {
635
645
  assertThirdPartyTargetBounds({
636
646
  targetPackage,
@@ -648,6 +658,8 @@ async function parseMigrationsOptions(options) {
648
658
  interactive: options.interactive,
649
659
  excludeAppliedMigrations: options.excludeAppliedMigrations,
650
660
  mode,
661
+ originalTargetVersion: multiMajorResult.originalTarget,
662
+ multiMajorMode: multiMajorResult.gradual ? 'gradual' : undefined,
651
663
  };
652
664
  }
653
665
  function assertThirdPartyModeFlagCompatibility(options) {
@@ -708,7 +720,7 @@ async function resolveTargetAndMode(args) {
708
720
  targetVersion = 'latest';
709
721
  }
710
722
  if (options.mode && !(0, version_utils_1.isNxEquivalentTarget)(targetPackage, targetVersion)) {
711
- const isLegacy = (0, semver_1.valid)(targetVersion) && (0, semver_1.lt)(targetVersion, '14.0.0-beta.0');
723
+ const isLegacy = (0, version_utils_1.isLegacyEra)(targetVersion);
712
724
  const validTargets = isLegacy
713
725
  ? `'@nrwl/workspace'`
714
726
  : `'nx' or '@nx/workspace'`;
@@ -767,10 +779,10 @@ function assertThirdPartyTargetBounds(args) {
767
779
  function resolveInstalledCanonical() {
768
780
  const installedNx = (0, installed_nx_version_1.getInstalledNxVersion)();
769
781
  if (installedNx) {
770
- if ((0, semver_1.lt)(installedNx, '14.0.0-beta.0')) {
771
- return { canonical: '@nrwl/workspace', version: installedNx };
772
- }
773
- return { canonical: 'nx', version: installedNx };
782
+ return {
783
+ canonical: resolveCanonicalNxPackage(installedNx),
784
+ version: installedNx,
785
+ };
774
786
  }
775
787
  const installedLegacy = (0, installed_nx_version_1.getInstalledLegacyNrwlWorkspaceVersion)();
776
788
  if (installedLegacy) {
@@ -923,11 +935,22 @@ async function downloadPackageMigrationsFromRegistry(packageName, packageVersion
923
935
  let result;
924
936
  try {
925
937
  const { tarballPath } = await (0, package_manager_1.packageRegistryPack)(dir, packageName, packageVersion);
926
- const migrations = await (0, fileutils_1.extractFileFromTarball)((0, path_1.join)(dir, tarballPath), (0, path_2.joinPathFragments)('package', migrationsFilePath), (0, path_1.join)(dir, migrationsFilePath)).then((path) => (0, fileutils_1.readJsonFile)(path));
927
- result = { ...migrations, packageGroup, version: packageVersion };
928
- }
929
- catch {
930
- throw new Error(`Failed to find migrations file "${migrationsFilePath}" in package "${packageName}@${packageVersion}".`);
938
+ const fullTarballPath = (0, path_1.join)(dir, tarballPath);
939
+ let migrations;
940
+ try {
941
+ migrations = await (0, fileutils_1.extractFileFromTarball)(fullTarballPath, (0, path_2.joinPathFragments)('package', migrationsFilePath), (0, path_1.join)(dir, migrationsFilePath)).then((path) => (0, fileutils_1.readJsonFile)(path));
942
+ }
943
+ catch {
944
+ throw new Error(`Failed to find migrations file "${migrationsFilePath}" in package "${packageName}@${packageVersion}".`);
945
+ }
946
+ (0, prompt_files_1.validateMigrationEntries)(packageName, packageVersion, migrations);
947
+ const resolvedPromptFiles = await (0, prompt_files_1.extractPromptFilesFromTarball)(packageName, packageVersion, migrations, migrationsFilePath, fullTarballPath, dir);
948
+ result = {
949
+ ...migrations,
950
+ packageGroup,
951
+ version: packageVersion,
952
+ ...(resolvedPromptFiles ? { resolvedPromptFiles } : {}),
953
+ };
931
954
  }
932
955
  finally {
933
956
  await cleanup();
@@ -981,10 +1004,18 @@ async function getPackageMigrationsUsingInstallImpl(packageName, packageVersion)
981
1004
  });
982
1005
  const { migrations: migrationsFilePath, packageGroup, packageJson, } = readPackageMigrationConfig(packageName, dir);
983
1006
  let migrations = undefined;
1007
+ let resolvedPromptFiles;
984
1008
  if (migrationsFilePath) {
985
1009
  migrations = (0, fileutils_1.readJsonFile)(migrationsFilePath);
986
- }
987
- result = { ...migrations, packageGroup, version: packageJson.version };
1010
+ (0, prompt_files_1.validateMigrationEntries)(packageName, packageVersion, migrations);
1011
+ resolvedPromptFiles = await (0, prompt_files_1.readPromptFilesFromInstall)(packageName, packageVersion, migrations, migrationsFilePath);
1012
+ }
1013
+ result = {
1014
+ ...migrations,
1015
+ packageGroup,
1016
+ version: packageJson.version,
1017
+ ...(resolvedPromptFiles ? { resolvedPromptFiles } : {}),
1018
+ };
988
1019
  }
989
1020
  catch (e) {
990
1021
  const pmc = (0, package_manager_1.getPackageManagerCommand)((0, package_manager_1.detectPackageManager)(dir), dir);
@@ -1191,8 +1222,7 @@ async function generateMigrationsJsonAndUpdatePackageJson(root, opts) {
1191
1222
  // `LEGACY_NRWL_PACKAGE_GROUP` for that case, and the post-build
1192
1223
  // third-party filter must mirror that set or first-party `@nrwl/*`
1193
1224
  // plugins slip past it.
1194
- const packageGroup = sourcePackage === '@nrwl/workspace' &&
1195
- (0, semver_1.lt)(opts.targetVersion, '14.0.0-beta.0')
1225
+ const packageGroup = sourcePackage === '@nrwl/workspace' && (0, version_utils_1.isLegacyEra)(opts.targetVersion)
1196
1226
  ? LEGACY_NRWL_PACKAGE_GROUP
1197
1227
  : rootMetadata.packageGroup;
1198
1228
  firstPartyPackages = resolveFirstPartyPackages(sourcePackage, packageGroup);
@@ -1210,7 +1240,7 @@ async function generateMigrationsJsonAndUpdatePackageJson(root, opts) {
1210
1240
  mode,
1211
1241
  firstPartyPackages,
1212
1242
  });
1213
- const { migrations, packageUpdates, minVersionWithSkippedUpdates } = await migrator.migrate(walkedTargetPackage, opts.targetVersion);
1243
+ const { migrations, packageUpdates, promptContents, minVersionWithSkippedUpdates, } = await migrator.migrate(walkedTargetPackage, opts.targetVersion);
1214
1244
  // The cascade collects packageJsonUpdates entries against the cascade
1215
1245
  // root's installed version, but inner per-package pins are only gated
1216
1246
  // against the in-flight cascade tally — not against each inner package's
@@ -1221,6 +1251,7 @@ async function generateMigrationsJsonAndUpdatePackageJson(root, opts) {
1221
1251
  const writableUpdates = (0, update_filters_1.filterDowngradedUpdates)(packageUpdates, originalPackageJson, installedPackageVersions);
1222
1252
  const wrotePackageJson = await updatePackageJson(root, writableUpdates);
1223
1253
  const wroteNxJsonInstallation = await updateInstallationDetails(root, writableUpdates);
1254
+ const promptMigrationFiles = (0, prompt_files_1.writePromptMigrationFiles)(root, migrations, promptContents ?? {}, packageUpdates[walkedTargetPackage].version);
1224
1255
  if (migrations.length > 0) {
1225
1256
  await createMigrationsFile(root, [
1226
1257
  ...addSplitConfigurationMigrationIfAvailable(from, writableUpdates),
@@ -1258,6 +1289,11 @@ async function generateMigrationsJsonAndUpdatePackageJson(root, opts) {
1258
1289
  migrations.length > 0
1259
1290
  ? `- migrations.json has been generated.`
1260
1291
  : `- There are no migrations to run, so migrations.json has not been created.`,
1292
+ ...(promptMigrationFiles.length > 0
1293
+ ? [
1294
+ `- ${promptMigrationFiles.length} AI migration prompt(s) have been written to ${prompt_files_1.AI_MIGRATIONS_DIR}/.`,
1295
+ ]
1296
+ : []),
1261
1297
  ],
1262
1298
  });
1263
1299
  try {
@@ -1291,9 +1327,21 @@ async function generateMigrationsJsonAndUpdatePackageJson(root, opts) {
1291
1327
  ]
1292
1328
  : [
1293
1329
  `- Make sure package.json changes make sense and then run '${pmc.install}',`,
1330
+ ...(promptMigrationFiles.length > 0
1331
+ ? [
1332
+ `- Review and tweak the AI migration prompts in ${prompt_files_1.AI_MIGRATIONS_DIR}/ as needed.`,
1333
+ ]
1334
+ : []),
1294
1335
  ...(migrations.length > 0
1295
1336
  ? [`- Run '${pmc.exec} nx migrate --run-migrations'`]
1296
1337
  : []),
1338
+ ...(opts.originalTargetVersion
1339
+ ? [
1340
+ `- After applying these migrations, run '${pmc.exec} nx migrate ${opts.targetPackage}@${opts.originalTargetVersion} --mode=${opts.mode}${opts.multiMajorMode === 'gradual'
1341
+ ? ` ${multi_major_1.MULTI_MAJOR_MODE_FLAG}=gradual`
1342
+ : ''}' to continue toward your original target.`,
1343
+ ]
1344
+ : []),
1297
1345
  ...(opts.interactive && minVersionWithSkippedUpdates
1298
1346
  ? [
1299
1347
  `- You opted out of some migrations for now. Write the following command down somewhere to apply these migrations later:`,
@@ -1,9 +1,30 @@
1
+ import type { MigrateMode } from './migrate';
2
+ export declare const MULTI_MAJOR_MODE_FLAG = "--multi-major-mode";
1
3
  export type MultiMajorMode = 'direct' | 'gradual';
4
+ /**
5
+ * Result of running the multi-major check.
6
+ *
7
+ * - `chosen`: the version to actually migrate to (always concrete semver when
8
+ * the function ran past the dist-tag resolution; otherwise the input value).
9
+ * - `originalTarget`: set only when an actual redirect happened (gradual mode
10
+ * auto-picked a smaller step, OR the interactive prompt returned a version
11
+ * different from the resolved target). Holds the concrete resolved target
12
+ * so callers can suggest re-running toward it.
13
+ * - `gradual`: set only when the redirect came from gradual mode (flag or
14
+ * env). Tells callers it's safe to propagate `--multi-major-mode=gradual`
15
+ * to a continuation command; left unset when the redirect came from the
16
+ * interactive prompt so the user isn't silently locked into gradual.
17
+ */
18
+ export type MultiMajorResult = {
19
+ chosen: string;
20
+ originalTarget?: string;
21
+ gradual?: boolean;
22
+ };
2
23
  export declare function maybePromptOrWarnMultiMajorMigration(args: {
3
- mode: 'first-party' | 'third-party' | 'all';
24
+ mode: MigrateMode;
4
25
  options: {
5
26
  multiMajorMode?: MultiMajorMode;
6
27
  };
7
28
  targetPackage: string;
8
29
  targetVersion: string;
9
- }): Promise<string>;
30
+ }): Promise<MultiMajorResult>;
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MULTI_MAJOR_MODE_FLAG = void 0;
3
4
  exports.maybePromptOrWarnMultiMajorMigration = maybePromptOrWarnMultiMajorMigration;
4
5
  const enquirer_1 = require("enquirer");
5
6
  const semver_1 = require("semver");
@@ -8,7 +9,8 @@ const installed_nx_version_1 = require("../../utils/installed-nx-version");
8
9
  const output_1 = require("../../utils/output");
9
10
  const package_manager_1 = require("../../utils/package-manager");
10
11
  const version_utils_1 = require("./version-utils");
11
- const STEPWISE_DOC_URL = 'https://nx.dev/docs/guides/tips-n-tricks/advanced-update#one-major-version-at-a-time-small-steps';
12
+ const INCREMENTAL_UPDATE_GUIDE_URL = 'https://nx.dev/docs/guides/tips-n-tricks/advanced-update#one-major-version-at-a-time-small-steps';
13
+ exports.MULTI_MAJOR_MODE_FLAG = '--multi-major-mode';
12
14
  const MULTI_MAJOR_MODE_ENV = 'NX_MULTI_MAJOR_MODE';
13
15
  // Caret-major (`^X.0.0`) excludes prereleases per semver, so
14
16
  // `resolvePackageVersionUsingRegistry` returns the highest stable in major X.
@@ -24,23 +26,30 @@ async function resolveLatestStableInMajor(packageName, majorVersion) {
24
26
  const multiMajorHeader = (pkg, installed, target) => `Migrating across multiple major versions: ${pkg}@${installed} → ${pkg}@${target}.`;
25
27
  const multiMajorBodyLines = [
26
28
  `The recommended process is to update one major version at a time, in small steps.`,
27
- `See ${STEPWISE_DOC_URL}`,
29
+ `See ${INCREMENTAL_UPDATE_GUIDE_URL}`,
28
30
  ];
29
31
  function warnMultiMajorMigration(targetPackage, installed, target) {
30
32
  output_1.output.warn({
31
33
  title: multiMajorHeader(targetPackage, installed, target),
32
34
  bodyLines: [
33
35
  ...multiMajorBodyLines,
34
- `Pass --multi-major-mode=direct (or =gradual) or set ${MULTI_MAJOR_MODE_ENV} to silence this warning.`,
36
+ `Pass ${exports.MULTI_MAJOR_MODE_FLAG}=direct (or =gradual) or set ${MULTI_MAJOR_MODE_ENV} to silence this warning.`,
35
37
  ],
36
38
  });
37
39
  }
38
40
  function logGradualStep(targetPackage, step, target) {
41
+ // Status-only announcement. The follow-up instruction ("re-run `nx migrate`
42
+ // to continue toward the original target") lives in the Next Steps block at
43
+ // the end of the run, where it's adjacent to the other re-run guidance and
44
+ // not scrolled out of view by the migration output.
39
45
  output_1.output.log({
40
46
  title: `Migrating to ${targetPackage}@${step} (one step toward ${targetPackage}@${target}).`,
41
- bodyLines: [
42
- `Re-run \`nx migrate\` after this completes to continue toward ${targetPackage}@${target}.`,
43
- ],
47
+ });
48
+ }
49
+ function warnGradualUnavailable(targetPackage, target, reason) {
50
+ output_1.output.warn({
51
+ title: `Could not look up incremental migration options for ${exports.MULTI_MAJOR_MODE_FLAG}=gradual. Proceeding directly to ${targetPackage}@${target}.`,
52
+ bodyLines: [reason],
44
53
  });
45
54
  }
46
55
  // Returns the chosen target version. Caller replaces `targetVersion` with it.
@@ -92,12 +101,13 @@ async function maybePromptOrWarnMultiMajorMigration(args) {
92
101
  const { mode, options, targetPackage } = args;
93
102
  let { targetVersion } = args;
94
103
  if (mode === 'third-party')
95
- return targetVersion;
104
+ return { chosen: targetVersion };
96
105
  const multiMajorMode = resolveMultiMajorMode(options);
97
106
  if (multiMajorMode === 'direct')
98
- return targetVersion;
99
- if (!(0, version_utils_1.isNxEquivalentTarget)(targetPackage, targetVersion))
100
- return targetVersion;
107
+ return { chosen: targetVersion };
108
+ if (!(0, version_utils_1.isNxEquivalentTarget)(targetPackage, targetVersion)) {
109
+ return { chosen: targetVersion };
110
+ }
101
111
  // Bare-package-name positionals (e.g. `nx migrate nx`, `nx migrate
102
112
  // @nx/workspace`) leave `targetVersion` as the literal `'latest'` because
103
113
  // `parseTargetPackageAndVersion` only resolves dist-tags via the registry
@@ -108,35 +118,38 @@ async function maybePromptOrWarnMultiMajorMigration(args) {
108
118
  targetVersion = await (0, version_utils_1.normalizeVersionWithTagCheck)(targetPackage, targetVersion);
109
119
  }
110
120
  catch {
111
- return targetVersion;
121
+ if (multiMajorMode === 'gradual') {
122
+ warnGradualUnavailable(targetPackage, targetVersion, `Failed to resolve the '${targetVersion}' dist-tag against the registry.`);
123
+ }
124
+ return { chosen: targetVersion };
112
125
  }
113
126
  }
114
- if (!(0, semver_1.valid)(targetVersion) || (0, semver_1.lt)(targetVersion, '14.0.0-beta.0')) {
115
- return targetVersion;
127
+ if (!(0, semver_1.valid)(targetVersion) || (0, version_utils_1.isLegacyEra)(targetVersion)) {
128
+ return { chosen: targetVersion };
116
129
  }
117
130
  const installed = (0, installed_nx_version_1.getInstalledNxVersion)();
118
131
  if (!installed || !(0, semver_1.valid)(installed))
119
- return targetVersion;
132
+ return { chosen: targetVersion };
120
133
  // Legacy-era installs are out of scope for the multi-major check.
121
- if ((0, semver_1.lt)(installed, '14.0.0-beta.0'))
122
- return targetVersion;
134
+ if ((0, version_utils_1.isLegacyEra)(installed))
135
+ return { chosen: targetVersion };
123
136
  const installedMajor = (0, semver_1.major)(installed);
124
- if ((0, semver_1.major)(targetVersion) - installedMajor < 2)
125
- return targetVersion;
137
+ if ((0, semver_1.major)(targetVersion) - installedMajor < 2) {
138
+ return { chosen: targetVersion };
139
+ }
126
140
  const interactive = !!process.stdin.isTTY && !(0, is_ci_1.isCI)();
127
141
  // Non-TTY without gradual opt-in stays on the warn-only path; avoid the
128
- // registry round-trip used to resolve stepwise options.
142
+ // registry round-trip used to look up incremental migration options.
129
143
  if (!interactive && multiMajorMode !== 'gradual') {
130
144
  warnMultiMajorMigration(targetPackage, installed, targetVersion);
131
- return targetVersion;
145
+ return { chosen: targetVersion };
132
146
  }
133
147
  const [latestInCurrent, latestInNext] = await Promise.all([
134
148
  resolveLatestStableInMajor(targetPackage, installedMajor),
135
149
  resolveLatestStableInMajor(targetPackage, installedMajor + 1),
136
150
  ]);
137
151
  // Only suggest the current-major latest when there's at least a minor
138
- // delta — a same-minor patch bump isn't a meaningful "step" in stepwise
139
- // migration framing.
152
+ // delta — a same-minor patch bump isn't a meaningful incremental step.
140
153
  const showCurrent = latestInCurrent &&
141
154
  (0, semver_1.gt)(latestInCurrent, installed) &&
142
155
  (0, semver_1.minor)(latestInCurrent) > (0, semver_1.minor)(installed)
@@ -146,21 +159,27 @@ async function maybePromptOrWarnMultiMajorMigration(args) {
146
159
  const step = showCurrent ?? latestInNext;
147
160
  if (step) {
148
161
  logGradualStep(targetPackage, step, targetVersion);
149
- return step;
162
+ return { chosen: step, originalTarget: targetVersion, gradual: true };
150
163
  }
151
- // No stepwise option resolved the requested target is effectively the
152
- // stepwise pick. Honour `gradual` silently.
153
- return targetVersion;
164
+ // Registry returned no eligible incremental version (or the lookup
165
+ // failed); without a step to land on, gradual silently degrades to direct.
166
+ // Surface that explicitly so the safety rail the user opted into isn't
167
+ // invisibly disabled.
168
+ warnGradualUnavailable(targetPackage, targetVersion, `Could not find an eligible version in major ${installedMajor} or ${installedMajor + 1} (registry lookup returned no result or failed).`);
169
+ return { chosen: targetVersion };
154
170
  }
155
171
  if (interactive && (showCurrent || latestInNext)) {
156
- return promptMultiMajorMigration({
172
+ const chosen = await promptMultiMajorMigration({
157
173
  targetPackage,
158
174
  installed,
159
175
  target: targetVersion,
160
176
  latestInCurrent: showCurrent,
161
177
  latestInNext,
162
178
  });
179
+ return chosen !== targetVersion
180
+ ? { chosen, originalTarget: targetVersion }
181
+ : { chosen };
163
182
  }
164
183
  warnMultiMajorMigration(targetPackage, installed, targetVersion);
165
- return targetVersion;
184
+ return { chosen: targetVersion };
166
185
  }
@@ -0,0 +1,12 @@
1
+ import { MigrationsJson } from '../../config/misc-interfaces';
2
+ export declare const AI_MIGRATIONS_DIR: string;
3
+ export declare function promptContentKey(packageName: string, promptRelPath: string): string;
4
+ export declare function validateMigrationEntries(packageName: string, packageVersion: string, migrations: MigrationsJson): void;
5
+ export declare function extractPromptFilesFromTarball(packageName: string, packageVersion: string, migrations: MigrationsJson, migrationsFilePath: string, fullTarballPath: string, destDir: string): Promise<Record<string, string> | undefined>;
6
+ export declare function readPromptFilesFromInstall(packageName: string, packageVersion: string, migrations: MigrationsJson, migrationsFilePath: string): Promise<Record<string, string> | undefined>;
7
+ export declare function writePromptMigrationFiles(root: string, migrations: {
8
+ package: string;
9
+ name: string;
10
+ version: string;
11
+ prompt?: string;
12
+ }[], promptContents: Record<string, string>, targetVersion: string): string[];
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AI_MIGRATIONS_DIR = void 0;
4
+ exports.promptContentKey = promptContentKey;
5
+ exports.validateMigrationEntries = validateMigrationEntries;
6
+ exports.extractPromptFilesFromTarball = extractPromptFilesFromTarball;
7
+ exports.readPromptFilesFromInstall = readPromptFilesFromInstall;
8
+ exports.writePromptMigrationFiles = writePromptMigrationFiles;
9
+ const fs_1 = require("fs");
10
+ const path_1 = require("path");
11
+ const fileutils_1 = require("../../utils/fileutils");
12
+ const path_2 = require("../../utils/path");
13
+ exports.AI_MIGRATIONS_DIR = (0, path_2.joinPathFragments)('tools', 'ai-migrations');
14
+ function promptContentKey(packageName, promptRelPath) {
15
+ return `${packageName}::${promptRelPath}`;
16
+ }
17
+ function* iterateMigrationEntries(migrations) {
18
+ const merged = { ...migrations.schematics, ...migrations.generators };
19
+ yield* Object.entries(merged);
20
+ }
21
+ function validateMigrationEntries(packageName, packageVersion, migrations) {
22
+ for (const [migrationName, entry] of iterateMigrationEntries(migrations)) {
23
+ if (!entry.implementation && !entry.factory && !entry.prompt) {
24
+ throw new Error(`Invalid migration "${migrationName}" in package "${packageName}@${packageVersion}": migration entries must have at least one of "implementation", "factory", or "prompt".`);
25
+ }
26
+ }
27
+ }
28
+ async function resolvePromptFiles(packageName, packageVersion, migrations, migrationsDir, resolveContent) {
29
+ const resolvedPromptFiles = {};
30
+ for (const [migrationName, entry] of iterateMigrationEntries(migrations)) {
31
+ if (!entry.prompt || resolvedPromptFiles[entry.prompt] !== undefined) {
32
+ continue;
33
+ }
34
+ assertPromptPathWithinMigrationsDir(migrationsDir, entry.prompt, packageName, packageVersion);
35
+ try {
36
+ resolvedPromptFiles[entry.prompt] = await resolveContent(entry.prompt);
37
+ }
38
+ catch (e) {
39
+ throw new Error(`Could not find prompt file "${entry.prompt}" for migration "${migrationName}" in package "${packageName}@${packageVersion}".`, { cause: e });
40
+ }
41
+ }
42
+ return Object.keys(resolvedPromptFiles).length > 0
43
+ ? resolvedPromptFiles
44
+ : undefined;
45
+ }
46
+ function assertPromptPathWithinMigrationsDir(migrationsDir, promptRelPath, packageName, packageVersion) {
47
+ const rel = (0, path_1.relative)(migrationsDir, (0, path_1.join)(migrationsDir, promptRelPath));
48
+ if ((0, path_1.isAbsolute)(promptRelPath) ||
49
+ rel === '..' ||
50
+ rel.startsWith(`..${path_1.sep}`) ||
51
+ rel.startsWith(`..${path_1.posix.sep}`)) {
52
+ throw new Error(`Invalid prompt path "${promptRelPath}" in package "${packageName}@${packageVersion}": prompt paths must be relative and resolve within the package's migrations directory.`);
53
+ }
54
+ }
55
+ function extractPromptFilesFromTarball(packageName, packageVersion, migrations, migrationsFilePath, fullTarballPath, destDir) {
56
+ const migrationsDir = (0, path_1.dirname)(migrationsFilePath);
57
+ return resolvePromptFiles(packageName, packageVersion, migrations, migrationsDir, async (promptRelPath) => {
58
+ const promptInTarball = (0, path_2.joinPathFragments)('package', migrationsDir, promptRelPath);
59
+ const promptDest = (0, path_1.join)(destDir, migrationsDir, promptRelPath);
60
+ await (0, fileutils_1.extractFileFromTarball)(fullTarballPath, promptInTarball, promptDest);
61
+ return (0, fs_1.readFileSync)(promptDest, 'utf-8');
62
+ });
63
+ }
64
+ function readPromptFilesFromInstall(packageName, packageVersion, migrations, migrationsFilePath) {
65
+ const migrationsDir = (0, path_1.dirname)(migrationsFilePath);
66
+ return resolvePromptFiles(packageName, packageVersion, migrations, migrationsDir, (promptRelPath) => (0, fs_1.readFileSync)((0, path_1.join)(migrationsDir, promptRelPath), 'utf-8'));
67
+ }
68
+ function writePromptMigrationFiles(root, migrations, promptContents, targetVersion) {
69
+ const sourceToChosen = new Map();
70
+ const result = [];
71
+ for (const migration of migrations) {
72
+ if (!migration.prompt)
73
+ continue;
74
+ const sourceKey = promptContentKey(migration.package, migration.prompt);
75
+ const content = promptContents[sourceKey];
76
+ if (content === undefined)
77
+ continue;
78
+ const cached = sourceToChosen.get(sourceKey);
79
+ if (cached !== undefined) {
80
+ migration.prompt = cached;
81
+ continue;
82
+ }
83
+ const baseName = path_1.posix.basename(migration.prompt);
84
+ const ext = path_1.posix.extname(baseName);
85
+ const stem = ext ? baseName.slice(0, -ext.length) : baseName;
86
+ const destDir = (0, path_2.joinPathFragments)(exports.AI_MIGRATIONS_DIR, migration.package, targetVersion);
87
+ let chosenPath;
88
+ for (let n = 0;; n++) {
89
+ const candidate = (0, path_2.joinPathFragments)(destDir, n === 0 ? baseName : `${stem}-${n}${ext}`);
90
+ const absCandidate = (0, path_1.join)(root, candidate);
91
+ if (!(0, fs_1.existsSync)(absCandidate)) {
92
+ (0, fs_1.mkdirSync)((0, path_1.dirname)(absCandidate), { recursive: true });
93
+ (0, fs_1.writeFileSync)(absCandidate, content);
94
+ result.push(candidate);
95
+ chosenPath = candidate;
96
+ break;
97
+ }
98
+ if ((0, fs_1.readFileSync)(absCandidate, 'utf-8') === content) {
99
+ chosenPath = candidate;
100
+ break;
101
+ }
102
+ }
103
+ sourceToChosen.set(sourceKey, chosenPath);
104
+ migration.prompt = chosenPath;
105
+ }
106
+ return result;
107
+ }