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.
- package/dist/src/command-line/affected/affected.js +20 -1
- package/dist/src/command-line/affected/command-object.js +1 -1
- package/dist/src/command-line/reset/reset.js +7 -4
- package/dist/src/command-line/show/command-object.js +1 -1
- package/dist/src/command-line/show/projects.js +22 -0
- package/dist/src/command-line/yargs-utils/shared-options.d.ts +4 -0
- package/dist/src/command-line/yargs-utils/shared-options.js +16 -0
- package/dist/src/daemon/message-types/select-affected-tasks.d.ts +3 -0
- package/dist/src/daemon/server/handle-select-affected-tasks.js +1 -0
- package/dist/src/devkit-internals.d.ts +3 -1
- package/dist/src/devkit-internals.js +8 -3
- package/dist/src/hasher/hash-task.d.ts +1 -1
- package/dist/src/hasher/hash-task.js +1 -0
- package/dist/src/native/index.d.ts +56 -0
- package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
- package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
- package/dist/src/nx-cloud/utilities/client.d.ts +1 -1
- package/dist/src/nx-cloud/utilities/client.js +1 -1
- package/dist/src/plugins/js/utils/register.js +11 -3
- package/dist/src/project-graph/affected/affected-reasons.d.ts +116 -0
- package/dist/src/project-graph/affected/affected-reasons.js +459 -0
- package/dist/src/project-graph/affected/affected-tasks.d.ts +12 -0
- package/dist/src/project-graph/affected/affected-tasks.js +187 -11
- package/dist/src/project-graph/affected/print-explanation.d.ts +11 -0
- package/dist/src/project-graph/affected/print-explanation.js +44 -0
- package/dist/src/tasks-runner/default-tasks-runner.js +1 -1
- package/dist/src/tasks-runner/run-command.js +2 -1
- package/dist/src/tasks-runner/task-orchestrator.d.ts +7 -3
- package/dist/src/tasks-runner/task-orchestrator.js +9 -8
- package/dist/src/tasks-runner/tasks-runner.d.ts +2 -0
- package/dist/src/utils/command-line-utils.d.ts +1 -0
- package/dist/src/utils/create-cli-options.d.ts +6 -0
- package/dist/src/utils/create-cli-options.js +29 -0
- 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
|
|
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
|
-
|
|
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
|
|
200
|
-
|
|
201
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
}>;
|
|
@@ -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.
|
|
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.
|
|
163
|
-
"@nx/nx-darwin-x64": "23.3.0-beta.
|
|
164
|
-
"@nx/nx-freebsd-x64": "23.3.0-beta.
|
|
165
|
-
"@nx/nx-linux-arm-gnueabihf": "23.3.0-beta.
|
|
166
|
-
"@nx/nx-linux-arm64-gnu": "23.3.0-beta.
|
|
167
|
-
"@nx/nx-linux-arm64-musl": "23.3.0-beta.
|
|
168
|
-
"@nx/nx-linux-x64-gnu": "23.3.0-beta.
|
|
169
|
-
"@nx/nx-linux-x64-musl": "23.3.0-beta.
|
|
170
|
-
"@nx/nx-win32-arm64-msvc": "23.3.0-beta.
|
|
171
|
-
"@nx/nx-win32-x64-msvc": "23.3.0-beta.
|
|
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",
|