nx 23.1.0 → 23.2.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 (79) hide show
  1. package/dist/bin/nx.js +1 -1
  2. package/dist/src/ai/configure-ai-agents-disclaimer.d.ts +5 -0
  3. package/dist/src/ai/configure-ai-agents-disclaimer.js +31 -0
  4. package/dist/src/command-line/add/add.js +8 -1
  5. package/dist/src/command-line/import/import.js +1 -1
  6. package/dist/src/command-line/init/ai-agent-prompts.d.ts +1 -1
  7. package/dist/src/command-line/init/ai-agent-prompts.js +7 -6
  8. package/dist/src/command-line/init/command-object.js +10 -2
  9. package/dist/src/command-line/init/configure-plugins.js +3 -2
  10. package/dist/src/command-line/init/implementation/add-nx-to-monorepo.js +4 -2
  11. package/dist/src/command-line/init/implementation/add-nx-to-nest.js +4 -3
  12. package/dist/src/command-line/init/implementation/add-nx-to-npm-repo.js +4 -3
  13. package/dist/src/command-line/init/implementation/add-nx-to-turborepo.js +4 -3
  14. package/dist/src/command-line/init/implementation/angular/index.js +4 -2
  15. package/dist/src/command-line/init/implementation/utils.d.ts +3 -3
  16. package/dist/src/command-line/init/implementation/utils.js +22 -3
  17. package/dist/src/command-line/migrate/execute-migration.d.ts +72 -0
  18. package/dist/src/command-line/migrate/execute-migration.js +389 -0
  19. package/dist/src/command-line/migrate/migrate-ui-api.js +17 -12
  20. package/dist/src/command-line/migrate/migrate.d.ts +14 -45
  21. package/dist/src/command-line/migrate/migrate.js +58 -375
  22. package/dist/src/command-line/release/utils/git.js +6 -2
  23. package/dist/src/command-line/release/utils/release-graph.d.ts +2 -2
  24. package/dist/src/command-line/release/utils/shared.d.ts +2 -2
  25. package/dist/src/command-line/show/show-target/info.js +11 -2
  26. package/dist/src/commands-runner/create-command-graph.js +5 -0
  27. package/dist/src/config/nx-json.d.ts +1 -1
  28. package/dist/src/core/graph/main.js +1 -1
  29. package/dist/src/daemon/client/client.d.ts +0 -2
  30. package/dist/src/daemon/client/client.js +17 -19
  31. package/dist/src/devkit-internals.d.ts +2 -0
  32. package/dist/src/devkit-internals.js +7 -1
  33. package/dist/src/generators/utils/nx-json.js +13 -3
  34. package/dist/src/native/index.d.ts +1 -1
  35. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  36. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  37. package/dist/src/plugins/js/lock-file/npm-parser.js +71 -27
  38. package/dist/src/plugins/js/lock-file/pnpm-parser.js +46 -15
  39. package/dist/src/plugins/js/lock-file/project-graph-pruning.js +1 -1
  40. package/dist/src/plugins/js/lock-file/yarn-parser.js +32 -5
  41. package/dist/src/plugins/js/utils/register.d.ts +18 -0
  42. package/dist/src/plugins/js/utils/register.js +81 -0
  43. package/dist/src/plugins/js/utils/typescript.d.ts +9 -0
  44. package/dist/src/plugins/js/utils/typescript.js +15 -0
  45. package/dist/src/project-graph/error-types.js +41 -1
  46. package/dist/src/project-graph/file-utils.js +6 -1
  47. package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +4 -0
  48. package/dist/src/project-graph/plugins/transpiler.js +4 -0
  49. package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.d.ts +9 -12
  50. package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.js +77 -61
  51. package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.d.ts +1 -1
  52. package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.js +1 -1
  53. package/dist/src/project-graph/utils/project-configuration-utils.js +4 -6
  54. package/dist/src/tasks-runner/life-cycles/performance-analysis.d.ts +11 -4
  55. package/dist/src/tasks-runner/life-cycles/performance-analysis.js +15 -6
  56. package/dist/src/tasks-runner/life-cycles/performance-life-cycle.js +5 -4
  57. package/dist/src/tasks-runner/life-cycles/performance-report.d.ts +18 -8
  58. package/dist/src/tasks-runner/life-cycles/performance-report.js +66 -29
  59. package/dist/src/tasks-runner/run-command.js +5 -3
  60. package/dist/src/utils/acknowledge-build-scripts.d.ts +13 -0
  61. package/dist/src/utils/acknowledge-build-scripts.js +100 -0
  62. package/dist/src/utils/catalog/manager-utils.d.ts +1 -2
  63. package/dist/src/utils/catalog/manager-utils.js +22 -5
  64. package/dist/src/utils/catalog/pnpm-manager.d.ts +1 -0
  65. package/dist/src/utils/catalog/pnpm-manager.js +3 -15
  66. package/dist/src/utils/catalog/yarn-manager.d.ts +1 -0
  67. package/dist/src/utils/catalog/yarn-manager.js +3 -15
  68. package/dist/src/utils/command-line-utils.js +30 -20
  69. package/dist/src/utils/git-revision.d.ts +12 -0
  70. package/dist/src/utils/git-revision.js +25 -0
  71. package/dist/src/utils/git-utils.d.ts +2 -3
  72. package/dist/src/utils/git-utils.js +101 -47
  73. package/dist/src/utils/min-release-age/behavior/pnpm.js +9 -7
  74. package/dist/src/utils/package-manager.js +6 -2
  75. package/dist/src/utils/provenance.js +12 -2
  76. package/migrations.json +7 -1
  77. package/package.json +11 -11
  78. package/dist/src/migrations/update-17-3-0/nx-release-path.d.ts +0 -3
  79. package/dist/src/migrations/update-17-3-0/nx-release-path.js +0 -47
@@ -5,6 +5,7 @@ exports.forceRegisterEsmLoader = forceRegisterEsmLoader;
5
5
  exports.nodeNextEsmResolveHook = nodeNextEsmResolveHook;
6
6
  exports.ensureNodeNextEsmResolverRegistered = ensureNodeNextEsmResolverRegistered;
7
7
  exports.ensureCjsResolverPatched = ensureCjsResolverPatched;
8
+ exports.ensureResolveConditionsInjected = ensureResolveConditionsInjected;
8
9
  exports.isNativeStripPreferred = isNativeStripPreferred;
9
10
  exports.registerTsProject = registerTsProject;
10
11
  exports.getSwcTranspiler = getSwcTranspiler;
@@ -388,6 +389,86 @@ const isInvokedByTsx = (() => {
388
389
  return (requireArgs.some((a) => isTsxPath(a)) ||
389
390
  importArgs.some((a) => isTsxPath(a)));
390
391
  })();
392
+ let resolveConditionsInjected = false;
393
+ /**
394
+ * Make Node's module resolver honor the given export conditions for the rest of
395
+ * the process, via `module.registerHooks()`. Used when a local plugin is loaded
396
+ * from source in-process (no child process to pass `--conditions` to at spawn):
397
+ * the hook appends the conditions to every resolution so the plugin's transitive
398
+ * workspace imports resolve to source the same way the plugin entry did.
399
+ *
400
+ * No-op when:
401
+ * - `conditions` is empty (nothing to inject);
402
+ * - every target condition is already active at startup (a spawned plugin
403
+ * worker or daemon was launched with the full `--conditions` set, so Node's
404
+ * resolver already honors them and the per-resolve hook would be redundant);
405
+ * - `module.registerHooks` is unavailable (Node < 22.15 / < 23.5). Those
406
+ * runtimes keep the `NODE_OPTIONS=--conditions` escape hatch.
407
+ *
408
+ * Idempotent and best-effort.
409
+ */
410
+ function ensureResolveConditionsInjected(conditions) {
411
+ if (resolveConditionsInjected)
412
+ return;
413
+ resolveConditionsInjected = true;
414
+ if (!conditions.length)
415
+ return;
416
+ // Skip only when Node already honors every target condition (e.g. a worker or
417
+ // daemon spawned with the full `--conditions` set); a partial overlap still
418
+ // needs the hook to add the missing ones.
419
+ const activeConditions = getConditionsActiveAtStartup();
420
+ if (conditions.every((condition) => activeConditions.has(condition)))
421
+ return;
422
+ const module = require('node:module');
423
+ const registerHooks = module.registerHooks;
424
+ if (typeof registerHooks !== 'function')
425
+ return;
426
+ try {
427
+ registerHooks.call(module, {
428
+ resolve(specifier, context, nextResolve) {
429
+ const merged = context.conditions
430
+ ? [...context.conditions, ...conditions]
431
+ : conditions;
432
+ return nextResolve(specifier, { ...context, conditions: merged });
433
+ },
434
+ });
435
+ }
436
+ catch {
437
+ // Best-effort: leave Node's native resolution in place rather than failing.
438
+ }
439
+ }
440
+ /**
441
+ * Export conditions this process was started with, parsed from `--conditions`
442
+ * (or its `-C` alias) in `process.execArgv` and `NODE_OPTIONS`. Node's resolver
443
+ * already honors these, so the injected hook only needs to cover target
444
+ * conditions not present here.
445
+ */
446
+ function getConditionsActiveAtStartup() {
447
+ const active = new Set();
448
+ const collect = (tokens) => {
449
+ for (let i = 0; i < tokens.length; i++) {
450
+ const token = tokens[i];
451
+ if (token === '--conditions' || token === '-C') {
452
+ const value = tokens[i + 1];
453
+ if (value) {
454
+ active.add(value);
455
+ i++;
456
+ }
457
+ }
458
+ else if (token.startsWith('--conditions=')) {
459
+ active.add(token.slice('--conditions='.length));
460
+ }
461
+ else if (token.startsWith('-C=')) {
462
+ active.add(token.slice('-C='.length));
463
+ }
464
+ }
465
+ };
466
+ collect(process.execArgv ?? []);
467
+ const nodeOptions = process.env.NODE_OPTIONS;
468
+ if (nodeOptions)
469
+ collect(nodeOptions.split(/\s+/).filter(Boolean));
470
+ return active;
471
+ }
391
472
  /**
392
473
  * Whether the current Node.js runtime exposes native TypeScript type
393
474
  * stripping. This is the authoritative gate - it correctly handles every
@@ -20,4 +20,13 @@ export declare function getRootTsConfigCustomConditions(root?: string): string[]
20
20
  * `migrate-development-custom-condition` (21.5).
21
21
  */
22
22
  export declare function getRootTsConfigResolveExportsConditions(root?: string): string[];
23
+ /**
24
+ * Node `--conditions <name>` CLI args for spawning a plugin worker or the daemon
25
+ * with the plugin-resolution conditions active at startup. Mirrors the set Nx
26
+ * uses to resolve the plugin entry (`getRootTsConfigResolveExportsConditions`)
27
+ * so the entry and the plugin's transitive workspace imports resolve the same
28
+ * way; Node's own resolver otherwise ignores TypeScript `customConditions` and a
29
+ * source-loaded plugin's imports land on their unbuilt `dist`.
30
+ */
31
+ export declare function getPluginResolveConditionNodeArgs(root?: string): string[];
23
32
  export declare function findNodes(node: Node, kind: SyntaxKind | SyntaxKind[], max?: number): Node[];
@@ -8,6 +8,7 @@ exports.getRootTsConfigFileName = getRootTsConfigFileName;
8
8
  exports.getRootTsConfigPath = getRootTsConfigPath;
9
9
  exports.getRootTsConfigCustomConditions = getRootTsConfigCustomConditions;
10
10
  exports.getRootTsConfigResolveExportsConditions = getRootTsConfigResolveExportsConditions;
11
+ exports.getPluginResolveConditionNodeArgs = getPluginResolveConditionNodeArgs;
11
12
  exports.findNodes = findNodes;
12
13
  const workspace_root_1 = require("../../../utils/workspace-root");
13
14
  const fs_1 = require("fs");
@@ -113,6 +114,20 @@ function getRootTsConfigResolveExportsConditions(root = workspace_root_1.workspa
113
114
  ? conditions
114
115
  : [...conditions, 'development'];
115
116
  }
117
+ /**
118
+ * Node `--conditions <name>` CLI args for spawning a plugin worker or the daemon
119
+ * with the plugin-resolution conditions active at startup. Mirrors the set Nx
120
+ * uses to resolve the plugin entry (`getRootTsConfigResolveExportsConditions`)
121
+ * so the entry and the plugin's transitive workspace imports resolve the same
122
+ * way; Node's own resolver otherwise ignores TypeScript `customConditions` and a
123
+ * source-loaded plugin's imports land on their unbuilt `dist`.
124
+ */
125
+ function getPluginResolveConditionNodeArgs(root = workspace_root_1.workspaceRoot) {
126
+ return getRootTsConfigResolveExportsConditions(root).flatMap((c) => [
127
+ '--conditions',
128
+ c,
129
+ ]);
130
+ }
116
131
  function findNodes(node, kind, max = Infinity) {
117
132
  if (!node || max == 0) {
118
133
  return [];
@@ -242,9 +242,48 @@ class AggregateCreateNodesError extends Error {
242
242
  errorTuple.length === 2)) {
243
243
  throw new Error('AggregateCreateNodesError must be constructed with an array of tuples where the first element is a filename or undefined and the second element is the underlying error.');
244
244
  }
245
+ // Plugins pass through whatever value they caught, which is not
246
+ // guaranteed to be an Error. Coerce so formatting can rely on
247
+ // message and stack being present.
248
+ for (const errorTuple of errors) {
249
+ errorTuple[1] = coerceToError(errorTuple[1]);
250
+ }
245
251
  }
246
252
  }
247
253
  exports.AggregateCreateNodesError = AggregateCreateNodesError;
254
+ function coerceToError(value) {
255
+ if (value instanceof Error) {
256
+ return value;
257
+ }
258
+ let message;
259
+ let stack;
260
+ if (typeof value === 'object' && value !== null) {
261
+ const candidate = value;
262
+ if (typeof candidate.message === 'string') {
263
+ message = candidate.message;
264
+ if (typeof candidate.stack === 'string') {
265
+ stack = candidate.stack;
266
+ }
267
+ }
268
+ else {
269
+ try {
270
+ message = JSON.stringify(value);
271
+ }
272
+ catch {
273
+ // Circular structures cannot be stringified.
274
+ message = String(value);
275
+ }
276
+ }
277
+ }
278
+ else {
279
+ message = String(value);
280
+ }
281
+ const error = new Error(message);
282
+ // A synthesized stack would point at this coercion site rather than
283
+ // the original failure, so prefer the original stack or just the message.
284
+ error.stack = stack ?? message;
285
+ return error;
286
+ }
248
287
  function formatAggregateCreateNodesError(error, pluginName) {
249
288
  const errorCount = error.errors.length > 1 ? `${error.errors.length} errors` : 'An error';
250
289
  const pluginLocation = error.pluginIndex
@@ -270,7 +309,8 @@ function formatAggregateCreateNodesError(error, pluginName) {
270
309
  }
271
310
  for (const e of errors) {
272
311
  const messageLines = e.message.split('\n');
273
- const stackLines = e.stack.split('\n');
312
+ // Errors deserialized from a plugin worker may arrive without a stack.
313
+ const stackLines = (e.stack ?? e.message).split('\n');
274
314
  if (file) {
275
315
  errorBodyLines.push(...messageLines.map((line) => ` ${line}`));
276
316
  errorStackLines.push(...stackLines.map((line) => ` ${line}`));
@@ -12,6 +12,7 @@ const fs_1 = require("fs");
12
12
  const os_1 = require("os");
13
13
  const path_1 = require("path");
14
14
  const fileutils_1 = require("../utils/fileutils");
15
+ const git_revision_1 = require("../utils/git-revision");
15
16
  const ignore_1 = require("../utils/ignore");
16
17
  const json_diff_1 = require("../utils/json-diff");
17
18
  const workspace_root_1 = require("../utils/workspace-root");
@@ -116,11 +117,14 @@ function readLockFileAtRevision(file, lockFileName, revision, readFileAtRevision
116
117
  }
117
118
  exports.TEN_MEGABYTES = 1024 * 10000;
118
119
  function defaultReadFileAtRevision(file, revision) {
120
+ if (revision) {
121
+ (0, git_revision_1.assertValidGitRevision)(revision);
122
+ }
119
123
  try {
120
124
  const filePathInGitRepository = getFilePathInGitRepository(file);
121
125
  return !revision
122
126
  ? (0, fs_1.readFileSync)(file, 'utf-8')
123
- : (0, child_process_1.execSync)(`git show ${revision}:${filePathInGitRepository}`, {
127
+ : (0, child_process_1.execFileSync)('git', ['show', `${revision}:${filePathInGitRepository}`], {
124
128
  maxBuffer: exports.TEN_MEGABYTES,
125
129
  stdio: ['pipe', 'pipe', 'ignore'],
126
130
  windowsHide: true,
@@ -140,6 +144,7 @@ function defaultReadBunLockFileAtRevision(file, revision) {
140
144
  windowsHide: true,
141
145
  }).trim();
142
146
  }
147
+ (0, git_revision_1.assertValidGitRevision)(revision);
143
148
  const filePathInGitRepository = getFilePathInGitRepository(file);
144
149
  const tempDirectory = (0, fs_1.mkdtempSync)((0, path_1.join)((0, os_1.tmpdir)(), 'nx-bun-lock-'));
145
150
  const tempLockfilePath = (0, path_1.join)(tempDirectory, 'bun.lockb');
@@ -7,6 +7,7 @@ const path = require("path");
7
7
  const logger_1 = require("../../../daemon/logger");
8
8
  const socket_utils_1 = require("../../../daemon/socket-utils");
9
9
  const consume_messages_from_socket_1 = require("../../../utils/consume-messages-from-socket");
10
+ const typescript_1 = require("../../../plugins/js/utils/typescript");
10
11
  const installation_directory_1 = require("../../../utils/installation-directory");
11
12
  const logger_2 = require("../../../utils/logger");
12
13
  const progress_topics_1 = require("../../../utils/progress-topics");
@@ -353,6 +354,9 @@ async function startPluginWorker(name) {
353
354
  };
354
355
  const ipcPath = (0, socket_utils_1.getPluginOsSocketPath)([process.pid, global.nxPluginWorkerCount++, performance.now()].join('-'));
355
356
  const worker = (0, child_process_1.spawn)(process.execPath, [
357
+ // Spawn the worker with the same resolve conditions Nx uses for plugin
358
+ // entries so the plugin's transitive workspace imports resolve to source.
359
+ ...(0, typescript_1.getPluginResolveConditionNodeArgs)(),
356
360
  ...(isWorkerTypescript ? ['--require', 'ts-node/register'] : []),
357
361
  workerPath,
358
362
  ipcPath,
@@ -25,6 +25,10 @@ function registerPluginTSTranspiler() {
25
25
  if (exports.unregisterPluginTSTranspiler !== null) {
26
26
  return;
27
27
  }
28
+ // Align Node's runtime resolution with the source-first plugin entry so the
29
+ // plugin's transitive workspace imports don't fall through to unbuilt `dist`.
30
+ // A no-op inside a plugin worker/daemon already spawned with `--conditions`.
31
+ (0, register_1.ensureResolveConditionsInjected)((0, typescript_1.getRootTsConfigResolveExportsConditions)(workspace_root_1.workspaceRoot));
28
32
  if ((0, register_1.isNativeStripPreferred)()) {
29
33
  // Native strip handles `.ts` syntax but doesn't rewrite NodeNext-style
30
34
  // `.js` relative specifiers to their `.ts` sources. Patch the CJS resolver
@@ -4,16 +4,13 @@ import { ProjectConfiguration } from '../../../config/workspace-json-project-jso
4
4
  * reference. `RootRef` carries the referenced project's root (resolved
5
5
  * via nameMap lookup); `UsageRef` carries the raw written name (for
6
6
  * forward refs, promoted to `RootRef` in place when the name is
7
- * identified). `parent` + `key` let the final pass write the resolved
8
- * name back; `targetPart` preserves the `:target` suffix from
7
+ * identified). `targetPart` preserves the `:target` suffix from
9
8
  * `dependsOn` strings.
10
9
  */
11
10
  export declare abstract class NameRef {
12
11
  value: string;
13
- parent: unknown;
14
- key: string | undefined;
15
12
  targetPart: string | undefined;
16
- constructor(value: string, parent: unknown, key: string | undefined, targetPart: string | undefined);
13
+ constructor(value: string, targetPart: string | undefined);
17
14
  }
18
15
  export declare class RootRef extends NameRef {
19
16
  }
@@ -27,15 +24,15 @@ export declare function isUsageRef(value: unknown): value is UsageRef;
27
24
  * then resolves them after all merging is done.
28
25
  *
29
26
  * Tracking by array position breaks once `'...'` spreads shuffle indices,
30
- * so each ref becomes a sentinel object. Arrays spread-merge by pushing
31
- * element references, so sentinel identity survives any downstream
32
- * merges the final pass walks a flat registry and writes the resolved
33
- * name back through each sentinel's `parent` back-reference. Orphaned
34
- * sentinels (from arrays dropped by a full-replace) write harmlessly.
27
+ * so each ref becomes a sentinel object. Merges copy sentinels by
28
+ * reference one sentinel can end up in many arrays (e.g. a pattern
29
+ * target's dependsOn applied to every matching target) so the final
30
+ * pass sweeps the merged rootMap and resolves every sentinel where it
31
+ * actually sits. Sentinels in arrays dropped by a full-replace are never
32
+ * visited and vanish with their array.
35
33
  */
36
34
  export declare class ProjectNameInNodePropsManager {
37
35
  private getNameMap;
38
- private allRefs;
39
36
  private pendingByName;
40
37
  constructor(getNameMap?: () => Record<string, ProjectConfiguration>);
41
38
  registerNameRefs(pluginResultProjects?: Record<string, Omit<ProjectConfiguration, 'root'> & Partial<ProjectConfiguration>>): void;
@@ -45,6 +42,6 @@ export declare class ProjectNameInNodePropsManager {
45
42
  private createRef;
46
43
  identifyProjectWithRoot(root: string, name: string): void;
47
44
  applySubstitutions(rootMap: Record<string, ProjectConfiguration>): void;
45
+ private substituteInArray;
48
46
  private resolveFinalName;
49
- private writeReplacement;
50
47
  }
@@ -11,15 +11,12 @@ const split_target_1 = require("../../../utils/split-target");
11
11
  * reference. `RootRef` carries the referenced project's root (resolved
12
12
  * via nameMap lookup); `UsageRef` carries the raw written name (for
13
13
  * forward refs, promoted to `RootRef` in place when the name is
14
- * identified). `parent` + `key` let the final pass write the resolved
15
- * name back; `targetPart` preserves the `:target` suffix from
14
+ * identified). `targetPart` preserves the `:target` suffix from
16
15
  * `dependsOn` strings.
17
16
  */
18
17
  class NameRef {
19
- constructor(value, parent, key, targetPart) {
18
+ constructor(value, targetPart) {
20
19
  this.value = value;
21
- this.parent = parent;
22
- this.key = key;
23
20
  this.targetPart = targetPart;
24
21
  }
25
22
  }
@@ -44,15 +41,15 @@ function isUsageRef(value) {
44
41
  * then resolves them after all merging is done.
45
42
  *
46
43
  * Tracking by array position breaks once `'...'` spreads shuffle indices,
47
- * so each ref becomes a sentinel object. Arrays spread-merge by pushing
48
- * element references, so sentinel identity survives any downstream
49
- * merges the final pass walks a flat registry and writes the resolved
50
- * name back through each sentinel's `parent` back-reference. Orphaned
51
- * sentinels (from arrays dropped by a full-replace) write harmlessly.
44
+ * so each ref becomes a sentinel object. Merges copy sentinels by
45
+ * reference one sentinel can end up in many arrays (e.g. a pattern
46
+ * target's dependsOn applied to every matching target) so the final
47
+ * pass sweeps the merged rootMap and resolves every sentinel where it
48
+ * actually sits. Sentinels in arrays dropped by a full-replace are never
49
+ * visited and vanish with their array.
52
50
  */
53
51
  class ProjectNameInNodePropsManager {
54
52
  constructor(getNameMap) {
55
- this.allRefs = new Set();
56
53
  this.pendingByName = new Map();
57
54
  this.getNameMap = getNameMap ?? (() => ({}));
58
55
  }
@@ -82,26 +79,20 @@ class ProjectNameInNodePropsManager {
82
79
  processInputs(inputs) {
83
80
  for (let i = 0; i < inputs.length; i++) {
84
81
  const entry = inputs[i];
85
- // Existing sentinel: spread merges may have copied it out of its
86
- // original array, so rebind parent to this one.
87
- if (isNameRef(entry)) {
88
- entry.parent = inputs;
82
+ if (isNameRef(entry))
89
83
  continue;
90
- }
91
84
  if (!entry || typeof entry !== 'object')
92
85
  continue;
93
86
  if (!('projects' in entry))
94
87
  continue;
95
88
  const element = entry;
96
89
  const projects = element.projects;
97
- if (isNameRef(projects)) {
98
- // Object-parent sentinel — element identity is stable across spread.
90
+ if (isNameRef(projects))
99
91
  continue;
100
- }
101
92
  if (typeof projects === 'string') {
102
93
  if (projects === 'self' || projects === 'dependencies')
103
94
  continue;
104
- element.projects = this.createRef(projects, element, 'projects');
95
+ element.projects = this.createRef(projects);
105
96
  }
106
97
  else if (Array.isArray(projects)) {
107
98
  this.processProjectsArray(projects);
@@ -111,12 +102,8 @@ class ProjectNameInNodePropsManager {
111
102
  processDependsOn(dependsOn, ownerTargets, ownerName) {
112
103
  for (let i = 0; i < dependsOn.length; i++) {
113
104
  const dep = dependsOn[i];
114
- // Existing sentinel: rebind parent to this array in case a spread
115
- // merge copied it out of its original.
116
- if (isNameRef(dep)) {
117
- dep.parent = dependsOn;
105
+ if (isNameRef(dep))
118
106
  continue;
119
- }
120
107
  if (typeof dep === 'string') {
121
108
  // `^target` and same-project targets aren't cross-project refs.
122
109
  if (dep.startsWith('^') || (ownerTargets && dep in ownerTargets)) {
@@ -126,7 +113,7 @@ class ProjectNameInNodePropsManager {
126
113
  if (rest.length === 0)
127
114
  continue;
128
115
  const targetPart = rest.join(':');
129
- dependsOn[i] = this.createRef(maybeProject, dependsOn, undefined, targetPart);
116
+ dependsOn[i] = this.createRef(maybeProject, targetPart);
130
117
  continue;
131
118
  }
132
119
  if (!dep || typeof dep !== 'object' || !('projects' in dep))
@@ -142,7 +129,7 @@ class ProjectNameInNodePropsManager {
142
129
  projects === 'dependencies') {
143
130
  continue;
144
131
  }
145
- element.projects = this.createRef(projects, element, 'projects');
132
+ element.projects = this.createRef(projects);
146
133
  }
147
134
  else if (Array.isArray(projects)) {
148
135
  this.processProjectsArray(projects);
@@ -152,24 +139,21 @@ class ProjectNameInNodePropsManager {
152
139
  processProjectsArray(projects) {
153
140
  for (let j = 0; j < projects.length; j++) {
154
141
  const name = projects[j];
155
- if (isNameRef(name)) {
156
- name.parent = projects;
142
+ if (isNameRef(name))
157
143
  continue;
158
- }
159
144
  if (typeof name !== 'string')
160
145
  continue;
161
146
  if ((0, globs_1.isGlobPattern)(name))
162
147
  continue;
163
- projects[j] = this.createRef(name, projects, undefined);
148
+ projects[j] = this.createRef(name);
164
149
  }
165
150
  }
166
151
  // Builds a sentinel and registers it.
167
- createRef(referencedName, parent, key, targetPart) {
152
+ createRef(referencedName, targetPart) {
168
153
  const referencedRoot = this.getNameMap()[referencedName]?.root;
169
154
  const ref = referencedRoot !== undefined
170
- ? new RootRef(referencedRoot, parent, key, targetPart)
171
- : new UsageRef(referencedName, parent, key, targetPart);
172
- this.allRefs.add(ref);
155
+ ? new RootRef(referencedRoot, targetPart)
156
+ : new UsageRef(referencedName, targetPart);
173
157
  if (ref instanceof UsageRef) {
174
158
  let set = this.pendingByName.get(referencedName);
175
159
  if (!set) {
@@ -195,25 +179,72 @@ class ProjectNameInNodePropsManager {
195
179
  ref.value = root;
196
180
  }
197
181
  }
198
- // Writes each sentinel's current resolved name back into its owning slot.
199
- // Called once after all plugin results have been merged.
182
+ // Resolves every sentinel in the merged rootMap in place. Sweeping the
183
+ // final config (rather than writing through back-references held by the
184
+ // sentinels) covers sentinels that merges copied into arrays other than
185
+ // the one they were created in, e.g. a pattern target's dependsOn applied
186
+ // to every matching target. Called once after all plugin results have
187
+ // been merged.
200
188
  applySubstitutions(rootMap) {
201
189
  const nameByRoot = {};
202
190
  for (const root in rootMap) {
203
191
  nameByRoot[root] = rootMap[root]?.name;
204
192
  }
205
- for (const ref of this.allRefs) {
206
- const finalName = this.resolveFinalName(ref, nameByRoot);
207
- if (finalName === undefined)
208
- continue;
209
- const replacement = ref.targetPart !== undefined
210
- ? `${finalName}:${ref.targetPart}`
211
- : finalName;
212
- this.writeReplacement(ref, replacement);
193
+ for (const root in rootMap) {
194
+ const targets = rootMap[root]?.targets;
195
+ if (!targets)
196
+ continue;
197
+ for (const targetName in targets) {
198
+ const targetConfig = targets[targetName];
199
+ if (!targetConfig || typeof targetConfig !== 'object')
200
+ continue;
201
+ if (Array.isArray(targetConfig.inputs)) {
202
+ this.substituteInArray(targetConfig.inputs, nameByRoot);
203
+ }
204
+ if (Array.isArray(targetConfig.dependsOn)) {
205
+ this.substituteInArray(targetConfig.dependsOn, nameByRoot);
206
+ }
207
+ }
213
208
  }
214
- this.allRefs.clear();
215
209
  this.pendingByName.clear();
216
210
  }
211
+ substituteInArray(entries, nameByRoot) {
212
+ for (let i = 0; i < entries.length; i++) {
213
+ const entry = entries[i];
214
+ if (isNameRef(entry)) {
215
+ const finalName = this.resolveFinalName(entry, nameByRoot);
216
+ if (finalName !== undefined) {
217
+ entries[i] =
218
+ entry.targetPart !== undefined
219
+ ? `${finalName}:${entry.targetPart}`
220
+ : finalName;
221
+ }
222
+ continue;
223
+ }
224
+ if (!entry || typeof entry !== 'object' || !('projects' in entry)) {
225
+ continue;
226
+ }
227
+ const element = entry;
228
+ if (isNameRef(element.projects)) {
229
+ const finalName = this.resolveFinalName(element.projects, nameByRoot);
230
+ if (finalName !== undefined) {
231
+ element.projects = finalName;
232
+ }
233
+ }
234
+ else if (Array.isArray(element.projects)) {
235
+ const projects = element.projects;
236
+ for (let j = 0; j < projects.length; j++) {
237
+ const name = projects[j];
238
+ if (!isNameRef(name))
239
+ continue;
240
+ const finalName = this.resolveFinalName(name, nameByRoot);
241
+ if (finalName !== undefined) {
242
+ projects[j] = finalName;
243
+ }
244
+ }
245
+ }
246
+ }
247
+ }
217
248
  resolveFinalName(ref, nameByRoot) {
218
249
  if (ref instanceof RootRef) {
219
250
  return nameByRoot[ref.value];
@@ -221,20 +252,5 @@ class ProjectNameInNodePropsManager {
221
252
  // Unpromoted forward ref — best effort, fall back to the written name.
222
253
  return this.getNameMap()[ref.value]?.name ?? ref.value;
223
254
  }
224
- writeReplacement(ref, replacement) {
225
- const parent = ref.parent;
226
- if (Array.isArray(parent)) {
227
- // One sentinel may appear at multiple indices (e.g. `[..., ...]`
228
- // pushed the same reference twice via spread), so replace all.
229
- for (let i = 0; i < parent.length; i++) {
230
- if (parent[i] === ref)
231
- parent[i] = replacement;
232
- }
233
- return;
234
- }
235
- if (parent && typeof parent === 'object' && ref.key !== undefined) {
236
- parent[ref.key] = replacement;
237
- }
238
- }
239
255
  }
240
256
  exports.ProjectNameInNodePropsManager = ProjectNameInNodePropsManager;
@@ -34,7 +34,7 @@ export declare class ProjectNodesManager {
34
34
  *
35
35
  * Pass a different `mergedRootMap` for the default-plugin intermediate
36
36
  * pass, then call again with `this.rootMap` after it's applied so
37
- * sentinel parents rebind onto the final arrays.
37
+ * name refs introduced by that merge are sentinelized as well.
38
38
  */
39
39
  registerNameRefs(pluginResultProjects?: Record<string, Omit<ProjectConfiguration, 'root'> & Partial<ProjectConfiguration>>, mergedRootMap?: Record<string, ProjectConfiguration>): void;
40
40
  /**
@@ -263,7 +263,7 @@ class ProjectNodesManager {
263
263
  *
264
264
  * Pass a different `mergedRootMap` for the default-plugin intermediate
265
265
  * pass, then call again with `this.rootMap` after it's applied so
266
- * sentinel parents rebind onto the final arrays.
266
+ * name refs introduced by that merge are sentinelized as well.
267
267
  */
268
268
  registerNameRefs(pluginResultProjects, mergedRootMap = this.rootMap) {
269
269
  if (!pluginResultProjects)
@@ -225,12 +225,10 @@ function mergeCreateNodesResults(specifiedResults, defaultResults, nxJsonConfigu
225
225
  }));
226
226
  }
227
227
  }
228
- // The intermediate apply may have rebuilt dependsOn / inputs arrays
229
- // via spread merges, leaving sentinels inserted against the
230
- // intermediate rootMap pointing at now-orphaned arrays. Re-walking
231
- // the final merged targets rebinds each encountered sentinel's
232
- // `parent` to the current array (see
233
- // ProjectNameInNodePropsManager#processInputs / processDependsOn).
228
+ // The intermediate apply may have rebuilt dependsOn / inputs arrays via
229
+ // spread merges, introducing name-ref strings that weren't visible in any
230
+ // single plugin result. Re-walking the final merged targets sentinelizes
231
+ // them so the final substitution sweep resolves them too.
234
232
  nodesManager.registerNameRefs(intermediateDefaultRootMap);
235
233
  // Overlay default-plugin attribution onto the main source maps using
236
234
  // "only fill missing" semantics. Any key already present in
@@ -29,15 +29,20 @@ export interface Timespan {
29
29
  /** `parallelism: false` tasks running during the slice (each one holds every slot). */
30
30
  nonParallel: number;
31
31
  }
32
+ /**
33
+ * A task and how long it ran (ms). The unit the report's task lists are built from —
34
+ * shared with the renderers so the producer and the formatters can't drift.
35
+ */
36
+ export interface TaskDurationRow {
37
+ id: string;
38
+ duration: number;
39
+ }
32
40
  export interface PerformanceSummary {
33
41
  runDuration: number;
34
42
  criticalPathDuration: number;
35
43
  criticalPathTaskCount: number;
36
44
  /** Longest critical-path tasks that ran (desc, capped at a few), cache hits excluded; empty when the path was fully cached. */
37
- criticalPathTop: Array<{
38
- id: string;
39
- duration: number;
40
- }>;
45
+ criticalPathTop: TaskDurationRow[];
41
46
  /** Ids of tasks that failed (slowest first), for the GitHub Actions summary's failed-tasks list. Continuous tasks and tasks without a complete window are excluded. */
42
47
  failedTasks: string[];
43
48
  /** runDuration − criticalPathDuration. */
@@ -60,6 +65,8 @@ export interface PerformanceSummary {
60
65
  cacheableCount: number;
61
66
  cacheSkipped: boolean;
62
67
  remoteCacheEnabled: boolean;
68
+ /** The workspace opted out of Nx Cloud (`neverConnectToCloud` / NX_NO_CLOUD) — never recommend it. */
69
+ cloudOptedOut: boolean;
63
70
  }
64
71
  /** Construction-time inputs for {@link PerformanceLifeCycle}. */
65
72
  export interface PerformanceLifeCycleOptions {