nx 23.0.0-beta.10 → 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.
- package/dist/src/command-line/migrate/command-object.js +1 -1
- package/dist/src/command-line/migrate/migrate.d.ts +23 -5
- package/dist/src/command-line/migrate/migrate.js +76 -28
- package/dist/src/command-line/migrate/multi-major.d.ts +23 -2
- package/dist/src/command-line/migrate/multi-major.js +47 -28
- package/dist/src/command-line/migrate/prompt-files.d.ts +12 -0
- package/dist/src/command-line/migrate/prompt-files.js +107 -0
- package/dist/src/command-line/migrate/version-utils.d.ts +1 -0
- package/dist/src/command-line/migrate/version-utils.js +9 -3
- package/dist/src/config/misc-interfaces.d.ts +3 -1
- package/dist/src/core/graph/main.js +1 -1
- package/dist/src/daemon/server/file-watching/route-workspace-changes.d.ts +0 -4
- package/dist/src/daemon/server/file-watching/route-workspace-changes.js +19 -8
- package/dist/src/daemon/server/project-graph-incremental-recomputation.js +38 -11
- package/dist/src/daemon/server/server.js +2 -1
- package/dist/src/executors/utils/convert-nx-executor.js +1 -1
- package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
- package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
- package/dist/src/project-graph/affected/locators/project-glob-changes.js +2 -1
- package/dist/src/project-graph/plugins/get-plugins.d.ts +7 -2
- package/dist/src/project-graph/plugins/get-plugins.js +8 -5
- package/dist/src/project-graph/plugins/tasks-execution-hooks.js +4 -2
- package/dist/src/project-graph/project-graph.js +1 -1
- package/dist/src/project-graph/utils/retrieve-workspace-files.js +1 -1
- package/dist/src/utils/installed-nx-version.js +5 -2
- package/package.json +11 -11
|
@@ -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.
|
|
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?:
|
|
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:
|
|
115
|
+
export declare function resolveMode(mode: MigrateMode | undefined, targetPackage: string, targetVersion: string, context?: {
|
|
110
116
|
hasFrom: boolean;
|
|
111
117
|
hasExcludeAppliedMigrations: boolean;
|
|
112
|
-
}): Promise<
|
|
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:
|
|
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 (!
|
|
144
|
+
const { generators: migrationEntries, resolvedPromptFiles } = await this.fetchMigrationConfig(packageName, version);
|
|
145
|
+
if (!migrationEntries)
|
|
143
146
|
return [];
|
|
144
|
-
|
|
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,
|
|
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,
|
|
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 =
|
|
592
|
-
? '
|
|
593
|
-
:
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
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
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
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
|
-
|
|
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:
|
|
24
|
+
mode: MigrateMode;
|
|
4
25
|
options: {
|
|
5
26
|
multiMajorMode?: MultiMajorMode;
|
|
6
27
|
};
|
|
7
28
|
targetPackage: string;
|
|
8
29
|
targetVersion: string;
|
|
9
|
-
}): Promise<
|
|
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
|
|
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 ${
|
|
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
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
//
|
|
152
|
-
//
|
|
153
|
-
|
|
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
|
-
|
|
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
|
+
}
|