nx 22.7.0 → 23.0.0-beta.1

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/src/command-line/init/implementation/dot-nx/add-nx-scripts.js +13 -1
  2. package/dist/src/command-line/init/implementation/dot-nx/nxw.js +2 -1
  3. package/dist/src/command-line/migrate/command-object.js +5 -0
  4. package/dist/src/command-line/migrate/migrate.d.ts +10 -2
  5. package/dist/src/command-line/migrate/migrate.js +157 -30
  6. package/dist/src/config/workspace-json-project-json.d.ts +10 -0
  7. package/dist/src/core/graph/main.js +1 -1
  8. package/dist/src/daemon/client/client.js +1 -1
  9. package/dist/src/daemon/client/daemon-environment.js +2 -0
  10. package/dist/src/daemon/server/handle-hash-tasks.js +1 -1
  11. package/dist/src/daemon/server/project-graph-incremental-recomputation.d.ts +1 -4
  12. package/dist/src/daemon/server/project-graph-incremental-recomputation.js +11 -20
  13. package/dist/src/executors/utils/convert-nx-executor.js +2 -2
  14. package/dist/src/hasher/create-task-hasher.js +1 -1
  15. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  16. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  17. package/dist/src/project-graph/build-project-graph.d.ts +2 -4
  18. package/dist/src/project-graph/build-project-graph.js +2 -7
  19. package/dist/src/project-graph/file-map-utils.d.ts +2 -4
  20. package/dist/src/project-graph/file-map-utils.js +0 -3
  21. package/dist/src/project-graph/plugins/get-plugins.d.ts +15 -0
  22. package/dist/src/project-graph/plugins/get-plugins.js +21 -3
  23. package/dist/src/project-graph/project-graph.js +7 -6
  24. package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.d.ts +40 -64
  25. package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.js +182 -411
  26. package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.d.ts +10 -4
  27. package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.js +22 -8
  28. package/dist/src/project-graph/utils/project-configuration/source-maps.d.ts +4 -61
  29. package/dist/src/project-graph/utils/project-configuration/source-maps.js +14 -59
  30. package/dist/src/project-graph/utils/project-configuration/target-defaults.d.ts +16 -0
  31. package/dist/src/project-graph/utils/project-configuration/target-defaults.js +117 -0
  32. package/dist/src/project-graph/utils/project-configuration/target-merging.d.ts +1 -4
  33. package/dist/src/project-graph/utils/project-configuration/target-merging.js +261 -136
  34. package/dist/src/project-graph/utils/project-configuration/target-normalization.js +0 -7
  35. package/dist/src/project-graph/utils/project-configuration/utils.d.ts +23 -0
  36. package/dist/src/project-graph/utils/project-configuration/utils.js +164 -0
  37. package/dist/src/project-graph/utils/project-configuration-utils.d.ts +33 -9
  38. package/dist/src/project-graph/utils/project-configuration-utils.js +153 -65
  39. package/dist/src/project-graph/utils/retrieve-workspace-files.d.ts +6 -3
  40. package/dist/src/project-graph/utils/retrieve-workspace-files.js +32 -13
  41. package/dist/src/tasks-runner/run-command.js +2 -3
  42. package/dist/src/utils/workspace-context.js +1 -1
  43. package/package.json +11 -11
@@ -64,7 +64,19 @@ function generateDotNxSetup(version) {
64
64
  (0, tree_1.flushChanges)(host.root, changes);
65
65
  // Ensure that the dot-nx installation is available.
66
66
  // This is needed when using a global nx with dot-nx, otherwise running any nx command using global command will fail due to missing modules.
67
- (0, child_process_1.execSync)('./nx --version', { stdio: 'ignore', windowsHide: true });
67
+ // Pipe stderr so failures surface in telemetry instead of bare "Command failed: ./nx --version".
68
+ try {
69
+ (0, child_process_1.execSync)('./nx --version', {
70
+ stdio: ['ignore', 'ignore', 'pipe'],
71
+ encoding: 'utf8',
72
+ windowsHide: true,
73
+ });
74
+ }
75
+ catch (e) {
76
+ if (e?.stderr)
77
+ process.stderr.write(e.stderr);
78
+ throw e;
79
+ }
68
80
  }
69
81
  function normalizeVersionForNxJson(pkg, version) {
70
82
  if (!(0, semver_1.valid)(version)) {
@@ -60,7 +60,8 @@ function performInstallation(currentInstallation, nxJson) {
60
60
  },
61
61
  }));
62
62
  try {
63
- cp.execSync('npm i', {
63
+ // --include=dev forces install even if consumer env sets NODE_ENV=production / omit=dev.
64
+ cp.execSync('npm i --include=dev', {
64
65
  cwd: path.dirname(installationPath),
65
66
  stdio: 'inherit',
66
67
  windowsHide: true,
@@ -61,6 +61,11 @@ function withMigrationOptions(yargs) {
61
61
  describe: 'Exclude migrations that should have been applied on previous updates. To be used with --from.',
62
62
  type: 'boolean',
63
63
  default: false,
64
+ })
65
+ .option('skipInstall', {
66
+ describe: 'Skip installing packages before running migrations. Useful when the installation needs to be performed manually (e.g., to resolve peer dependency conflicts).',
67
+ type: 'boolean',
68
+ default: false,
64
69
  })
65
70
  .check(({ createCommits, commitPrefix, from, excludeAppliedMigrations }) => {
66
71
  if (!createCommits && commitPrefix !== defaultCommitPrefix) {
@@ -104,12 +104,20 @@ type RunMigrations = {
104
104
  export declare function parseMigrationsOptions(options: {
105
105
  [k: string]: any;
106
106
  }): Promise<GenerateMigrations | RunMigrations>;
107
+ /**
108
+ * Detects npm peer-dependency resolution failures. Keyed on the `ERESOLVE`
109
+ * error code, which npm consistently emits for this class of failure across
110
+ * v7+ (`npm ERR! code ERESOLVE` / `npm error code ERESOLVE`). Falls back to a
111
+ * small set of stable phrases in case the code line is missing from the
112
+ * captured output.
113
+ */
114
+ export declare function isNpmPeerDepsError(stderr: string): boolean;
107
115
  export declare function executeMigrations(root: string, migrations: {
108
116
  package: string;
109
117
  name: string;
110
118
  description?: string;
111
119
  version: string;
112
- }[], isVerbose: boolean, shouldCreateCommits: boolean, commitPrefix: string): Promise<{
120
+ }[], isVerbose: boolean, shouldCreateCommits: boolean, commitPrefix: string, shouldSkipInstall?: boolean): Promise<{
113
121
  migrationsWithNoChanges: {
114
122
  package: string;
115
123
  name: string;
@@ -123,7 +131,7 @@ export declare function runNxOrAngularMigration(root: string, migration: {
123
131
  name: string;
124
132
  description?: string;
125
133
  version: string;
126
- }, isVerbose: boolean, shouldCreateCommits: boolean, commitPrefix: string, installDepsIfChanged?: () => void, handleInstallDeps?: boolean): Promise<{
134
+ }, isVerbose: boolean, shouldCreateCommits: boolean, commitPrefix: string, installDepsIfChanged?: () => Promise<void>, handleInstallDeps?: boolean): Promise<{
127
135
  changes: FileChange[];
128
136
  nextSteps: string[];
129
137
  }>;
@@ -4,6 +4,7 @@ exports.Migrator = void 0;
4
4
  exports.formatCommandFailure = formatCommandFailure;
5
5
  exports.normalizeVersion = normalizeVersion;
6
6
  exports.parseMigrationsOptions = parseMigrationsOptions;
7
+ exports.isNpmPeerDepsError = isNpmPeerDepsError;
7
8
  exports.executeMigrations = executeMigrations;
8
9
  exports.runNxOrAngularMigration = runNxOrAngularMigration;
9
10
  exports.migrate = migrate;
@@ -1064,28 +1065,120 @@ function showConnectToCloudMessage() {
1064
1065
  return false;
1065
1066
  }
1066
1067
  }
1067
- function runInstall(nxWorkspaceRoot) {
1068
- let packageManager;
1069
- let pmCommands;
1070
- if (nxWorkspaceRoot) {
1071
- packageManager = (0, package_manager_1.detectPackageManager)(nxWorkspaceRoot);
1072
- pmCommands = (0, package_manager_1.getPackageManagerCommand)(packageManager, nxWorkspaceRoot);
1073
- }
1074
- else {
1075
- pmCommands = (0, package_manager_1.getPackageManagerCommand)();
1076
- }
1068
+ function runInstall(nxWorkspaceRoot, phase = 'pre-migration') {
1069
+ const cwd = nxWorkspaceRoot ?? process.cwd();
1070
+ const packageManager = (0, package_manager_1.detectPackageManager)(cwd);
1071
+ const pmCommands = (0, package_manager_1.getPackageManagerCommand)(packageManager, cwd);
1077
1072
  const installCommand = `${pmCommands.install} ${pmCommands.ignoreScriptsFlag ?? ''}`;
1078
1073
  output_1.output.log({
1079
1074
  title: `Running '${installCommand}' to make sure necessary packages are installed`,
1080
1075
  });
1081
- (0, child_process_1.execSync)(installCommand, {
1082
- stdio: [0, 1, 2],
1083
- windowsHide: true,
1084
- cwd: nxWorkspaceRoot ?? process.cwd(),
1076
+ return new Promise((resolve, reject) => {
1077
+ // For npm, pipe stderr so we can detect peer dependency errors while still
1078
+ // mirroring it live to the user's terminal. Other package managers inherit
1079
+ // stderr directly since we don't need to inspect their output.
1080
+ const shouldCaptureStderr = packageManager === 'npm';
1081
+ const child = (0, child_process_1.spawn)(installCommand, {
1082
+ shell: true,
1083
+ stdio: ['inherit', 'inherit', shouldCaptureStderr ? 'pipe' : 'inherit'],
1084
+ windowsHide: true,
1085
+ cwd,
1086
+ });
1087
+ const stderrChunks = [];
1088
+ child.stderr?.on('data', (chunk) => {
1089
+ process.stderr.write(chunk);
1090
+ stderrChunks.push(chunk);
1091
+ });
1092
+ child.on('error', reject);
1093
+ child.on('close', (code) => {
1094
+ if (code === 0) {
1095
+ resolve();
1096
+ return;
1097
+ }
1098
+ if (shouldCaptureStderr) {
1099
+ const stderr = Buffer.concat(stderrChunks).toString().trim();
1100
+ if (isNpmPeerDepsError(stderr)) {
1101
+ // Log the remediation guidance here so every caller of `runInstall`
1102
+ // (CLI migrate, `nx repair`, single-migration runner, etc.) surfaces
1103
+ // it consistently. Top-level callers catch `NpmPeerDepsInstallError`
1104
+ // and return a non-zero exit code without re-logging.
1105
+ logNpmPeerDepsError(phase);
1106
+ reject(new NpmPeerDepsInstallError());
1107
+ return;
1108
+ }
1109
+ }
1110
+ reject(new Error(`Command failed: ${installCommand}`));
1111
+ });
1085
1112
  });
1086
1113
  }
1087
- async function executeMigrations(root, migrations, isVerbose, shouldCreateCommits, commitPrefix) {
1088
- const changedDepInstaller = new ChangedDepInstaller(root);
1114
+ class NpmPeerDepsInstallError extends Error {
1115
+ constructor() {
1116
+ super('npm install failed due to peer dependency conflicts.');
1117
+ this.name = 'NpmPeerDepsInstallError';
1118
+ }
1119
+ }
1120
+ /**
1121
+ * Detects npm peer-dependency resolution failures. Keyed on the `ERESOLVE`
1122
+ * error code, which npm consistently emits for this class of failure across
1123
+ * v7+ (`npm ERR! code ERESOLVE` / `npm error code ERESOLVE`). Falls back to a
1124
+ * small set of stable phrases in case the code line is missing from the
1125
+ * captured output.
1126
+ */
1127
+ function isNpmPeerDepsError(stderr) {
1128
+ if (/\bERESOLVE\b/.test(stderr)) {
1129
+ return true;
1130
+ }
1131
+ const lowerStderr = stderr.toLowerCase();
1132
+ return (lowerStderr.includes('unable to resolve dependency tree') ||
1133
+ lowerStderr.includes('could not resolve dependency') ||
1134
+ lowerStderr.includes('conflicting peer dependency'));
1135
+ }
1136
+ function logNpmPeerDepsError(phase) {
1137
+ const peerDepsResolutionSteps = [
1138
+ 'Recommended approaches (in order of preference):',
1139
+ '',
1140
+ '1. Use "overrides" in package.json to force compatible versions across the dependency tree.',
1141
+ ' See https://docs.npmjs.com/cli/configuring-npm/package-json#overrides',
1142
+ '2. Persist legacy peer deps resolution in the project ".npmrc":',
1143
+ ' npm config set legacy-peer-deps=true --location=project',
1144
+ ' (bypasses peer dependency resolution; use with caution)',
1145
+ '3. As a last resort, force the installation by running "npm install --force".',
1146
+ ' (does not persist and may produce broken installs)',
1147
+ ];
1148
+ const manualInstallHint = [
1149
+ 'If you installed the dependencies manually, pass "--skip-install" to avoid re-installing them:',
1150
+ ' nx migrate --run-migrations --skip-install',
1151
+ ];
1152
+ if (phase === 'pre-migration') {
1153
+ output_1.output.error({
1154
+ title: 'You need to resolve the peer dependency conflicts before the migration can continue',
1155
+ bodyLines: [
1156
+ ...peerDepsResolutionSteps,
1157
+ '',
1158
+ 'Once the conflicts are resolved, re-run the migrations:',
1159
+ ' nx migrate --run-migrations',
1160
+ '',
1161
+ ...manualInstallHint,
1162
+ ],
1163
+ });
1164
+ }
1165
+ else {
1166
+ output_1.output.error({
1167
+ title: 'Some migrations have been applied, but installing the updated dependencies failed',
1168
+ bodyLines: [
1169
+ ...peerDepsResolutionSteps,
1170
+ '',
1171
+ 'Once the conflicts are resolved, run "npm install" to install the updated dependencies.',
1172
+ 'If the migration was interrupted before completing, re-run the remaining migrations:',
1173
+ ' nx migrate --run-migrations',
1174
+ '',
1175
+ ...manualInstallHint,
1176
+ ],
1177
+ });
1178
+ }
1179
+ }
1180
+ async function executeMigrations(root, migrations, isVerbose, shouldCreateCommits, commitPrefix, shouldSkipInstall = false) {
1181
+ const changedDepInstaller = new ChangedDepInstaller(root, shouldSkipInstall);
1089
1182
  const migrationsWithNoChanges = [];
1090
1183
  const sortedMigrations = migrations.sort((a, b) => {
1091
1184
  // special case for the split configuration migration to run first
@@ -1114,30 +1207,53 @@ async function executeMigrations(root, migrations, isVerbose, shouldCreateCommit
1114
1207
  logger_1.logger.info(`---------------------------------------------------------`);
1115
1208
  }
1116
1209
  catch (e) {
1117
- output_1.output.error({
1118
- title: `Failed to run ${m.name} from ${m.package}. This workspace is NOT up to date!`,
1119
- });
1210
+ if (!(e instanceof NpmPeerDepsInstallError)) {
1211
+ output_1.output.error({
1212
+ title: `Failed to run ${m.name} from ${m.package}. This workspace is NOT up to date!`,
1213
+ });
1214
+ }
1120
1215
  throw e;
1121
1216
  }
1122
1217
  }
1123
1218
  if (!shouldCreateCommits) {
1124
- changedDepInstaller.installDepsIfChanged();
1219
+ await changedDepInstaller.installDepsIfChanged();
1220
+ }
1221
+ if (changedDepInstaller.skippedInstall) {
1222
+ logSkippedPostMigrationInstall(root);
1125
1223
  }
1126
1224
  return { migrationsWithNoChanges, nextSteps: allNextSteps };
1127
1225
  }
1128
1226
  class ChangedDepInstaller {
1129
- constructor(root) {
1227
+ constructor(root, shouldSkipInstall = false) {
1130
1228
  this.root = root;
1229
+ this.shouldSkipInstall = shouldSkipInstall;
1230
+ this._skippedInstall = false;
1131
1231
  this.initialDeps = getStringifiedPackageJsonDeps(root);
1132
1232
  }
1133
- installDepsIfChanged() {
1233
+ get skippedInstall() {
1234
+ return this._skippedInstall;
1235
+ }
1236
+ async installDepsIfChanged() {
1134
1237
  const currentDeps = getStringifiedPackageJsonDeps(this.root);
1135
1238
  if (this.initialDeps !== currentDeps) {
1136
- runInstall(this.root);
1239
+ if (this.shouldSkipInstall) {
1240
+ this._skippedInstall = true;
1241
+ }
1242
+ else {
1243
+ await runInstall(this.root, 'post-migration');
1244
+ }
1137
1245
  }
1138
1246
  this.initialDeps = currentDeps;
1139
1247
  }
1140
1248
  }
1249
+ function logSkippedPostMigrationInstall(root) {
1250
+ const packageManager = (0, package_manager_1.detectPackageManager)(root);
1251
+ const installCommand = (0, package_manager_1.getPackageManagerCommand)(packageManager, root).install;
1252
+ output_1.output.warn({
1253
+ title: 'Migrations updated your dependencies, but the install was skipped',
1254
+ bodyLines: [`Run "${installCommand}" to install the updated dependencies.`],
1255
+ });
1256
+ }
1141
1257
  async function runNxOrAngularMigration(root, migration, isVerbose, shouldCreateCommits, commitPrefix, installDepsIfChanged, handleInstallDeps = false) {
1142
1258
  if (!installDepsIfChanged) {
1143
1259
  const changedDepInstaller = new ChangedDepInstaller(root);
@@ -1173,7 +1289,7 @@ async function runNxOrAngularMigration(root, migration, isVerbose, shouldCreateC
1173
1289
  logger_1.logger.info('');
1174
1290
  }
1175
1291
  if (shouldCreateCommits) {
1176
- installDepsIfChanged();
1292
+ await installDepsIfChanged();
1177
1293
  const commitMessage = `${commitPrefix}${migration.name}`;
1178
1294
  try {
1179
1295
  const committedSha = (0, git_utils_1.commitChanges)(commitMessage, root);
@@ -1190,13 +1306,13 @@ async function runNxOrAngularMigration(root, migration, isVerbose, shouldCreateC
1190
1306
  // if we are running this function alone, we need to install deps internally
1191
1307
  }
1192
1308
  else if (handleInstallDeps) {
1193
- installDepsIfChanged();
1309
+ await installDepsIfChanged();
1194
1310
  }
1195
1311
  return { changes, nextSteps };
1196
1312
  }
1197
- async function runMigrations(root, opts, args, isVerbose, shouldCreateCommits = false, commitPrefix) {
1198
- if (!process.env.NX_MIGRATE_SKIP_INSTALL) {
1199
- runInstall();
1313
+ async function runMigrations(root, opts, args, isVerbose, shouldCreateCommits = false, commitPrefix, shouldSkipInstall = false) {
1314
+ if (!shouldSkipInstall && !process.env.NX_MIGRATE_SKIP_INSTALL) {
1315
+ await runInstall();
1200
1316
  }
1201
1317
  if (!__dirname.startsWith(workspace_root_1.workspaceRoot)) {
1202
1318
  // we are running from a temp installation with nx latest, switch to running
@@ -1229,7 +1345,7 @@ async function runMigrations(root, opts, args, isVerbose, shouldCreateCommits =
1229
1345
  (shouldCreateCommits ? ', with each applied in a dedicated commit' : ''),
1230
1346
  });
1231
1347
  const migrations = (0, fileutils_1.readJsonFile)((0, path_1.join)(root, opts.runMigrations)).migrations;
1232
- const { migrationsWithNoChanges, nextSteps } = await executeMigrations(root, migrations, isVerbose, shouldCreateCommits, commitPrefix);
1348
+ const { migrationsWithNoChanges, nextSteps } = await executeMigrations(root, migrations, isVerbose, shouldCreateCommits, commitPrefix, shouldSkipInstall);
1233
1349
  if (migrationsWithNoChanges.length < migrations.length) {
1234
1350
  output_1.output.success({
1235
1351
  title: `Successfully finished running migrations from '${opts.runMigrations}'. This workspace is up to date!`,
@@ -1283,7 +1399,18 @@ async function migrate(root, args, rawArgs) {
1283
1399
  await generateMigrationsJsonAndUpdatePackageJson(root, opts);
1284
1400
  }
1285
1401
  else {
1286
- return runMigrations(root, opts, rawArgs, args['verbose'], args['createCommits'], args['commitPrefix']);
1402
+ try {
1403
+ return await runMigrations(root, opts, rawArgs, args['verbose'], args['createCommits'], args['commitPrefix'], args['skipInstall']);
1404
+ }
1405
+ catch (e) {
1406
+ // The remediation guidance is already logged by `runInstall`; swallow
1407
+ // the error here so `handleErrors` doesn't print a noisy stack after
1408
+ // the friendly output.
1409
+ if (e instanceof NpmPeerDepsInstallError) {
1410
+ return 1;
1411
+ }
1412
+ throw e;
1413
+ }
1287
1414
  }
1288
1415
  });
1289
1416
  }
@@ -260,4 +260,14 @@ export interface TargetConfiguration<T = any> {
260
260
  * is up to date.
261
261
  */
262
262
  syncGenerators?: string[];
263
+ /**
264
+ * Spread token used when merging target configurations. When set to `true`,
265
+ * base (inferred) values take priority over this target's values for any
266
+ * shared keys — effectively "only add new keys without overwriting inferred
267
+ * values". Keys that do not exist in the base target are still added.
268
+ *
269
+ * The position of `'...'` in the object's key order follows standard
270
+ * last-write-wins semantics with {@link https://nx.dev/reference/project-configuration#spread-token}.
271
+ */
272
+ '...'?: true;
263
273
  }