nx 23.0.0-beta.22 → 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 (43) 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/ai/set-up-ai-agents/set-up-ai-agents.js +2 -1
  5. package/dist/src/command-line/configure-ai-agents/configure-ai-agents.js +1 -1
  6. package/dist/src/command-line/graph/graph.js +1 -1
  7. package/dist/src/command-line/init/init-v2.js +1 -1
  8. package/dist/src/command-line/migrate/command-object.js +1 -1
  9. package/dist/src/command-line/migrate/migrate.d.ts +2 -0
  10. package/dist/src/command-line/migrate/migrate.js +110 -37
  11. package/dist/src/command-line/migrate/multi-major.js +5 -2
  12. package/dist/src/core/graph/main.js +1 -1
  13. package/dist/src/daemon/server/latest-nx.js +3 -1
  14. package/dist/src/devkit-exports.d.ts +11 -11
  15. package/dist/src/devkit-exports.js +7 -4
  16. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  17. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  18. package/dist/src/plugins/js/index.d.ts +2 -2
  19. package/dist/src/plugins/js/index.js +4 -4
  20. package/dist/src/plugins/js/lock-file/lock-file.d.ts +2 -2
  21. package/dist/src/plugins/js/lock-file/lock-file.js +1 -1
  22. package/dist/src/plugins/js/utils/register.d.ts +52 -0
  23. package/dist/src/plugins/js/utils/register.js +195 -0
  24. package/dist/src/plugins/package-json/create-nodes.d.ts +2 -2
  25. package/dist/src/plugins/package-json/create-nodes.js +2 -2
  26. package/dist/src/plugins/project-json/build-nodes/project-json.d.ts +2 -2
  27. package/dist/src/plugins/project-json/build-nodes/project-json.js +1 -1
  28. package/dist/src/project-graph/error-types.d.ts +6 -6
  29. package/dist/src/project-graph/error-types.js +1 -1
  30. package/dist/src/project-graph/plugins/isolation/isolated-plugin.d.ts +2 -2
  31. package/dist/src/project-graph/plugins/isolation/messaging.d.ts +2 -2
  32. package/dist/src/project-graph/plugins/loaded-nx-plugin.d.ts +3 -3
  33. package/dist/src/project-graph/plugins/loaded-nx-plugin.js +6 -4
  34. package/dist/src/project-graph/plugins/public-api.d.ts +31 -12
  35. package/dist/src/project-graph/plugins/transpiler.js +11 -0
  36. package/dist/src/project-graph/plugins/utils.d.ts +3 -3
  37. package/dist/src/project-graph/utils/project-configuration-utils.d.ts +1 -1
  38. package/dist/src/project-graph/utils/project-configuration-utils.js +6 -17
  39. package/dist/src/utils/package-json.d.ts +3 -3
  40. package/dist/src/utils/package-json.js +9 -11
  41. package/dist/src/utils/package-manager.d.ts +13 -0
  42. package/dist/src/utils/package-manager.js +27 -0
  43. package/package.json +11 -11
@@ -22,7 +22,7 @@ function writeCache() {
22
22
  }
23
23
  const plugin = {
24
24
  name: 'nx-all-package-jsons-plugin',
25
- createNodesV2: [
25
+ createNodes: [
26
26
  '*/**/package.json',
27
27
  (configFiles, options, context) => {
28
28
  const cache = readPackageJsonConfigurationCache();
@@ -1,7 +1,7 @@
1
1
  import { ProjectsConfigurations } from '../config/workspace-json-project-json';
2
- import { NxPluginV2 } from '../project-graph/plugins';
2
+ import { NxPlugin } from '../project-graph/plugins';
3
3
  export declare const NX_ANGULAR_JSON_PLUGIN_NAME = "nx-angular-json-plugin";
4
- export declare const NxAngularJsonPlugin: NxPluginV2;
4
+ export declare const NxAngularJsonPlugin: NxPlugin;
5
5
  export default NxAngularJsonPlugin;
6
6
  export declare function shouldMergeAngularProjects(root: string, includeProjectsFromAngularJson: boolean): boolean;
7
7
  export declare function isAngularPluginInstalled(): boolean;
@@ -25,7 +25,6 @@ const createNodes = [
25
25
  exports.NxAngularJsonPlugin = {
26
26
  name: exports.NX_ANGULAR_JSON_PLUGIN_NAME,
27
27
  createNodes,
28
- createNodesV2: createNodes,
29
28
  };
30
29
  exports.default = exports.NxAngularJsonPlugin;
31
30
  function shouldMergeAngularProjects(root, includeProjectsFromAngularJson) {
@@ -12,6 +12,7 @@ const generate_files_1 = require("../../generators/utils/generate-files");
12
12
  const json_1 = require("../../generators/utils/json");
13
13
  const native_1 = require("../../native");
14
14
  const package_json_1 = require("../../utils/package-json");
15
+ const package_manager_1 = require("../../utils/package-manager");
15
16
  const ignore_1 = require("../../utils/ignore");
16
17
  const provenance_1 = require("../../utils/provenance");
17
18
  const workspace_root_1 = require("../../utils/workspace-root");
@@ -48,7 +49,7 @@ async function setupAiAgentsGenerator(tree, options, inner = false) {
48
49
  }
49
50
  try {
50
51
  await (0, provenance_1.ensurePackageHasProvenance)('nx', normalizedOptions.packageVersion);
51
- const { tempDir, cleanup } = (0, package_json_1.installPackageToTmp)('nx', normalizedOptions.packageVersion);
52
+ const { tempDir, cleanup } = (0, package_json_1.installPackageToTmp)('nx', normalizedOptions.packageVersion, (0, package_manager_1.detectPackageManager)(tree.root));
52
53
  let modulePath = (0, path_1.join)(tempDir, 'node_modules', 'nx', 'src/ai/set-up-ai-agents/set-up-ai-agents.js');
53
54
  const module = await (0, handle_import_1.handleImport)(modulePath);
54
55
  const setupAiAgentsGeneratorResult = await module.setupAiAgentsGenerator(tree, normalizedOptions, true);
@@ -44,7 +44,7 @@ async function configureAiAgentsHandler(args, inner = false) {
44
44
  let cleanup;
45
45
  try {
46
46
  await (0, provenance_1.ensurePackageHasProvenance)('nx', 'latest');
47
- const packageInstallResults = (0, devkit_internals_1.installPackageToTmp)('nx', 'latest');
47
+ const packageInstallResults = (0, devkit_internals_1.installPackageToTmp)('nx', 'latest', (0, package_manager_1.detectPackageManager)(workspace_root_1.workspaceRoot));
48
48
  cleanup = packageInstallResults.cleanup;
49
49
  let modulePath = require.resolve('nx/src/command-line/configure-ai-agents/configure-ai-agents.js', { paths: [packageInstallResults.tempDir] });
50
50
  const module = await (0, handle_import_1.handleImport)(modulePath);
@@ -172,7 +172,7 @@ async function generateGraph(args, affectedProjects) {
172
172
  });
173
173
  }
174
174
  output_1.output.warn({
175
- title: `${errors?.length > 1 ? `${errors.length} errors` : `An error`} occured while processing the project graph. Showing partial graph.`,
175
+ title: `${errors?.length > 1 ? `${errors.length} errors` : `An error`} occurred while processing the project graph. Showing partial graph.`,
176
176
  });
177
177
  }
178
178
  }
@@ -42,7 +42,7 @@ async function initHandler(options, inner = false) {
42
42
  let cleanup;
43
43
  try {
44
44
  await (0, provenance_1.ensurePackageHasProvenance)('nx', 'latest');
45
- const packageInstallResults = (0, devkit_internals_1.installPackageToTmp)('nx', 'latest');
45
+ const packageInstallResults = (0, devkit_internals_1.installPackageToTmp)('nx', 'latest', (0, package_manager_2.detectPackageManager)(process.cwd()));
46
46
  cleanup = packageInstallResults.cleanup;
47
47
  let modulePath = require.resolve('nx/src/command-line/init/init-v2.js', {
48
48
  paths: [packageInstallResults.tempDir],
@@ -90,7 +90,7 @@ function withMigrationOptions(yargs) {
90
90
  default: false,
91
91
  })
92
92
  .option('mode', {
93
- 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'.",
93
+ 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. The 'first-party' and 'third-party' modes are only available on Nx v23+: 'third-party' requires the workspace to already be on v23+, and 'first-party' requires migrating from v22+ into a v23+ target. When targeting Nx in an interactive terminal, prompts for the value if not provided; otherwise defaults to 'all'.",
94
94
  type: 'string',
95
95
  choices: exports.MIGRATE_MODES,
96
96
  })
@@ -119,6 +119,8 @@ export declare function resolveCanonicalNxPackage(targetVersion: string): 'nx' |
119
119
  export declare function resolveMode(mode: MigrateMode | undefined, targetPackage: string, targetVersion: string, context?: {
120
120
  hasFrom: boolean;
121
121
  hasExcludeAppliedMigrations: boolean;
122
+ installedMajor?: number | null;
123
+ isV23Plus?: boolean;
122
124
  }, configuredMode?: MigrateMode): Promise<MigrateMode>;
123
125
  type GenerateMigrations = {
124
126
  type: 'generateMigrations';
@@ -533,32 +533,75 @@ async function resolveMode(mode, targetPackage, targetVersion, context = {
533
533
  hasFrom: false,
534
534
  hasExcludeAppliedMigrations: false,
535
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;
536
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
+ }
537
561
  return mode;
538
562
  }
539
563
  if (!(0, version_utils_1.isNxEquivalentTarget)(targetPackage, targetVersion)) {
540
564
  return 'all';
541
565
  }
542
- // nx.json `migrate.mode` pre-selects the value the interactive prompt would
543
- // ask for; it applies only to Nx targets (non-Nx returned 'all' above).
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'.
544
569
  if (configuredMode) {
545
- return configuredMode;
546
- }
547
- if (!process.stdin.isTTY || (0, is_ci_1.isCI)()) {
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
+ });
548
582
  return 'all';
549
583
  }
550
- const choices = [
551
- {
584
+ const choices = [];
585
+ if (firstPartyAvailable) {
586
+ choices.push({
552
587
  name: 'first-party',
553
588
  message: 'First-party only (Nx and its official packages)',
554
- },
555
- ];
556
- if (!context.hasFrom && !context.hasExcludeAppliedMigrations) {
589
+ });
590
+ }
591
+ if (thirdPartyAvailable &&
592
+ !context.hasFrom &&
593
+ !context.hasExcludeAppliedMigrations) {
557
594
  choices.push({
558
595
  name: 'third-party',
559
596
  message: 'Third-party only (deps managed by Nx)',
560
597
  });
561
598
  }
599
+ if (!choices.length) {
600
+ return 'all';
601
+ }
602
+ if (!process.stdin.isTTY || (0, is_ci_1.isCI)()) {
603
+ return 'all';
604
+ }
562
605
  choices.push({
563
606
  name: 'all',
564
607
  message: 'All (first-party and third-party)',
@@ -701,9 +744,11 @@ function assertThirdPartyModeFlagCompatibility(options) {
701
744
  throw new Error(`Error: '--mode=third-party' cannot be combined with '--exclude-applied-migrations'.`);
702
745
  }
703
746
  }
704
- // Defaults target package/version mode-aware (third-party → installed
705
- // canonical, otherwise nx@latest) and enforces the era gate when --mode
706
- // 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.
707
752
  async function resolveTargetAndMode(args) {
708
753
  const { positional, from, options } = args;
709
754
  let targetPackage;
@@ -713,14 +758,54 @@ async function resolveTargetAndMode(args) {
713
758
  targetPackage = normalizeSlashes(parsed.targetPackage);
714
759
  targetVersion = parsed.targetVersion;
715
760
  }
716
- // Resolve mode before defaulting target so the default can depend on the
717
- // resolved mode (third-party defaults to nx@<installed>; otherwise nx@latest).
718
- // For bare invocation, `targetPackage='nx'` and `targetVersion='latest'` are
719
- // safe sentinels: `isNxEquivalentTarget` treats the literal `'latest'` as
720
- // 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
+ }
721
804
  const mode = await resolveMode(options.mode, targetPackage ?? 'nx', targetVersion ?? 'latest', {
722
805
  hasFrom: Object.keys(from).length > 0,
723
806
  hasExcludeAppliedMigrations: options.excludeAppliedMigrations === true,
807
+ installedMajor,
808
+ isV23Plus,
724
809
  }, options.modeFromConfig);
725
810
  let installedNxVersion;
726
811
  // For third-party, anchor `targetPackage`/`targetVersion` to the installed
@@ -730,7 +815,6 @@ async function resolveTargetAndMode(args) {
730
815
  // the literal `'latest'` that `parseTargetPackageAndVersion` emits for bare
731
816
  // package names.
732
817
  if (mode === 'third-party' && (!positional || !(0, semver_1.valid)(targetVersion))) {
733
- const installed = resolveInstalledCanonical();
734
818
  if (!installed) {
735
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.`);
736
820
  }
@@ -738,15 +822,6 @@ async function resolveTargetAndMode(args) {
738
822
  targetPackage = installed.canonical;
739
823
  targetVersion = installed.version;
740
824
  }
741
- else if (!positional) {
742
- // Bare invocation: default to `nx@latest` as a literal sentinel rather
743
- // than resolving via the registry here. Multi-major resolves the dist-tag
744
- // when needed (and bails gracefully on registry failure), and the cascade
745
- // resolves it for the walk (honouring `NX_MIGRATE_SKIP_REGISTRY_FETCH`).
746
- // This matches the resilience of `nx migrate nx`.
747
- targetPackage = 'nx';
748
- targetVersion = 'latest';
749
- }
750
825
  if (options.mode && !(0, version_utils_1.isNxEquivalentTarget)(targetPackage, targetVersion)) {
751
826
  const isLegacy = (0, version_utils_1.isLegacyEra)(targetVersion);
752
827
  const validTargets = isLegacy
@@ -863,14 +938,14 @@ function createInstalledPackageVersionsResolver(root) {
863
938
  return getInstalledPackageVersion;
864
939
  }
865
940
  // testing-fetch-start
866
- function createFetcher() {
941
+ function createFetcher(pmc) {
867
942
  const migrationsCache = {};
868
943
  const resolvedVersionCache = {};
869
944
  function fetchMigrations(packageName, packageVersion, setCache) {
870
945
  if (process.env.NX_MIGRATE_SKIP_REGISTRY_FETCH === 'true') {
871
946
  // Skip registry fetch and use installation method directly
872
947
  logger_1.logger.info(`Fetching ${packageName}@${packageVersion}`);
873
- return getPackageMigrationsUsingInstall(packageName, packageVersion);
948
+ return getPackageMigrationsUsingInstall(packageName, packageVersion, pmc);
874
949
  }
875
950
  const cacheKey = packageName + '-' + packageVersion;
876
951
  return Promise.resolve(resolvedVersionCache[cacheKey])
@@ -892,7 +967,7 @@ function createFetcher() {
892
967
  .catch((e) => {
893
968
  logger_1.logger.verbose(`Failed to get migrations from registry for ${packageName}@${packageVersion}: ${e.message}. Falling back to install.`);
894
969
  logger_1.logger.info(`Fetching ${packageName}@${packageVersion}`);
895
- return getPackageMigrationsUsingInstall(packageName, packageVersion);
970
+ return getPackageMigrationsUsingInstall(packageName, packageVersion, pmc);
896
971
  });
897
972
  }
898
973
  return function nxMigrateFetcher(packageName, packageVersion) {
@@ -1011,18 +1086,17 @@ function createConcurrencyLimiter(concurrency) {
1011
1086
  const installConcurrencyLimit = process.env.NX_MIGRATE_INSTALL_CONCURRENCY
1012
1087
  ? createConcurrencyLimiter(Math.max(1, Math.floor(Number(process.env.NX_MIGRATE_INSTALL_CONCURRENCY)) || 1))
1013
1088
  : null;
1014
- async function getPackageMigrationsUsingInstall(packageName, packageVersion) {
1015
- const run = () => getPackageMigrationsUsingInstallImpl(packageName, packageVersion);
1089
+ async function getPackageMigrationsUsingInstall(packageName, packageVersion, pmc) {
1090
+ const run = () => getPackageMigrationsUsingInstallImpl(packageName, packageVersion, pmc);
1016
1091
  return installConcurrencyLimit ? installConcurrencyLimit(run) : run();
1017
1092
  }
1018
- async function getPackageMigrationsUsingInstallImpl(packageName, packageVersion) {
1093
+ async function getPackageMigrationsUsingInstallImpl(packageName, packageVersion, pmc) {
1019
1094
  const { dir, cleanup } = (0, package_manager_1.createTempNpmDirectory)();
1020
1095
  let result;
1021
1096
  if ((0, provenance_1.getNxPackageGroup)().includes(packageName)) {
1022
1097
  await (0, provenance_1.ensurePackageHasProvenance)(packageName, packageVersion);
1023
1098
  }
1024
1099
  try {
1025
- const pmc = (0, package_manager_1.getPackageManagerCommand)((0, package_manager_1.detectPackageManager)(dir), dir);
1026
1100
  await execAsync(`${pmc.add} ${packageName}@${packageVersion}`, {
1027
1101
  cwd: dir,
1028
1102
  env: {
@@ -1046,7 +1120,6 @@ async function getPackageMigrationsUsingInstallImpl(packageName, packageVersion)
1046
1120
  };
1047
1121
  }
1048
1122
  catch (e) {
1049
- const pmc = (0, package_manager_1.getPackageManagerCommand)((0, package_manager_1.detectPackageManager)(dir), dir);
1050
1123
  throw new Error([
1051
1124
  `Failed to fetch migrations for ${packageName}@${packageVersion}`,
1052
1125
  formatCommandFailure(`${pmc.add} ${packageName}@${packageVersion}`, e),
@@ -1233,7 +1306,7 @@ async function generateMigrationsJsonAndUpdatePackageJson(root, opts) {
1233
1306
  }
1234
1307
  logger_1.logger.info(`Fetching meta data about packages.`);
1235
1308
  logger_1.logger.info(`It may take a few minutes.`);
1236
- const fetch = createFetcher();
1309
+ const fetch = createFetcher(pmc);
1237
1310
  let firstPartyPackages;
1238
1311
  if (mode === 'first-party' || mode === 'third-party') {
1239
1312
  // `@nx/workspace` is version-synced with `nx` and declares an
@@ -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