nx 23.3.0 → 24.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 (114) hide show
  1. package/README.md +3 -6
  2. package/dist/nx-cloud-internals.d.ts +1 -0
  3. package/dist/nx-cloud-internals.js +7 -1
  4. package/dist/src/adapter/ngcli-adapter.js +1 -0
  5. package/dist/src/command-line/configure-ai-agents/configure-ai-agents.js +0 -12
  6. package/dist/src/command-line/graph/graph.js +3 -5
  7. package/dist/src/command-line/init/command-object.js +0 -12
  8. package/dist/src/command-line/init/init-v2.js +0 -2
  9. package/dist/src/command-line/migrate/agentic/close-agent-session.d.ts +1 -1
  10. package/dist/src/command-line/migrate/agentic/close-agent-session.js +4 -3
  11. package/dist/src/command-line/migrate/agentic/master/spawn-master.js +76 -15
  12. package/dist/src/command-line/migrate/execute-migration.d.ts +2 -2
  13. package/dist/src/command-line/migrate/execute-migration.js +18 -10
  14. package/dist/src/command-line/migrate/migrate-commits.d.ts +4 -1
  15. package/dist/src/command-line/migrate/migrate-commits.js +10 -3
  16. package/dist/src/command-line/migrate/migrate.js +43 -6
  17. package/dist/src/command-line/migrate/run/agent-output.d.ts +1 -0
  18. package/dist/src/command-line/migrate/run/agent-output.js +5 -0
  19. package/dist/src/command-line/migrate/run/broker.d.ts +14 -5
  20. package/dist/src/command-line/migrate/run/broker.js +68 -20
  21. package/dist/src/command-line/migrate/run/index.d.ts +4 -3
  22. package/dist/src/command-line/migrate/run/index.js +8 -1
  23. package/dist/src/command-line/migrate/run/orchestrator.js +72 -13
  24. package/dist/src/command-line/migrate/run/runbook.js +19 -0
  25. package/dist/src/command-line/migrate/run/state-machine.js +4 -1
  26. package/dist/src/command-line/migrate/run/util.d.ts +3 -0
  27. package/dist/src/command-line/migrate/run/util.js +7 -1
  28. package/dist/src/command-line/nx-cloud/connect/connect-to-nx-cloud.js +17 -12
  29. package/dist/src/command-line/release/utils/remote-release-clients/github.js +5 -6
  30. package/dist/src/command-line/release/utils/remote-release-clients/gitlab.js +5 -6
  31. package/dist/src/command-line/run/executor-utils.js +1 -0
  32. package/dist/src/command-line/run/run-one.d.ts +20 -1
  33. package/dist/src/command-line/run/run-one.js +153 -5
  34. package/dist/src/command-line/show/show-target/info.js +1 -1
  35. package/dist/src/command-line/show/show-target/utils.js +1 -0
  36. package/dist/src/command-line/watch/command-object.js +3 -22
  37. package/dist/src/command-line/watch/watch.d.ts +0 -7
  38. package/dist/src/command-line/watch/watch.js +1 -1
  39. package/dist/src/config/misc-interfaces.d.ts +12 -0
  40. package/dist/src/config/task-graph.d.ts +1 -1
  41. package/dist/src/config/workspace-json-project-json.d.ts +65 -0
  42. package/dist/src/core/graph/main.js +1 -1
  43. package/dist/src/daemon/client/client.d.ts +9 -0
  44. package/dist/src/daemon/client/client.js +89 -37
  45. package/dist/src/daemon/server/shutdown-utils.js +0 -3
  46. package/dist/src/daemon/socket-utils.d.ts +9 -4
  47. package/dist/src/daemon/socket-utils.js +12 -49
  48. package/dist/src/daemon/tmp-dir.d.ts +19 -14
  49. package/dist/src/daemon/tmp-dir.js +142 -354
  50. package/dist/src/devkit-exports.d.ts +1 -1
  51. package/dist/src/devkit-internals.d.ts +1 -1
  52. package/dist/src/devkit-internals.js +1 -0
  53. package/dist/src/hasher/check-task-files.js +4 -4
  54. package/dist/src/hasher/file-hasher.js +9 -7
  55. package/dist/src/hasher/hash-task.js +4 -0
  56. package/dist/src/hasher/native-task-hasher-impl.js +3 -1
  57. package/dist/src/migrations/update-24-0-0/find-custom-hashers.d.ts +3 -0
  58. package/dist/src/migrations/update-24-0-0/find-custom-hashers.js +100 -0
  59. package/dist/src/migrations/update-24-0-0/find-custom-hashers.md +63 -0
  60. package/dist/src/migrations/update-24-0-0/move-custom-hashers-to-inputs.md +145 -0
  61. package/dist/src/native/index.d.ts +180 -12
  62. package/dist/src/native/native-bindings.js +8 -0
  63. package/dist/src/native/nx.wasi-browser.js +1 -0
  64. package/dist/src/native/nx.wasi.cjs +1 -0
  65. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  66. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  67. package/dist/src/nx-cloud/update-manager.d.ts +7 -0
  68. package/dist/src/nx-cloud/update-manager.js +237 -50
  69. package/dist/src/project-graph/affected/affected-tasks.js +1 -0
  70. package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +7 -0
  71. package/dist/src/tasks-runner/create-task-graph.d.ts +8 -7
  72. package/dist/src/tasks-runner/create-task-graph.js +47 -26
  73. package/dist/src/tasks-runner/init-tasks-runner.js +13 -0
  74. package/dist/src/tasks-runner/life-cycle.d.ts +4 -1
  75. package/dist/src/tasks-runner/life-cycle.js +7 -0
  76. package/dist/src/tasks-runner/life-cycles/performance-analysis.d.ts +5 -2
  77. package/dist/src/tasks-runner/life-cycles/performance-analysis.js +13 -7
  78. package/dist/src/tasks-runner/life-cycles/performance-life-cycle.d.ts +4 -3
  79. package/dist/src/tasks-runner/life-cycles/performance-life-cycle.js +9 -2
  80. package/dist/src/tasks-runner/readiness/probes.d.ts +9 -0
  81. package/dist/src/tasks-runner/readiness/probes.js +62 -0
  82. package/dist/src/tasks-runner/readiness/ready-when.d.ts +38 -0
  83. package/dist/src/tasks-runner/readiness/ready-when.js +190 -0
  84. package/dist/src/tasks-runner/run-command.js +5 -4
  85. package/dist/src/tasks-runner/running-tasks/node-child-process.js +5 -4
  86. package/dist/src/tasks-runner/task-env.d.ts +10 -0
  87. package/dist/src/tasks-runner/task-env.js +26 -5
  88. package/dist/src/tasks-runner/task-graph-utils.d.ts +9 -6
  89. package/dist/src/tasks-runner/task-graph-utils.js +17 -12
  90. package/dist/src/tasks-runner/task-orchestrator.d.ts +37 -8
  91. package/dist/src/tasks-runner/task-orchestrator.js +460 -76
  92. package/dist/src/tasks-runner/tasks-schedule.d.ts +18 -1
  93. package/dist/src/tasks-runner/tasks-schedule.js +64 -6
  94. package/dist/src/tasks-runner/utils.d.ts +20 -5
  95. package/dist/src/tasks-runner/utils.js +70 -35
  96. package/dist/src/ultracache/overrides.js +4 -0
  97. package/dist/src/utils/child-process.js +1 -0
  98. package/dist/src/utils/git-utils.d.ts +6 -1
  99. package/dist/src/utils/git-utils.js +26 -5
  100. package/dist/src/utils/owned-private-dir.d.ts +1 -17
  101. package/dist/src/utils/owned-private-dir.js +10 -62
  102. package/dist/src/utils/perf-hooks.d.ts +1 -0
  103. package/dist/src/utils/plugins/core-plugins.js +1 -1
  104. package/dist/src/utils/spawn-without-terminal.d.ts +27 -0
  105. package/dist/src/utils/spawn-without-terminal.js +110 -0
  106. package/dist/src/utils/string-similarity.d.ts +38 -0
  107. package/dist/src/utils/string-similarity.js +108 -0
  108. package/migrations.json +7 -0
  109. package/package.json +11 -20
  110. package/readme-template.md +3 -3
  111. package/schemas/nx-schema.json +70 -0
  112. package/schemas/project-schema.json +67 -0
  113. package/dist/src/tasks-runner/legacy-depends-on-warning.d.ts +0 -18
  114. package/dist/src/tasks-runner/legacy-depends-on-warning.js +0 -109
@@ -2,16 +2,23 @@ import { DefaultTasksRunnerOptions } from './default-tasks-runner';
2
2
  import { Task, TaskGraph } from '../config/task-graph';
3
3
  import { ProjectGraph } from '../config/project-graph';
4
4
  import { ProjectConfiguration } from '../config/workspace-json-project-json';
5
+ import { TaskReadiness } from '../native';
5
6
  export interface Batch {
6
7
  id: string;
7
8
  executorName: string;
8
9
  taskGraph: TaskGraph;
9
10
  }
11
+ export interface TasksScheduleHooks {
12
+ readinessElsewhere?: (producerId: string) => TaskReadiness | null;
13
+ onReadinessHold?: (producerId: string) => void;
14
+ }
10
15
  export declare class TasksSchedule {
11
16
  private readonly projectGraph;
12
17
  private readonly projects;
13
18
  private readonly taskGraph;
14
19
  private readonly options;
20
+ private readonly fullTaskGraph;
21
+ private readonly hooks;
15
22
  private notScheduledTaskGraph;
16
23
  private reverseTaskDeps;
17
24
  private reverseProjectGraph;
@@ -19,14 +26,21 @@ export declare class TasksSchedule {
19
26
  private scheduledBatches;
20
27
  private scheduledTasks;
21
28
  private runningTasks;
29
+ private pendingStart;
22
30
  private completedTasks;
31
+ private readiness;
32
+ private probedProducers;
23
33
  private scheduleRequestsExecutionChain;
24
34
  private estimatedTaskTimings;
25
35
  private projectDependencies;
26
36
  private batchCounters;
27
- constructor(projectGraph: ProjectGraph, projects: Record<string, ProjectConfiguration>, taskGraph: TaskGraph, options: DefaultTasksRunnerOptions);
37
+ constructor(projectGraph: ProjectGraph, projects: Record<string, ProjectConfiguration>, taskGraph: TaskGraph, options: DefaultTasksRunnerOptions, fullTaskGraph?: TaskGraph, hooks?: TasksScheduleHooks);
28
38
  init(): Promise<void>;
29
39
  scheduleNextTasks(): Promise<void>;
40
+ markContinuousTaskStarted(taskId: string): void;
41
+ markReady(taskId: string): void;
42
+ markReadinessFailed(taskId: string): void;
43
+ markReadinessPending(taskId: string): void;
30
44
  hasTasks(): boolean;
31
45
  complete(taskIds: string[]): void;
32
46
  getAllScheduledTasks(): {
@@ -42,7 +56,10 @@ export declare class TasksSchedule {
42
56
  private scheduleBatches;
43
57
  private scheduleBatch;
44
58
  private processTaskForBatches;
59
+ private probedProducersOf;
60
+ private isProducerReady;
45
61
  private canBatchTaskBeScheduled;
62
+ private areContinuousDependenciesReady;
46
63
  private canBeScheduled;
47
64
  getEstimatedTaskTimings(): Record<string, number>;
48
65
  }
@@ -5,12 +5,15 @@ const utils_1 = require("./utils");
5
5
  const project_graph_utils_1 = require("../utils/project-graph-utils");
6
6
  const operators_1 = require("../project-graph/operators");
7
7
  const task_history_1 = require("../utils/task-history");
8
+ const ready_when_1 = require("./readiness/ready-when");
8
9
  class TasksSchedule {
9
- constructor(projectGraph, projects, taskGraph, options) {
10
+ constructor(projectGraph, projects, taskGraph, options, fullTaskGraph = taskGraph, hooks = {}) {
10
11
  this.projectGraph = projectGraph;
11
12
  this.projects = projects;
12
13
  this.taskGraph = taskGraph;
13
14
  this.options = options;
15
+ this.fullTaskGraph = fullTaskGraph;
16
+ this.hooks = hooks;
14
17
  this.notScheduledTaskGraph = this.taskGraph;
15
18
  this.reverseTaskDeps = (0, utils_1.calculateReverseDeps)(this.taskGraph);
16
19
  this.reverseProjectGraph = (0, operators_1.reverse)(this.projectGraph);
@@ -18,7 +21,13 @@ class TasksSchedule {
18
21
  this.scheduledBatches = [];
19
22
  this.scheduledTasks = [];
20
23
  this.runningTasks = new Set();
24
+ // Continuous tasks with ready edges, queued but not spawned; their
25
+ // dependents stay unreleased
26
+ this.pendingStart = new Set();
21
27
  this.completedTasks = new Set();
28
+ this.readiness = new Map();
29
+ // Read from the full graph: an agent worker's own graph has no edges
30
+ this.probedProducers = (0, ready_when_1.getReadyDependencies)(this.fullTaskGraph, this.projectGraph);
22
31
  this.scheduleRequestsExecutionChain = Promise.resolve();
23
32
  this.estimatedTaskTimings = {};
24
33
  this.projectDependencies = {};
@@ -38,6 +47,18 @@ class TasksSchedule {
38
47
  this.scheduleRequestsExecutionChain.then(() => this.scheduleTasks());
39
48
  await this.scheduleRequestsExecutionChain;
40
49
  }
50
+ markContinuousTaskStarted(taskId) {
51
+ this.pendingStart.delete(taskId);
52
+ }
53
+ markReady(taskId) {
54
+ this.readiness.set(taskId, 'ready');
55
+ }
56
+ markReadinessFailed(taskId) {
57
+ this.readiness.set(taskId, 'failed');
58
+ }
59
+ markReadinessPending(taskId) {
60
+ this.readiness.delete(taskId);
61
+ }
41
62
  hasTasks() {
42
63
  return (this.scheduledBatches.length +
43
64
  this.scheduledTasks.length +
@@ -116,6 +137,10 @@ class TasksSchedule {
116
137
  for (const taskId of taskIds) {
117
138
  this.scheduledTasks.push(taskId);
118
139
  this.runningTasks.add(taskId);
140
+ const task = this.taskGraph.tasks[taskId];
141
+ if (task.continuous && this.probedProducersOf(task).length > 0) {
142
+ this.pendingStart.add(taskId);
143
+ }
119
144
  }
120
145
  this.sortScheduledTasks();
121
146
  }
@@ -182,6 +207,15 @@ class TasksSchedule {
182
207
  if (!this.canBatchTaskBeScheduled(task, batches[rootExecutorName])) {
183
208
  return;
184
209
  }
210
+ // A batch never waits, so a task joins one once its probed producers are
211
+ // ready. A continuous task that something waits on, or that declares a
212
+ // probe, runs alone: only that path records its start and readiness
213
+ if ((task.continuous &&
214
+ (this.reverseTaskDeps[task.id].length > 0 ||
215
+ (0, ready_when_1.getReadyWhenConfig)(task, this.projectGraph) != null)) ||
216
+ this.probedProducersOf(task).some((id) => !this.isProducerReady(id))) {
217
+ return;
218
+ }
185
219
  const { batchImplementationFactory, preferBatch } = (0, utils_1.getExecutorForTask)(task, this.projects);
186
220
  const executorName = (0, utils_1.getExecutorNameForTask)(task, this.projectGraph);
187
221
  if (rootExecutorName !== executorName) {
@@ -222,17 +256,41 @@ class TasksSchedule {
222
256
  await this.processTaskForBatches(batches, depTask, rootExecutorName, false, visitedInBatch);
223
257
  }
224
258
  }
259
+ probedProducersOf(task) {
260
+ return this.probedProducers[task.id] ?? [];
261
+ }
262
+ isProducerReady(producerId) {
263
+ if (this.taskGraph.tasks[producerId]) {
264
+ return this.readiness.get(producerId) === 'ready';
265
+ }
266
+ return this.hooks.readinessElsewhere?.(producerId) === 1 /* TaskReadiness.Ready */;
267
+ }
225
268
  canBatchTaskBeScheduled(task, batchTaskGraph) {
226
269
  // task self needs to support parallelism (undefined defaults to parallel)
227
- // all deps have either completed or belong to the same batch
270
+ // all deps have either completed or belong to the same batch, and every
271
+ // continuous dep has started, and is ready where the edge asks for it
228
272
  return (task.parallelism !== false &&
229
- this.taskGraph.dependencies[task.id].every((id) => this.completedTasks.has(id) || !!batchTaskGraph?.tasks[id]));
273
+ this.taskGraph.dependencies[task.id].every(({ id }) => this.completedTasks.has(id) || !!batchTaskGraph?.tasks[id]) &&
274
+ this.areContinuousDependenciesReady(task.id));
275
+ }
276
+ // A producer outside this task graph is polled by the task itself
277
+ areContinuousDependenciesReady(taskId) {
278
+ const started = this.taskGraph.continuousDependencies[taskId].every(({ id }) => this.runningTasks.has(id) && !this.pendingStart.has(id));
279
+ if (!started) {
280
+ return false;
281
+ }
282
+ const pendingProducer = this.probedProducersOf(this.taskGraph.tasks[taskId]).find((id) => this.taskGraph.tasks[id] && !this.readiness.has(id));
283
+ if (pendingProducer) {
284
+ this.hooks.onReadinessHold?.(pendingProducer);
285
+ return false;
286
+ }
287
+ return true;
230
288
  }
231
289
  canBeScheduled(taskId) {
232
- const hasDependenciesCompleted = this.taskGraph.dependencies[taskId].every((id) => this.completedTasks.has(id));
233
- const hasContinuousDependenciesStarted = this.taskGraph.continuousDependencies[taskId].every((id) => this.runningTasks.has(id));
290
+ const hasDependenciesCompleted = this.taskGraph.dependencies[taskId].every(({ id }) => this.completedTasks.has(id));
291
+ const continuousDependenciesReady = this.areContinuousDependenciesReady(taskId);
234
292
  // if dependencies have not completed, cannot schedule
235
- if (!hasDependenciesCompleted || !hasContinuousDependenciesStarted) {
293
+ if (!hasDependenciesCompleted || !continuousDependenciesReady) {
236
294
  return false;
237
295
  }
238
296
  // if there are no running tasks, can schedule anything
@@ -2,7 +2,6 @@ import { CustomHasher, ExecutorConfig } from '../config/misc-interfaces';
2
2
  import { ProjectGraph, ProjectGraphProjectNode } from '../config/project-graph';
3
3
  import { Task, TaskGraph } from '../config/task-graph';
4
4
  import { ProjectConfiguration, TargetConfiguration, TargetDependencyConfig } from '../config/workspace-json-project-json';
5
- import { LegacyDependsOnLocation } from './legacy-depends-on-warning';
6
5
  export type NormalizedTargetDependencyConfig = TargetDependencyConfig & {
7
6
  projects: string[];
8
7
  };
@@ -10,9 +9,8 @@ export declare function getDependencyConfigs({ project, target }: {
10
9
  project: string;
11
10
  target: string;
12
11
  }, extraTargetDependencies: Record<string, (TargetDependencyConfig | string)[]>, projectGraph: ProjectGraph, allTargetNames: string[]): NormalizedTargetDependencyConfig[] | undefined;
13
- export type DependsOnEntryLocation = LegacyDependsOnLocation;
14
- export declare function normalizeDependencyConfigDefinition(definition: string | TargetDependencyConfig, currentProject: string, graph: ProjectGraph, allTargetNames: string[], location?: DependsOnEntryLocation): NormalizedTargetDependencyConfig[];
15
- export declare function normalizeDependencyConfigProjects(dependencyConfig: TargetDependencyConfig, currentProject: string, graph: ProjectGraph, location?: DependsOnEntryLocation): NormalizedTargetDependencyConfig;
12
+ export declare function normalizeDependencyConfigDefinition(definition: string | TargetDependencyConfig, currentProject: string, graph: ProjectGraph, allTargetNames: string[]): NormalizedTargetDependencyConfig[];
13
+ export declare function normalizeDependencyConfigProjects(dependencyConfig: TargetDependencyConfig, currentProject: string, graph: ProjectGraph): NormalizedTargetDependencyConfig;
16
14
  export declare function expandDependencyConfigSyntaxSugar(dependencyConfigString: string | TargetDependencyConfig, graph: ProjectGraph, currentProject?: string): TargetDependencyConfig;
17
15
  export declare function expandWildcardTargetConfiguration(dependencyConfig: NormalizedTargetDependencyConfig, allTargetNames: string[]): NormalizedTargetDependencyConfig[];
18
16
  export declare function readProjectAndTargetFromTargetString(targetString: string, projects: Record<string, ProjectGraphProjectNode>, currentProject?: string): {
@@ -20,7 +18,7 @@ export declare function readProjectAndTargetFromTargetString(targetString: strin
20
18
  target: string;
21
19
  };
22
20
  export declare function getOutputs(p: Record<string, ProjectGraphProjectNode>, target: Task['target'], overrides: Task['overrides']): string[];
23
- export declare function normalizeTargetDependencyWithStringProjects(dependencyConfig: TargetDependencyConfig, currentProject?: string, location?: DependsOnEntryLocation): Omit<TargetDependencyConfig, 'projects'> & {
21
+ export declare function normalizeTargetDependencyWithStringProjects(dependencyConfig: TargetDependencyConfig): Omit<TargetDependencyConfig, 'projects'> & {
24
22
  projects?: string[];
25
23
  };
26
24
  export declare function validateOutputs(outputs: string[]): void;
@@ -49,6 +47,8 @@ export declare function getExecutorForTask(task: Task, projects: Record<string,
49
47
  isNxExecutor: boolean;
50
48
  };
51
49
  export declare function getCustomHasher(task: Task, projects: Record<string, ProjectConfiguration>): CustomHasher | null;
50
+ /** Test-only: forget which executors have been warned about. */
51
+ export declare function resetCustomHasherDeprecationWarnings(): void;
52
52
  /**
53
53
  * `taskGraph` pruned to `required`, which must already hold its dependencies.
54
54
  * Unknown ids are ignored: a sync generator can remove a task after selection.
@@ -64,3 +64,18 @@ export declare function shouldStreamOutput(task: Task, initiatingProject: string
64
64
  export declare function isCacheableTask(task: Task): boolean;
65
65
  export declare function unparse(options: Object): string[];
66
66
  export declare function createTaskId(project: string, target: string, configuration: string | undefined): string;
67
+ /**
68
+ * Distinguishes two invocations of the same task for the recursive invocation
69
+ * detector. The task id alone would report `nx run app:build --flag` invoked
70
+ * from within `app:build` as a loop, so the detector pairs the id with this
71
+ * hash and only treats an exact repeat as an immediate loop.
72
+ *
73
+ * The hash cannot decide recursion on its own. Arguments that accumulate
74
+ * through forwarding (`app:build` -> `app:build hello` -> `app:build hello
75
+ * hello`) hash differently on every hop, so the tracker also bounds how many
76
+ * ancestors may be running one task id.
77
+ *
78
+ * `__overrides_unparsed__` is dropped, matching how the task hasher treats it:
79
+ * it restates the other keys as raw argv, down to flag order and spelling.
80
+ */
81
+ export declare function hashTaskOverrides(task: Task): string;
@@ -17,6 +17,7 @@ exports.getExecutorNameForTask = getExecutorNameForTask;
17
17
  exports.expandInitiatingTasksThroughNoop = expandInitiatingTasksThroughNoop;
18
18
  exports.getExecutorForTask = getExecutorForTask;
19
19
  exports.getCustomHasher = getCustomHasher;
20
+ exports.resetCustomHasherDeprecationWarnings = resetCustomHasherDeprecationWarnings;
20
21
  exports.pruneToSelectedTasks = pruneToSelectedTasks;
21
22
  exports.removeTasksFromTaskGraph = removeTasksFromTaskGraph;
22
23
  exports.calculateReverseDeps = calculateReverseDeps;
@@ -28,37 +29,35 @@ exports.shouldStreamOutput = shouldStreamOutput;
28
29
  exports.isCacheableTask = isCacheableTask;
29
30
  exports.unparse = unparse;
30
31
  exports.createTaskId = createTaskId;
32
+ exports.hashTaskOverrides = hashTaskOverrides;
31
33
  const minimatch_1 = require("minimatch");
32
34
  const node_path_1 = require("node:path");
33
35
  const posix_1 = require("node:path/posix");
34
36
  const executor_utils_1 = require("../command-line/run/executor-utils");
37
+ const file_hasher_1 = require("../hasher/file-hasher");
35
38
  const native_1 = require("../native");
36
39
  const fileutils_1 = require("../utils/fileutils");
37
40
  const find_matching_projects_1 = require("../utils/find-matching-projects");
38
- const legacy_depends_on_warning_1 = require("./legacy-depends-on-warning");
39
41
  const globs_1 = require("../utils/globs");
40
42
  const long_running_target_1 = require("../utils/long-running-target");
43
+ const output_1 = require("../utils/output");
41
44
  const path_1 = require("../utils/path");
42
45
  const serialize_overrides_into_command_line_1 = require("../utils/serialize-overrides-into-command-line");
43
46
  const split_target_1 = require("../utils/split-target");
44
47
  const workspace_root_1 = require("../utils/workspace-root");
45
48
  const is_tui_enabled_1 = require("./is-tui-enabled");
46
49
  function getDependencyConfigs({ project, target }, extraTargetDependencies, projectGraph, allTargetNames) {
47
- const legacyViolations = [];
48
- const dependencyConfigs = (projectGraph.nodes[project].data?.targets[target]?.dependsOn ??
50
+ return (projectGraph.nodes[project].data?.targets[target]?.dependsOn ??
49
51
  // This is passed into `run-command` from programmatic invocations
50
52
  extraTargetDependencies[target] ??
51
- []).flatMap((config, index) => normalizeDependencyConfigDefinition(config, project, projectGraph, allTargetNames, { ownerTarget: target, index, legacyViolations }));
52
- if (legacyViolations.length) {
53
- (0, legacy_depends_on_warning_1.flushLegacyDependsOnViolations)(project, target, legacyViolations, projectGraph.nodes[project]?.data?.root);
54
- }
55
- return dependencyConfigs;
53
+ []).flatMap((config) => normalizeDependencyConfigDefinition(config, project, projectGraph, allTargetNames));
56
54
  }
57
- function normalizeDependencyConfigDefinition(definition, currentProject, graph, allTargetNames, location) {
58
- return expandWildcardTargetConfiguration(normalizeDependencyConfigProjects(expandDependencyConfigSyntaxSugar(definition, graph, currentProject), currentProject, graph, location), allTargetNames);
55
+ function normalizeDependencyConfigDefinition(definition, currentProject, graph, allTargetNames) {
56
+ return expandWildcardTargetConfiguration(normalizeDependencyConfigProjects(expandDependencyConfigSyntaxSugar(definition, graph, currentProject), currentProject, graph), allTargetNames);
59
57
  }
60
- function normalizeDependencyConfigProjects(dependencyConfig, currentProject, graph, location) {
61
- const noStringConfig = normalizeTargetDependencyWithStringProjects(dependencyConfig, currentProject, location);
58
+ function normalizeDependencyConfigProjects(dependencyConfig, currentProject, graph) {
59
+ assertNotLegacyProjectsString(dependencyConfig, currentProject, graph);
60
+ const noStringConfig = normalizeTargetDependencyWithStringProjects(dependencyConfig);
62
61
  if (noStringConfig.projects) {
63
62
  dependencyConfig.projects = (0, find_matching_projects_1.findMatchingProjects)(noStringConfig.projects, graph.nodes);
64
63
  }
@@ -67,6 +66,13 @@ function normalizeDependencyConfigProjects(dependencyConfig, currentProject, gra
67
66
  }
68
67
  return dependencyConfig;
69
68
  }
69
+ function assertNotLegacyProjectsString(dependencyConfig, currentProject, graph) {
70
+ const projects = dependencyConfig.projects;
71
+ if ((projects === 'self' || projects === 'dependencies') &&
72
+ !graph.nodes[projects]) {
73
+ throw new Error(`${currentProject} has a dependsOn entry using projects: '${projects}', which is no longer supported: ${JSON.stringify(dependencyConfig)}\nPlease run "nx repair" to repair your configuration`);
74
+ }
75
+ }
70
76
  function expandDependencyConfigSyntaxSugar(dependencyConfigString, graph, currentProject) {
71
77
  if (typeof dependencyConfigString !== 'string') {
72
78
  return dependencyConfigString;
@@ -133,23 +139,9 @@ function readProjectAndTargetFromTargetString(targetString, projects, currentPro
133
139
  function getOutputs(p, target, overrides) {
134
140
  return getOutputsForTargetAndConfiguration(target, overrides, p[target.project]);
135
141
  }
136
- function normalizeTargetDependencyWithStringProjects(dependencyConfig, currentProject, location) {
142
+ function normalizeTargetDependencyWithStringProjects(dependencyConfig) {
137
143
  if (typeof dependencyConfig.projects === 'string') {
138
- // TODO(v24): Remove the `self` / `dependencies` magic-string shim.
139
- // The v16 `update-depends-on-to-tokens` migration already rewrites
140
- // these to the modern shape, and `nx repair` will re-run it on demand.
141
- if (dependencyConfig.projects === 'self') {
142
- (0, legacy_depends_on_warning_1.warnLegacyDependsOnMagicString)(currentProject, dependencyConfig, location);
143
- delete dependencyConfig.projects;
144
- }
145
- else if (dependencyConfig.projects === 'dependencies') {
146
- (0, legacy_depends_on_warning_1.warnLegacyDependsOnMagicString)(currentProject, dependencyConfig, location);
147
- dependencyConfig.dependencies = true;
148
- delete dependencyConfig.projects;
149
- }
150
- else {
151
- dependencyConfig.projects = [dependencyConfig.projects];
152
- }
144
+ dependencyConfig.projects = [dependencyConfig.projects];
153
145
  }
154
146
  return dependencyConfig;
155
147
  }
@@ -324,10 +316,10 @@ function expandInitiatingTasksThroughNoop(initiatingTasks, taskGraph, projectGra
324
316
  continue;
325
317
  if (getExecutorNameForTask(task, projectGraph) === 'nx:noop') {
326
318
  for (const dep of taskGraph.dependencies[taskId] ?? []) {
327
- queue.push(dep);
319
+ queue.push(dep.id);
328
320
  }
329
321
  for (const dep of taskGraph.continuousDependencies[taskId] ?? []) {
330
- queue.push(dep);
322
+ queue.push(dep.id);
331
323
  }
332
324
  }
333
325
  else {
@@ -341,9 +333,33 @@ function getExecutorForTask(task, projects) {
341
333
  const [nodeModule, executorName] = (0, executor_utils_1.parseExecutor)(executor);
342
334
  return (0, executor_utils_1.getExecutorInformation)(nodeModule, executorName, workspace_root_1.workspaceRoot, projects);
343
335
  }
336
+ // TODO(v25): remove custom hasher support.
344
337
  function getCustomHasher(task, projects) {
345
338
  const factory = getExecutorForTask(task, projects).hasherFactory;
346
- return factory ? factory() : null;
339
+ if (!factory) {
340
+ return null;
341
+ }
342
+ warnCustomHasherDeprecation(projects[task.target.project].targets[task.target.target].executor);
343
+ return factory();
344
+ }
345
+ const customHasherDeprecationWarned = new Set();
346
+ function warnCustomHasherDeprecation(executor) {
347
+ if (customHasherDeprecationWarned.has(executor)) {
348
+ return;
349
+ }
350
+ customHasherDeprecationWarned.add(executor);
351
+ output_1.output.warn({
352
+ title: `The "${executor}" executor uses a custom hasher, which is deprecated and will be removed in Nx 25.`,
353
+ bodyLines: [
354
+ 'Replace the custom hasher with target inputs, following the migration guide:',
355
+ 'https://nx.dev/docs/kb/local-executors#replace-a-custom-hasher-with-inputs',
356
+ 'If the executor comes from a third-party plugin, report this to its maintainers.',
357
+ ],
358
+ });
359
+ }
360
+ /** Test-only: forget which executors have been warned about. */
361
+ function resetCustomHasherDeprecationWarnings() {
362
+ customHasherDeprecationWarned.clear();
347
363
  }
348
364
  /**
349
365
  * `taskGraph` pruned to `required`, which must already hold its dependencies.
@@ -370,8 +386,8 @@ function removeIdsFromTaskGraph(graph, ids, mapWithIds) {
370
386
  for (let id of Object.keys(mapWithIds)) {
371
387
  if (!removedSet.has(id)) {
372
388
  filteredMapWithIds[id] = mapWithIds[id];
373
- dependencies[id] = graph.dependencies[id].filter((depId) => !removedSet.has(depId));
374
- continuousDependencies[id] = graph.continuousDependencies[id].filter((depId) => !removedSet.has(depId));
389
+ dependencies[id] = graph.dependencies[id].filter((dep) => !removedSet.has(dep.id));
390
+ continuousDependencies[id] = graph.continuousDependencies[id].filter((dep) => !removedSet.has(dep.id));
375
391
  }
376
392
  }
377
393
  return {
@@ -388,12 +404,12 @@ function calculateReverseDeps(taskGraph) {
388
404
  });
389
405
  Object.keys(taskGraph.dependencies).forEach((taskId) => {
390
406
  taskGraph.dependencies[taskId].forEach((d) => {
391
- reverseTaskDeps[d].push(taskId);
407
+ reverseTaskDeps[d.id].push(taskId);
392
408
  });
393
409
  });
394
410
  Object.keys(taskGraph.continuousDependencies).forEach((taskId) => {
395
411
  taskGraph.continuousDependencies[taskId].forEach((d) => {
396
- reverseTaskDeps[d].push(taskId);
412
+ reverseTaskDeps[d.id].push(taskId);
397
413
  });
398
414
  });
399
415
  return reverseTaskDeps;
@@ -457,3 +473,22 @@ function createTaskId(project, target, configuration) {
457
473
  }
458
474
  return id;
459
475
  }
476
+ /**
477
+ * Distinguishes two invocations of the same task for the recursive invocation
478
+ * detector. The task id alone would report `nx run app:build --flag` invoked
479
+ * from within `app:build` as a loop, so the detector pairs the id with this
480
+ * hash and only treats an exact repeat as an immediate loop.
481
+ *
482
+ * The hash cannot decide recursion on its own. Arguments that accumulate
483
+ * through forwarding (`app:build` -> `app:build hello` -> `app:build hello
484
+ * hello`) hash differently on every hop, so the tracker also bounds how many
485
+ * ancestors may be running one task id.
486
+ *
487
+ * `__overrides_unparsed__` is dropped, matching how the task hasher treats it:
488
+ * it restates the other keys as raw argv, down to flag order and spelling.
489
+ */
490
+ function hashTaskOverrides(task) {
491
+ const overrides = { ...task.overrides };
492
+ delete overrides['__overrides_unparsed__'];
493
+ return (0, file_hasher_1.hashObject)(overrides);
494
+ }
@@ -19,6 +19,7 @@ function customHasherTaskIds(projectGraph, taskGraph) {
19
19
  return memoized;
20
20
  }
21
21
  const projects = (0, project_graph_1.readProjectsConfigurationFromProjectGraph)(projectGraph).projects;
22
+ // TODO(v25): remove custom hasher support.
22
23
  const ids = Object.values(taskGraph.tasks)
23
24
  .filter((task) => {
24
25
  try {
@@ -48,6 +49,9 @@ function getUltracacheSettings(taskGraph) {
48
49
  function ultracacheEligibilityOptions(projectGraph, taskGraph) {
49
50
  return {
50
51
  customHasherTaskIds: customHasherTaskIds(projectGraph, taskGraph),
52
+ continuousTaskIds: Object.values(taskGraph.tasks)
53
+ .filter((task) => task.continuous)
54
+ .map((task) => task.id),
51
55
  };
52
56
  }
53
57
  /**
@@ -128,6 +128,7 @@ function runNxArgvSync(argv, options) {
128
128
  if (result.status !== 0) {
129
129
  const error = new Error(`Command failed: nx ${argv.join(' ')} (exit code ${result.status})`);
130
130
  error.status = result.status ?? 1;
131
+ error.signal = result.signal;
131
132
  throw error;
132
133
  }
133
134
  }
@@ -93,8 +93,13 @@ export declare function tryCommitChanges(commitMessage: string, directory: strin
93
93
  /**
94
94
  * `tryCommitChanges` for a process that has to stay responsive while git
95
95
  * runs: a signing prompt or a hook can hold the commit for minutes.
96
+ * `withoutTerminal` is for a caller sharing the terminal with another program:
97
+ * git runs without it (see `spawnWithoutTerminal`), so such a prompt fails the
98
+ * commit instead.
96
99
  */
97
- export declare function tryCommitChangesAsync(commitMessage: string, directory: string, excludePaths?: string[]): Promise<string | null>;
100
+ export declare function tryCommitChangesAsync(commitMessage: string, directory: string, excludePaths?: string[], { withoutTerminal }?: {
101
+ withoutTerminal?: boolean;
102
+ }): Promise<string | null>;
98
103
  /**
99
104
  * `git reset --hard <ref>` then `git clean -fd`, keeping `keepPaths` (git
100
105
  * exclusion patterns, repo-relative) out of the clean. Throws with git's
@@ -30,6 +30,7 @@ const crypto = tslib_1.__importStar(require("crypto"));
30
30
  const fs = tslib_1.__importStar(require("fs"));
31
31
  const path_1 = require("path");
32
32
  const owned_private_dir_1 = require("./owned-private-dir");
33
+ const spawn_without_terminal_1 = require("./spawn-without-terminal");
33
34
  function execFileAsync(file, args, execOptions) {
34
35
  return new Promise((res, rej) => {
35
36
  (0, child_process_1.execFile)(file, args, { ...execOptions, windowsHide: true }, (err, stdout) => {
@@ -870,11 +871,14 @@ function tryCommitChanges(commitMessage, directory, excludePaths = []) {
870
871
  /**
871
872
  * `tryCommitChanges` for a process that has to stay responsive while git
872
873
  * runs: a signing prompt or a hook can hold the commit for minutes.
874
+ * `withoutTerminal` is for a caller sharing the terminal with another program:
875
+ * git runs without it (see `spawnWithoutTerminal`), so such a prompt fails the
876
+ * commit instead.
873
877
  */
874
- async function tryCommitChangesAsync(commitMessage, directory, excludePaths = []) {
878
+ async function tryCommitChangesAsync(commitMessage, directory, excludePaths = [], { withoutTerminal = false } = {}) {
875
879
  try {
876
880
  for (const { command, input } of commitCommands(commitMessage, excludePaths)) {
877
- await execAsync(command, directory, input);
881
+ await execAsync(command, directory, input, withoutTerminal);
878
882
  }
879
883
  }
880
884
  catch (err) {
@@ -908,12 +912,14 @@ function commitFailure(err) {
908
912
  // for callers to inspect; otherwise only stderr/stdout text survives.
909
913
  return new Error(detail || (err instanceof Error ? err.message : String(err)), { cause: err });
910
914
  }
911
- function execAsync(command, cwd, input) {
915
+ function execAsync(command, cwd, input, withoutTerminal) {
912
916
  return new Promise((res, rej) => {
913
- const child = (0, child_process_1.exec)(command, { encoding: 'utf8', cwd, windowsHide: true },
914
917
  // The streams ride on the error as `execSync` reports them, so
915
918
  // `commitFailure` shapes both the same way.
916
- (err, stdout, stderr) => err ? rej(Object.assign(err, { stdout, stderr })) : res());
919
+ const settle = (err, stdout, stderr) => err ? rej(Object.assign(err, { stdout, stderr })) : res();
920
+ const child = withoutTerminal
921
+ ? collectOutput((0, spawn_without_terminal_1.spawnWithoutTerminal)(command, { cwd }), command, settle)
922
+ : (0, child_process_1.exec)(command, { encoding: 'utf8', cwd, windowsHide: true }, settle);
917
923
  // Closed for every command, since `git commit -F -` reads to end of
918
924
  // input. A child that exits before reading raises EPIPE here, which the
919
925
  // exit callback already reports.
@@ -921,6 +927,21 @@ function execAsync(command, cwd, input) {
921
927
  child.stdin?.end(input ?? '');
922
928
  });
923
929
  }
930
+ // What `exec` reports through its callback; `exec` itself ignores `detached`.
931
+ function collectOutput(child, command, done) {
932
+ let stdout = '';
933
+ let stderr = '';
934
+ child.stdout?.setEncoding('utf8').on('data', (chunk) => (stdout += chunk));
935
+ child.stderr?.setEncoding('utf8').on('data', (chunk) => (stderr += chunk));
936
+ child.once('error', (err) => done(err, stdout, stderr));
937
+ child.once('close', (code, signal) => done(code === 0
938
+ ? null
939
+ : Object.assign(new Error(`Command failed: ${command}`), {
940
+ code,
941
+ signal,
942
+ }), stdout, stderr));
943
+ return child;
944
+ }
924
945
  /**
925
946
  * `git reset --hard <ref>` then `git clean -fd`, keeping `keepPaths` (git
926
947
  * exclusion patterns, repo-relative) out of the clean. Throws with git's
@@ -25,7 +25,7 @@ export type OwnedRealDir = string & {
25
25
  export type OwnedPrivateDir = string & {
26
26
  readonly [ownedPrivateDirBrand]: true;
27
27
  };
28
- /** Why a guard refused a directory. Data, not a sentence: `remedyFor` decides on it. */
28
+ /** Why a guard refused a directory. Data, not a sentence. */
29
29
  export type DirRefusal = {
30
30
  kind: 'not-created';
31
31
  dir: string;
@@ -65,8 +65,6 @@ export type GuardResult<T> = {
65
65
  };
66
66
  /** The user-facing sentence for a refusal. The only place wording is decided. */
67
67
  export declare function describeRefusal(r: DirRefusal): string;
68
- /** What the user can do about a refusal, or `undefined` when there is nothing. */
69
- export declare function remedyFor(r: DirRefusal): string | undefined;
70
68
  /** One refusal as an `Error`, so several can travel in an `AggregateError`. */
71
69
  export declare class DirectoryRefusedError extends Error {
72
70
  readonly refusal: DirRefusal;
@@ -98,20 +96,6 @@ export declare function canonicalDir(dir: string): string;
98
96
  * verified private directory beneath it.
99
97
  */
100
98
  export declare function isSafeSharedRoot(dir: string): GuardResult<SafeSharedRoot>;
101
- /**
102
- * Whether other users on this machine can write into `dir`. Gates whether a
103
- * refusal message may cite other users, so it must not over-report: `false` on
104
- * Windows, where libuv synthesizes `st_mode` from the READONLY attribute and
105
- * reports an ordinary per-account directory as `0666`, and `false` on a path
106
- * that cannot be inspected.
107
- *
108
- * `statSync`, not `lstatSync`: the question is about the directory that will be
109
- * used, not the link pointing at it. Link modes mislead in both directions —
110
- * Linux creates symlinks `0777`, macOS applies the umask. Nothing here decides
111
- * whether a path is accepted, so following the link costs nothing the guards
112
- * do not already re-check.
113
- */
114
- export declare function isPeerWritable(dir: string): boolean;
115
99
  /**
116
100
  * Create a shared container as sticky + world-writable if it does not exist,
117
101
  * and report whether the resulting path is safe for the current user.