nx 23.3.0-beta.8 → 23.3.0-beta.9

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 (34) hide show
  1. package/dist/src/command-line/affected/affected.js +20 -1
  2. package/dist/src/command-line/affected/command-object.js +1 -1
  3. package/dist/src/command-line/reset/reset.js +7 -4
  4. package/dist/src/command-line/show/command-object.js +1 -1
  5. package/dist/src/command-line/show/projects.js +22 -0
  6. package/dist/src/command-line/yargs-utils/shared-options.d.ts +4 -0
  7. package/dist/src/command-line/yargs-utils/shared-options.js +16 -0
  8. package/dist/src/daemon/message-types/select-affected-tasks.d.ts +3 -0
  9. package/dist/src/daemon/server/handle-select-affected-tasks.js +1 -0
  10. package/dist/src/devkit-internals.d.ts +3 -1
  11. package/dist/src/devkit-internals.js +8 -3
  12. package/dist/src/hasher/hash-task.d.ts +1 -1
  13. package/dist/src/hasher/hash-task.js +1 -0
  14. package/dist/src/native/index.d.ts +56 -0
  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/nx-cloud/utilities/client.d.ts +1 -1
  18. package/dist/src/nx-cloud/utilities/client.js +1 -1
  19. package/dist/src/plugins/js/utils/register.js +11 -3
  20. package/dist/src/project-graph/affected/affected-reasons.d.ts +116 -0
  21. package/dist/src/project-graph/affected/affected-reasons.js +459 -0
  22. package/dist/src/project-graph/affected/affected-tasks.d.ts +12 -0
  23. package/dist/src/project-graph/affected/affected-tasks.js +187 -11
  24. package/dist/src/project-graph/affected/print-explanation.d.ts +11 -0
  25. package/dist/src/project-graph/affected/print-explanation.js +44 -0
  26. package/dist/src/tasks-runner/default-tasks-runner.js +1 -1
  27. package/dist/src/tasks-runner/run-command.js +2 -1
  28. package/dist/src/tasks-runner/task-orchestrator.d.ts +7 -3
  29. package/dist/src/tasks-runner/task-orchestrator.js +9 -8
  30. package/dist/src/tasks-runner/tasks-runner.d.ts +2 -0
  31. package/dist/src/utils/command-line-utils.d.ts +1 -0
  32. package/dist/src/utils/create-cli-options.d.ts +6 -0
  33. package/dist/src/utils/create-cli-options.js +29 -0
  34. package/package.json +11 -11
@@ -14,6 +14,7 @@ const file_utils_1 = require("../file-utils");
14
14
  const find_matching_projects_1 = require("../../utils/find-matching-projects");
15
15
  const logger_1 = require("../../utils/logger");
16
16
  const error_types_1 = require("../error-types");
17
+ const affected_reasons_1 = require("./affected-reasons");
17
18
  const workspace_root_1 = require("../../utils/workspace-root");
18
19
  const task_planning_context_1 = require("../../hasher/task-planning-context");
19
20
  const client_1 = require("../../daemon/client/client");
@@ -21,6 +22,7 @@ const is_on_daemon_1 = require("../../daemon/is-on-daemon");
21
22
  const affected_projects_1 = require("./affected-projects");
22
23
  const lock_file_changes_1 = require("../../plugins/js/project-graph/affected/lock-file-changes");
23
24
  const npm_packages_1 = require("../../plugins/js/project-graph/affected/npm-packages");
25
+ const lock_file_1 = require("../../plugins/js/lock-file/lock-file");
24
26
  /**
25
27
  * Whether `nx affected` selects the individual tasks whose inputs a change
26
28
  * reaches, rather than whole projects. Off by default while the task path
@@ -51,6 +53,7 @@ async function computeAffectedTasks(opts) {
51
53
  excludeTaskDependencies: opts.excludeTaskDependencies ?? false,
52
54
  exclude: opts.exclude ?? [],
53
55
  selectivelyHashTsConfig: opts.selectivelyHashTsConfig,
56
+ explain: opts.explain,
54
57
  };
55
58
  const ultracacheConfigurations = (0, store_1.configurationsOf)(opts.ultracacheConfigurationOutcome ?? null);
56
59
  if (ultracacheConfigurations) {
@@ -65,6 +68,7 @@ async function computeAffectedTasks(opts) {
65
68
  return {
66
69
  ...selection,
67
70
  affectedTaskIds: new Set(selection.affectedTaskIds),
71
+ explanation: selection.explanation && (0, affected_reasons_1.hydrateExplanation)(selection.explanation),
68
72
  taskSelection: {
69
73
  ...selection.taskSelection,
70
74
  ultracacheConfigurationOutcome: opts.ultracacheConfigurationOutcome,
@@ -91,6 +95,7 @@ async function computeAffectedTasks(opts) {
91
95
  projectGraph,
92
96
  affectedTaskIds: selection.affectedTaskIds,
93
97
  taskGraph: selection.taskGraph,
98
+ explanation: selection.explanation && (0, affected_reasons_1.hydrateExplanation)(selection.explanation),
94
99
  taskSelection: {
95
100
  ...selection.taskSelection,
96
101
  // The planner remembers what selection planned, so the run's hashing reuses it.
@@ -122,6 +127,15 @@ async function selectAffectedTasks(projectGraph, nxJson, planningContext, reques
122
127
  affectedTaskIds: new Set(),
123
128
  taskGraph: empty,
124
129
  taskSelection: { taskGraph: empty, initiatingTaskIds: [], taskIds: [] },
130
+ explanation: request.explain
131
+ ? {
132
+ inputs: [],
133
+ affected: {},
134
+ upstream: {},
135
+ touched: [],
136
+ requested: { targets, total: 0 },
137
+ }
138
+ : undefined,
125
139
  };
126
140
  }
127
141
  const { taskGraph, dependencyOverrides } = (0, create_task_graph_1.createTaskGraphWithDependencyOverrides)(projectGraph, request.extraTargetDependencies, candidates, targets, request.configuration, request.overrides,
@@ -134,11 +148,12 @@ async function selectAffectedTasks(projectGraph, nxJson, planningContext, reques
134
148
  const dependencies = dependencyChanges(projectGraph, touchedFiles, nxJson, packageJson);
135
149
  const namedProjects = new Set(dependencies.projects);
136
150
  const projects = (0, project_graph_1.readProjectsConfigurationFromProjectGraph)(projectGraph).projects;
137
- const selection = (0, native_1.affectedTasks)(planningContext.projectGraphRef, plans, taskGraph, request.changedFiles, {
151
+ const customHashed = new Set(taskIds.filter((id) => hasCustomHasher(taskGraph.tasks[id], projects)));
152
+ const options = {
138
153
  projectGlobPatterns: await (0, affected_projects_1.getProjectGlobPatterns)(nxJson),
139
154
  workspaceRoot: workspace_root_1.workspaceRoot,
140
155
  alwaysTouchedTaskIds: taskIds.filter((id) => namedProjects.has(taskGraph.tasks[id].target.project) ||
141
- hasCustomHasher(taskGraph.tasks[id], projects)),
156
+ customHashed.has(id)),
142
157
  changedExternals: dependencies.externals,
143
158
  changedExternalTypes: dependencies.changedExternalTypes,
144
159
  excludedProjects: request.exclude.length
@@ -147,7 +162,9 @@ async function selectAffectedTasks(projectGraph, nxJson, planningContext, reques
147
162
  targets,
148
163
  revisions: fileRevisions(request.fileChangeArgs),
149
164
  selectivelyHashTsConfig: request.selectivelyHashTsConfig ?? false,
150
- });
165
+ explain: request.explain ?? false,
166
+ };
167
+ const selection = (0, native_1.affectedTasks)(planningContext.projectGraphRef, plans, taskGraph, request.changedFiles, options);
151
168
  // The run builds from the owning projects, so only their requested tasks
152
169
  // take the CLI overrides there; every other task is a dependency.
153
170
  const owning = new Set(selection.affected.map((id) => taskGraph.tasks[id].target.project));
@@ -162,6 +179,10 @@ async function selectAffectedTasks(projectGraph, nxJson, planningContext, reques
162
179
  return {
163
180
  affectedTaskIds: new Set(selection.affected),
164
181
  taskGraph,
182
+ explanation: selection.explanation && {
183
+ ...explainTasks(selection.affected, selection.explanation, dependencies, request.changedFiles, taskGraph, customHashed, keep),
184
+ requested: requestedTasks(taskIds, taskGraph, targets, options),
185
+ },
165
186
  taskSelection: {
166
187
  // Edges that disagree on a dependency's overrides leave it to a build
167
188
  // from the owning projects, which settles them the way the run always has.
@@ -191,20 +212,175 @@ function hasCustomHasher(task, projects) {
191
212
  return false;
192
213
  }
193
214
  }
194
- /**
195
- * A lockfile or package.json change reaches a hash as `External(name)`, a
196
- * package rather than a path, so it is handed to the plans as package names.
197
- */
198
215
  function dependencyChanges(projectGraph, touchedFiles, nxJson, packageJson = (0, file_utils_1.readPackageJson)()) {
199
- const changes = [
200
- (0, lock_file_changes_1.lockFileDependencyChanges)(touchedFiles, projectGraph.nodes, nxJson, packageJson, projectGraph),
201
- (0, npm_packages_1.packageJsonDependencyChanges)(touchedFiles, nxJson, projectGraph),
202
- ];
216
+ const lockFile = (0, lock_file_changes_1.lockFileDependencyChanges)(touchedFiles, projectGraph.nodes, nxJson, packageJson, projectGraph);
217
+ const packageJsonChanges = (0, npm_packages_1.packageJsonDependencyChanges)(touchedFiles, nxJson, projectGraph);
218
+ const changes = [lockFile, packageJsonChanges];
219
+ const named = new Map();
220
+ const name = (project, reason) => named.set(project, [...(named.get(project) ?? []), reason]);
221
+ const changedLockFile = touchedFiles.find((f) => lock_file_1.AUTO_AFFECTED_LOCK_FILES.includes(f.file))?.file;
222
+ for (const project of lockFile.projects) {
223
+ name(project, { kind: 'lockfile', file: changedLockFile });
224
+ }
225
+ for (const project of packageJsonChanges.projects) {
226
+ name(project, {
227
+ kind: 'npm-package',
228
+ // The name package.json uses, which need not be the project's.
229
+ package: projectGraph.nodes[project]?.data.metadata?.js?.packageName ?? project,
230
+ file: 'package.json',
231
+ });
232
+ }
233
+ const movedBy = new Map();
234
+ for (const external of packageJsonChanges.externals) {
235
+ movedBy.set(external, 'package.json');
236
+ }
237
+ for (const external of lockFile.externals) {
238
+ movedBy.set(external, changedLockFile);
239
+ }
240
+ const ecosystemMovedBy = new Map();
241
+ for (const [changes, file] of [
242
+ [packageJsonChanges, 'package.json'],
243
+ [lockFile, changedLockFile],
244
+ ]) {
245
+ for (const ecosystem of changes.changedExternalTypes) {
246
+ ecosystemMovedBy.set(ecosystem, [
247
+ ...(ecosystemMovedBy.get(ecosystem) ?? []),
248
+ file,
249
+ ]);
250
+ }
251
+ }
203
252
  return {
204
253
  externals: [...new Set(changes.flatMap((c) => c.externals))],
205
254
  changedExternalTypes: [
206
255
  ...new Set(changes.flatMap((c) => c.changedExternalTypes)),
207
256
  ],
208
257
  projects: [...new Set(changes.flatMap((c) => c.projects))],
258
+ named,
259
+ movedBy,
260
+ ecosystemMovedBy,
261
+ };
262
+ }
263
+ /**
264
+ * Why each selected task is in the answer, and why each task outside it that
265
+ * a reason names as a producer is too.
266
+ *
267
+ * Every reason that applies is listed: a task can match a changed file, hash a package that
268
+ * moved, and read an affected producer, all at once.
269
+ */
270
+ function explainTasks(affected, explanation, dependencies, changedPaths, taskGraph, customHashed,
271
+ /** What the run keeps: a reached task in it is listed even if nothing reads its outputs. */
272
+ keep) {
273
+ // What a plan hashing every external saw change.
274
+ const dependencyFiles = changedPaths.filter((file) => file === 'package.json' ||
275
+ lock_file_1.AUTO_AFFECTED_LOCK_FILES.includes(file));
276
+ const named = dependencies.named;
277
+ // Each matching instruction's reasons, built once and shared by every task
278
+ // whose plan holds it.
279
+ const inputs = explanation.inputMatches.map((matches) => {
280
+ const reasons = [];
281
+ for (const match of matches.files) {
282
+ reasons.push({
283
+ kind: 'input-file',
284
+ file: match.file,
285
+ pattern: match.pattern,
286
+ });
287
+ }
288
+ for (const pkg of matches.packages) {
289
+ reasons.push({
290
+ kind: 'npm-package',
291
+ package: pkg,
292
+ file: dependencies.movedBy.get(pkg),
293
+ });
294
+ }
295
+ for (const ecosystem of matches.movedEcosystems) {
296
+ // Only the files that could not be narrowed: a package.json naming its
297
+ // packages beside an unreadable lockfile did not move everything.
298
+ const files = dependencyFiles.filter((file) => dependencies.ecosystemMovedBy.get(ecosystem)?.includes(file));
299
+ for (const file of files.length ? files : [undefined]) {
300
+ reasons.push({ kind: 'moved-ecosystem', ecosystem, file });
301
+ }
302
+ }
303
+ if (matches.allExternals) {
304
+ for (const file of dependencyFiles) {
305
+ reasons.push({ kind: 'external-dependencies', file });
306
+ }
307
+ }
308
+ for (const file of matches.projectConfigs) {
309
+ reasons.push({ kind: 'project-configuration', file });
310
+ }
311
+ return reasons;
312
+ });
313
+ const reasonsFor = (taskId) => {
314
+ const forTask = [];
315
+ const matched = (explanation.taskInputMatches[taskId] ?? []).filter((index) => inputs[index].length);
316
+ if (matched.length) {
317
+ forTask.push({ kind: 'inputs', inputs: matched });
318
+ }
319
+ // Only the reached producers: the walk records the edges it crossed.
320
+ for (const producer of explanation.producersOf[taskId] ?? []) {
321
+ forTask.push({ kind: 'dependent-output', producer });
322
+ }
323
+ const project = taskGraph.tasks[taskId]?.target.project;
324
+ for (const reason of (project && named.get(project)) ?? []) {
325
+ forTask.push(reason);
326
+ }
327
+ if (customHashed.has(taskId)) {
328
+ forTask.push({ kind: 'custom-hasher' });
329
+ }
330
+ // A deleted config seeded every task. Only worth saying when nothing
331
+ // narrower applies, or it would repeat on every line.
332
+ if (!forTask.length) {
333
+ for (const file of explanation.deletedProjectConfigs) {
334
+ forTask.push({ kind: 'deleted-project-configuration', file });
335
+ }
336
+ }
337
+ return forTask;
338
+ };
339
+ const result = {
340
+ inputs,
341
+ affected: Object.fromEntries(affected.map((id) => [id, reasonsFor(id)])),
342
+ upstream: {},
343
+ touched: [],
344
+ };
345
+ // Reached: touched, or carried to through outputs, which leaves an edge.
346
+ const reached = new Set([
347
+ ...explanation.touched,
348
+ ...Object.keys(explanation.producersOf),
349
+ ]);
350
+ for (const id of keep) {
351
+ if (reached.has(id) && !(id in result.affected)) {
352
+ result.upstream[id] = reasonsFor(id);
353
+ }
354
+ }
355
+ const producers = [
356
+ ...Object.keys(result.affected),
357
+ ...Object.keys(result.upstream),
358
+ ].flatMap((id) => explanation.producersOf[id] ?? []);
359
+ while (producers.length) {
360
+ const id = producers.pop();
361
+ if (id in result.affected || id in result.upstream)
362
+ continue;
363
+ result.upstream[id] = reasonsFor(id);
364
+ producers.push(...(explanation.producersOf[id] ?? []));
365
+ }
366
+ result.touched = explanation.touched.filter((id) => id in result.affected || id in result.upstream);
367
+ for (const [pkg, file] of dependencies.movedBy) {
368
+ result.moved ??= {};
369
+ (result.moved[file] ??= []).push(pkg);
370
+ }
371
+ return result;
372
+ }
373
+ /**
374
+ * How many tasks of the requested targets selection chose among. Tasks of
375
+ * excluded projects are left out, since they were never candidates.
376
+ */
377
+ function requestedTasks(taskIds, taskGraph, targets, options) {
378
+ const excluded = new Set(options.excludedProjects);
379
+ return {
380
+ targets,
381
+ total: taskIds.filter((id) => {
382
+ const { project, target } = taskGraph.tasks[id].target;
383
+ return targets.includes(target) && !excluded.has(project);
384
+ }).length,
209
385
  };
210
386
  }
@@ -0,0 +1,11 @@
1
+ import { AffectedExplanation } from './affected-reasons';
2
+ /**
3
+ * Prints `--explain` output.
4
+ *
5
+ * `destination` is what the flag was given, following `--graph`: `true` is a
6
+ * bare `--explain` and prints the human form, `"stdout"` prints JSON, and any
7
+ * other string is a file to write the JSON to.
8
+ */
9
+ export declare function printAffectedExplanation(explanation: AffectedExplanation, heading: string, destination: string | boolean | undefined, options?: {
10
+ verbose?: boolean;
11
+ }): void;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.printAffectedExplanation = printAffectedExplanation;
4
+ const tslib_1 = require("tslib");
5
+ const fs_1 = require("fs");
6
+ const pc = tslib_1.__importStar(require("picocolors"));
7
+ const output_1 = require("../../utils/output");
8
+ const affected_reasons_1 = require("./affected-reasons");
9
+ /**
10
+ * Prints `--explain` output.
11
+ *
12
+ * `destination` is what the flag was given, following `--graph`: `true` is a
13
+ * bare `--explain` and prints the human form, `"stdout"` prints JSON, and any
14
+ * other string is a file to write the JSON to.
15
+ */
16
+ function printAffectedExplanation(explanation, heading, destination, options = {}) {
17
+ if (destination === 'stdout') {
18
+ console.log(JSON.stringify(explanation, null, 2));
19
+ return;
20
+ }
21
+ if (typeof destination === 'string' && destination) {
22
+ (0, fs_1.writeFileSync)(destination, JSON.stringify(explanation, null, 2));
23
+ output_1.output.success({ title: `Reasons written to ${destination}` });
24
+ return;
25
+ }
26
+ const rendered = (0, affected_reasons_1.formatAffectedExplanation)(explanation, heading, {
27
+ ...options,
28
+ styleTask,
29
+ dim: pc.dim,
30
+ bold: pc.bold,
31
+ });
32
+ if (!Object.keys(explanation.affected).length) {
33
+ output_1.output.log({ title: rendered });
34
+ return;
35
+ }
36
+ const [title, , ...bodyLines] = rendered.split('\n');
37
+ output_1.output.log({ title: title.replace(/:$/, ''), bodyLines });
38
+ }
39
+ // Colored like `nx show target`, with the tasks asked for in bold.
40
+ function styleTask(id, requested) {
41
+ const split = id.indexOf(':');
42
+ const colored = `${pc.cyan(id.slice(0, split))}:${pc.green(id.slice(split + 1))}`;
43
+ return requested ? pc.bold(colored) : colored;
44
+ }
@@ -68,7 +68,7 @@ const defaultTasksRunner = async (tasks, options, context) => {
68
68
  };
69
69
  exports.defaultTasksRunner = defaultTasksRunner;
70
70
  async function runAllTasks(options, context) {
71
- const orchestrator = new task_orchestrator_1.TaskOrchestrator(context.hasher, context.initiatingProject, context.initiatingTasks, context.projectGraph, context.taskGraph, context.nxJson, options, context.nxArgs?.nxBail, context.daemon, context.nxArgs?.specifiedOutputStyle, context.nxArgs?.resolvedOutputStyle ?? 'static-failures-only');
71
+ const orchestrator = new task_orchestrator_1.TaskOrchestrator(context.hasher, context.initiatingProject, context.initiatingTasks, context.projectGraph, context.taskGraph, context.nxJson, options, context.nxArgs?.nxBail, context.daemon, context.nxArgs?.specifiedOutputStyle, context.nxArgs?.resolvedOutputStyle ?? 'static-failures-only', context.taskGraph, context.deferredTaskIds);
72
72
  return orchestrator.run();
73
73
  }
74
74
  exports.default = exports.defaultTasksRunner;
@@ -719,7 +719,7 @@ async function invokeTasksRunner({ tasks, projectGraph, taskGraph, lifeCycle, nx
719
719
  // this is used for two reasons: to fetch all remote cache hits AND
720
720
  // to submit everything that is known in advance to Nx Cloud to run in
721
721
  // a distributed fashion
722
- await (0, hash_task_1.hashTasksThatDoNotDependOnOutputsOfOtherTasks)(hasher, projectGraph, taskGraph, nxJson, taskDetails, ultracacheConfigurations);
722
+ const deferredTaskIds = await (0, hash_task_1.hashTasksThatDoNotDependOnOutputsOfOtherTasks)(hasher, projectGraph, taskGraph, nxJson, taskDetails, ultracacheConfigurations);
723
723
  reportUltracacheConfigurations(ultracacheConfigurationOutcome, projectGraph, taskGraph, nxArgs);
724
724
  const taskResultsLifecycle = new task_results_life_cycle_1.TaskResultsLifeCycle();
725
725
  const compositedLifeCycle = new life_cycle_1.CompositeLifeCycle([
@@ -790,6 +790,7 @@ async function invokeTasksRunner({ tasks, projectGraph, taskGraph, lifeCycle, nx
790
790
  },
791
791
  },
792
792
  daemon: client_1.daemonClient,
793
+ deferredTaskIds,
793
794
  });
794
795
  if (promiseOrObservable.subscribe) {
795
796
  promiseOrObservable = convertObservableToPromise(promiseOrObservable);
@@ -26,6 +26,8 @@ export declare class TaskOrchestrator {
26
26
  /** What this run renders with, after defaults. */
27
27
  private readonly resolvedOutputStyle;
28
28
  private readonly fullTaskGraph;
29
+ /** Tasks the up-front pass left to hash once the tasks they read from have run. */
30
+ private readonly deferredTaskIds?;
29
31
  private taskDetails;
30
32
  private cache;
31
33
  private readonly tuiEnabled;
@@ -68,7 +70,9 @@ export declare class TaskOrchestrator {
68
70
  /** What the user named; undefined means they named nothing. */
69
71
  specifiedOutputStyle: OutputStyle | undefined,
70
72
  /** What this run renders with, after defaults. */
71
- resolvedOutputStyle: OutputStyle, fullTaskGraph?: TaskGraph);
73
+ resolvedOutputStyle: OutputStyle, fullTaskGraph?: TaskGraph,
74
+ /** Tasks the up-front pass left to hash once the tasks they read from have run. */
75
+ deferredTaskIds?: ReadonlySet<string>);
72
76
  init(): Promise<void>;
73
77
  run(): Promise<{
74
78
  [k: string]: TaskStatus;
@@ -128,8 +132,8 @@ export declare class TaskOrchestrator {
128
132
  * Hash all batch tasks and resolve cache hits topologically.
129
133
  *
130
134
  * Walks the task graph level by level. Every task gets a preliminary hash
131
- * (so startTasks always has a valid hash for Cloud). Tasks with depsOutputs
132
- * whose deps weren't cached are ineligible for cache lookup but still
135
+ * (so startTasks always has a valid hash for Cloud). Tasks the up-front
136
+ * pass deferred, whose deps weren't cached, are ineligible for cache lookup but still
133
137
  * receive a preliminary hash — they'll be re-hashed after execution.
134
138
  */
135
139
  private applyBatchCachedResults;
@@ -12,7 +12,6 @@ const project_graph_1 = require("../project-graph/project-graph");
12
12
  const run_commands_impl_1 = require("../executors/run-commands/run-commands.impl");
13
13
  const hash_task_1 = require("../hasher/hash-task");
14
14
  const task_graph_utils_1 = require("./task-graph-utils");
15
- const task_hasher_1 = require("../hasher/task-hasher");
16
15
  const native_1 = require("../native");
17
16
  const db_connection_1 = require("../utils/db-connection");
18
17
  const exit_codes_1 = require("../utils/exit-codes");
@@ -52,7 +51,9 @@ class TaskOrchestrator {
52
51
  /** What the user named; undefined means they named nothing. */
53
52
  specifiedOutputStyle,
54
53
  /** What this run renders with, after defaults. */
55
- resolvedOutputStyle, fullTaskGraph = taskGraph) {
54
+ resolvedOutputStyle, fullTaskGraph = taskGraph,
55
+ /** Tasks the up-front pass left to hash once the tasks they read from have run. */
56
+ deferredTaskIds) {
56
57
  this.hasher = hasher;
57
58
  this.initiatingProject = initiatingProject;
58
59
  this.initiatingTasks = initiatingTasks;
@@ -65,6 +66,7 @@ class TaskOrchestrator {
65
66
  this.specifiedOutputStyle = specifiedOutputStyle;
66
67
  this.resolvedOutputStyle = resolvedOutputStyle;
67
68
  this.fullTaskGraph = fullTaskGraph;
69
+ this.deferredTaskIds = deferredTaskIds;
68
70
  this.taskDetails = (0, hash_task_1.getTaskDetails)();
69
71
  this.cache = (0, cache_1.getCache)(this.options);
70
72
  this.tuiEnabled = (0, is_tui_enabled_1.isTuiEnabled)();
@@ -477,8 +479,8 @@ class TaskOrchestrator {
477
479
  * Hash all batch tasks and resolve cache hits topologically.
478
480
  *
479
481
  * Walks the task graph level by level. Every task gets a preliminary hash
480
- * (so startTasks always has a valid hash for Cloud). Tasks with depsOutputs
481
- * whose deps weren't cached are ineligible for cache lookup but still
482
+ * (so startTasks always has a valid hash for Cloud). Tasks the up-front
483
+ * pass deferred, whose deps weren't cached, are ineligible for cache lookup but still
482
484
  * receive a preliminary hash — they'll be re-hashed after execution.
483
485
  */
484
486
  async applyBatchCachedResults(batch, doNotSkipCache, groupId) {
@@ -498,8 +500,7 @@ class TaskOrchestrator {
498
500
  for (const task of rootTasks) {
499
501
  const depIds = batch.taskGraph.dependencies[task.id];
500
502
  const hasNonCachedDep = depIds.some((id) => nonCachedTaskIds.has(id));
501
- if (hasNonCachedDep &&
502
- (0, task_hasher_1.getInputs)(task, this.projectGraph, this.nxJson).depsOutputs.length > 0) {
503
+ if (hasNonCachedDep && this.deferredTaskIds?.has(task.id)) {
503
504
  nonCachedTaskIds.add(task.id);
504
505
  needsRehashAfterExecution.add(task.id);
505
506
  }
@@ -555,7 +556,7 @@ class TaskOrchestrator {
555
556
  await Promise.all(nonCachedTasks.map((task) => this.options.lifeCycle.scheduleTask(task)));
556
557
  await this.preRunSteps(nonCachedTasks, { groupId });
557
558
  }
558
- // Phase 2: Run non-cached tasks, then re-hash depsOutputs tasks
559
+ // Phase 2: Run non-cached tasks, then re-hash tasks that read their outputs
559
560
  const taskIdsToSkip = cachedResults.map((r) => r.task.id);
560
561
  let batchResults = [];
561
562
  if (taskIdsToSkip.length < tasks.length) {
@@ -568,7 +569,7 @@ class TaskOrchestrator {
568
569
  executorName: batch.executorName,
569
570
  taskGraph: runGraph,
570
571
  }, this.batchEnv, groupId);
571
- // Re-hash depsOutputs tasks — their dep outputs are now on disk
572
+ // Re-hash tasks that read dep outputs — those outputs are now on disk
572
573
  const tasksToRehash = batchResults
573
574
  .filter((r) => needsRehashAfterExecution.has(r.task.id) &&
574
575
  (r.status === 'success' || r.status === 'failure'))
@@ -19,6 +19,8 @@ export type TasksRunner<T = unknown> = (tasks: Task[], options: T, context?: {
19
19
  taskGraph?: TaskGraph;
20
20
  hasher?: TaskHasher;
21
21
  daemon?: DaemonClient;
22
+ /** Tasks hashed only once the tasks they read outputs from have run. */
23
+ deferredTaskIds?: ReadonlySet<string>;
22
24
  }) => any | Promise<{
23
25
  [id: string]: TaskStatus;
24
26
  }>;
@@ -28,6 +28,7 @@ export interface NxArgs {
28
28
  select?: string;
29
29
  graph?: string | boolean;
30
30
  skipNxCache?: boolean;
31
+ explain?: string | boolean;
31
32
  skipRemoteCache?: boolean;
32
33
  /**
33
34
  * The style the user named - CLI flag or `NX_DEFAULT_OUTPUT_STYLE`. Left
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Turns options into CLI flags: `{ maxWarnings: 0, fix: true }` gives
3
+ * `--max-warnings=0 --fix`. Arrays repeat the flag; `false`, `null` and
4
+ * `undefined` give none.
5
+ */
6
+ export declare function createCliOptions(obj: Record<string, string | number | boolean | string[] | undefined | null>): string[];
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createCliOptions = createCliOptions;
4
+ /**
5
+ * Turns options into CLI flags: `{ maxWarnings: 0, fix: true }` gives
6
+ * `--max-warnings=0 --fix`. Arrays repeat the flag; `false`, `null` and
7
+ * `undefined` give none.
8
+ */
9
+ function createCliOptions(obj) {
10
+ const args = [];
11
+ for (const [key, value] of Object.entries(obj)) {
12
+ if (value === undefined || value === null || value === false) {
13
+ continue;
14
+ }
15
+ const flag = `--${key.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase())}`;
16
+ if (value === true) {
17
+ args.push(flag);
18
+ }
19
+ else if (Array.isArray(value)) {
20
+ for (const item of value) {
21
+ args.push(`${flag}=${item}`);
22
+ }
23
+ }
24
+ else {
25
+ args.push(`${flag}=${value}`);
26
+ }
27
+ }
28
+ return args;
29
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nx",
3
- "version": "23.3.0-beta.8",
3
+ "version": "23.3.0-beta.9",
4
4
  "private": false,
5
5
  "type": "commonjs",
6
6
  "description": "The core Nx plugin contains the core functionality of Nx like the project graph, nx commands and task orchestration.",
@@ -159,16 +159,16 @@
159
159
  }
160
160
  },
161
161
  "optionalDependencies": {
162
- "@nx/nx-darwin-arm64": "23.3.0-beta.8",
163
- "@nx/nx-darwin-x64": "23.3.0-beta.8",
164
- "@nx/nx-freebsd-x64": "23.3.0-beta.8",
165
- "@nx/nx-linux-arm-gnueabihf": "23.3.0-beta.8",
166
- "@nx/nx-linux-arm64-gnu": "23.3.0-beta.8",
167
- "@nx/nx-linux-arm64-musl": "23.3.0-beta.8",
168
- "@nx/nx-linux-x64-gnu": "23.3.0-beta.8",
169
- "@nx/nx-linux-x64-musl": "23.3.0-beta.8",
170
- "@nx/nx-win32-arm64-msvc": "23.3.0-beta.8",
171
- "@nx/nx-win32-x64-msvc": "23.3.0-beta.8"
162
+ "@nx/nx-darwin-arm64": "23.3.0-beta.9",
163
+ "@nx/nx-darwin-x64": "23.3.0-beta.9",
164
+ "@nx/nx-freebsd-x64": "23.3.0-beta.9",
165
+ "@nx/nx-linux-arm-gnueabihf": "23.3.0-beta.9",
166
+ "@nx/nx-linux-arm64-gnu": "23.3.0-beta.9",
167
+ "@nx/nx-linux-arm64-musl": "23.3.0-beta.9",
168
+ "@nx/nx-linux-x64-gnu": "23.3.0-beta.9",
169
+ "@nx/nx-linux-x64-musl": "23.3.0-beta.9",
170
+ "@nx/nx-win32-arm64-msvc": "23.3.0-beta.9",
171
+ "@nx/nx-win32-x64-msvc": "23.3.0-beta.9"
172
172
  },
173
173
  "nx-migrations": {
174
174
  "migrations": "./migrations.json",