nx 24.0.0-beta.1 → 24.0.0-beta.2
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/graph/graph.js +2 -1
- package/dist/src/command-line/migrate/agentic/handoff-gitignore.d.ts +19 -23
- package/dist/src/command-line/migrate/agentic/handoff-gitignore.js +16 -19
- package/dist/src/command-line/migrate/agentic/master/run-master-session.js +25 -13
- package/dist/src/command-line/migrate/agentic/master/spawn-master.js +6 -7
- package/dist/src/command-line/migrate/agentic/select.js +3 -3
- package/dist/src/command-line/migrate/agentic/types.js +1 -3
- package/dist/src/command-line/migrate/command-object.js +5 -9
- package/dist/src/command-line/migrate/migrate-analytics.d.ts +6 -0
- package/dist/src/command-line/migrate/migrate-analytics.js +12 -0
- package/dist/src/command-line/migrate/migrate-config.d.ts +6 -6
- package/dist/src/command-line/migrate/migrate-config.js +12 -6
- package/dist/src/command-line/migrate/migrate.d.ts +3 -30
- package/dist/src/command-line/migrate/migrate.js +163 -231
- package/dist/src/command-line/migrate/run/existing-run-report.d.ts +1 -1
- package/dist/src/command-line/migrate/run/existing-run-report.js +7 -5
- package/dist/src/command-line/migrate/run/index.d.ts +3 -3
- package/dist/src/command-line/migrate/run/index.js +5 -1
- package/dist/src/command-line/migrate/run/orchestrator.d.ts +21 -0
- package/dist/src/command-line/migrate/run/orchestrator.js +77 -19
- package/dist/src/command-line/migrate/run/run-state.d.ts +13 -0
- package/dist/src/command-line/migrate/run/run-state.js +21 -0
- package/dist/src/command-line/migrate/sort-migrations.d.ts +1 -7
- package/dist/src/command-line/migrate/sort-migrations.js +1 -12
- package/dist/src/config/nx-json.d.ts +13 -4
- package/dist/src/config/workspace-json-project-json.d.ts +12 -0
- package/dist/src/core/graph/main.js +1 -1
- package/dist/src/hasher/task-hasher.d.ts +2 -0
- package/dist/src/hasher/task-hasher.js +4 -1
- package/dist/src/native/index.d.ts +32 -3
- package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
- package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
- package/dist/src/project-graph/utils/project-configuration/target-normalization.js +6 -1
- package/dist/src/project-graph/utils/retrieve-workspace-files.js +27 -6
- package/dist/src/tasks-runner/default-tasks-runner.js +1 -1
- package/dist/src/tasks-runner/run-command.js +2 -2
- package/dist/src/tasks-runner/running-tasks/batch-process.js +6 -0
- package/dist/src/tasks-runner/task-orchestrator.d.ts +10 -6
- package/dist/src/tasks-runner/task-orchestrator.js +89 -59
- package/dist/src/tasks-runner/tasks-runner.d.ts +1 -1
- package/dist/src/utils/plugins/core-plugins.js +4 -0
- package/package.json +12 -11
- package/schemas/nx-schema.json +28 -4
- package/schemas/project-schema.json +22 -2
|
@@ -142,6 +142,7 @@ export declare function getInputs(task: Task, projectGraph: ProjectGraph, nxJson
|
|
|
142
142
|
projectInputs: {
|
|
143
143
|
input: string;
|
|
144
144
|
projects: string[];
|
|
145
|
+
dependencies?: true;
|
|
145
146
|
}[];
|
|
146
147
|
depsFilesets: {
|
|
147
148
|
fileset: string;
|
|
@@ -158,6 +159,7 @@ export declare function splitInputsIntoSelfAndDependencies(inputs: ReadonlyArray
|
|
|
158
159
|
projectInputs: {
|
|
159
160
|
input: string;
|
|
160
161
|
projects: string[];
|
|
162
|
+
dependencies?: true;
|
|
161
163
|
}[];
|
|
162
164
|
selfInputs: ExpandedSelfInput[];
|
|
163
165
|
depsOutputs: ExpandedDepsOutput[];
|
|
@@ -200,7 +200,7 @@ function splitInputsIntoSelfAndDependencies(inputs, namedInputs) {
|
|
|
200
200
|
}
|
|
201
201
|
else if ('input' in d &&
|
|
202
202
|
!('fileset' in d) &&
|
|
203
|
-
(('dependencies' in d && d.dependencies) ||
|
|
203
|
+
(('dependencies' in d && d.dependencies && !('projects' in d)) ||
|
|
204
204
|
// Todo(@AgentEnder): Remove check in v17
|
|
205
205
|
('projects' in d &&
|
|
206
206
|
typeof d.projects === 'string' &&
|
|
@@ -219,6 +219,9 @@ function splitInputsIntoSelfAndDependencies(inputs, namedInputs) {
|
|
|
219
219
|
projectInputs.push({
|
|
220
220
|
input: d.input,
|
|
221
221
|
projects: Array.isArray(d.projects) ? d.projects : [d.projects],
|
|
222
|
+
...('dependencies' in d && d.dependencies
|
|
223
|
+
? { dependencies: true }
|
|
224
|
+
: {}),
|
|
222
225
|
});
|
|
223
226
|
}
|
|
224
227
|
else {
|
|
@@ -99,7 +99,8 @@ export declare class HashPlanner {
|
|
|
99
99
|
constructor(nxJson: NxJson, projectGraph: ExternalObject<ProjectGraph>)
|
|
100
100
|
/**
|
|
101
101
|
* `configurations` is this run's Ultracache configurations; a task with an
|
|
102
|
-
* eligible entry hashes its observed reads instead of its declared
|
|
102
|
+
* eligible entry hashes its observed reads instead of its declared
|
|
103
|
+
* filesets, except `always` ones.
|
|
103
104
|
* `options` carries the task ids decided in JS, where executors and
|
|
104
105
|
* target configuration are resolved.
|
|
105
106
|
*/
|
|
@@ -732,8 +733,12 @@ export declare const enum EventType {
|
|
|
732
733
|
rescan = 'rescan'
|
|
733
734
|
}
|
|
734
735
|
|
|
735
|
-
/**
|
|
736
|
-
|
|
736
|
+
/**
|
|
737
|
+
* The files an `includeIgnored` fileset group matches on disk, sorted.
|
|
738
|
+
* Given the workspace's project roots, an entry skips projects nested below
|
|
739
|
+
* the one it reads from, as hashing does.
|
|
740
|
+
*/
|
|
741
|
+
export declare function expandFilesInput(workspaceRoot: string, globs: Array<string>, projectRoots?: Array<string> | undefined | null): Array<string>
|
|
737
742
|
|
|
738
743
|
export declare function expandOutputs(directory: string, entries: Array<string>): Array<string>
|
|
739
744
|
|
|
@@ -779,6 +784,11 @@ export interface FileSetInput {
|
|
|
779
784
|
* instead of the workspace file map.
|
|
780
785
|
*/
|
|
781
786
|
includeIgnored?: boolean
|
|
787
|
+
/**
|
|
788
|
+
* Keep the matched files in the hash even when the task is hashed from
|
|
789
|
+
* its ultracache recording.
|
|
790
|
+
*/
|
|
791
|
+
always?: boolean
|
|
782
792
|
}
|
|
783
793
|
|
|
784
794
|
export declare function findImports(projectFileMap: Record<string, Array<string>>): Array<ImportResult>
|
|
@@ -937,8 +947,17 @@ export interface InputMatch {
|
|
|
937
947
|
|
|
938
948
|
export interface InputsInput {
|
|
939
949
|
input: string
|
|
950
|
+
/**
|
|
951
|
+
* With `projects`: the selected projects' dependencies' `input`, as `^input`
|
|
952
|
+
* resolves for each, instead of the projects' own files.
|
|
953
|
+
*/
|
|
940
954
|
dependencies?: boolean
|
|
941
955
|
projects?: string | Array<string>
|
|
956
|
+
/**
|
|
957
|
+
* Keep the files this input resolves to in the hash even when the task
|
|
958
|
+
* is hashed from its ultracache recording.
|
|
959
|
+
*/
|
|
960
|
+
always?: boolean
|
|
942
961
|
}
|
|
943
962
|
|
|
944
963
|
export declare function installNxConsole(): Promise<boolean>
|
|
@@ -978,6 +997,11 @@ export interface JsonInput {
|
|
|
978
997
|
json: string
|
|
979
998
|
fields?: Array<string>
|
|
980
999
|
excludeFields?: Array<string>
|
|
1000
|
+
/**
|
|
1001
|
+
* Keep the file in the hash even when the task is hashed from its
|
|
1002
|
+
* ultracache recording.
|
|
1003
|
+
*/
|
|
1004
|
+
always?: boolean
|
|
981
1005
|
}
|
|
982
1006
|
|
|
983
1007
|
/**
|
|
@@ -1435,6 +1459,11 @@ export interface TaskTarget {
|
|
|
1435
1459
|
export interface TaskUltracacheSettings {
|
|
1436
1460
|
/** How this target's tasks participate. Defaults to `on`. */
|
|
1437
1461
|
mode?: 'on' | 'warn' | 'error' | 'off'
|
|
1462
|
+
/**
|
|
1463
|
+
* Collect successful stat metadata accesses as inputs. Defaults to `false`.
|
|
1464
|
+
* Missing-path probes are tracked independently of this setting.
|
|
1465
|
+
*/
|
|
1466
|
+
collectStats?: boolean
|
|
1438
1467
|
/**
|
|
1439
1468
|
* Workspace-relative glob patterns for reads that should be excluded
|
|
1440
1469
|
* from ultracache reports. The first path segment cannot contain `*`,
|
|
Binary file
|
|
Binary file
|
|
@@ -186,6 +186,7 @@ function warnAboutLegacyCachedTargets(legacyCacheReads) {
|
|
|
186
186
|
*/
|
|
187
187
|
const KNOWN_ULTRACACHE_KEYS = new Set([
|
|
188
188
|
'mode',
|
|
189
|
+
'collectStats',
|
|
189
190
|
'ignoredReads',
|
|
190
191
|
'ignoredWrites',
|
|
191
192
|
utils_1.NX_SPREAD_TOKEN,
|
|
@@ -233,7 +234,7 @@ function validateTargetUltracache(ultracache, projectName, projectRoot, targetNa
|
|
|
233
234
|
const errors = [];
|
|
234
235
|
for (const key of Object.keys(ultracache)) {
|
|
235
236
|
if (!KNOWN_ULTRACACHE_KEYS.has(key)) {
|
|
236
|
-
errors.push(`"ultracache.${key}" for target ${where} is not an ultracache option. Supported options are "mode", "ignoredReads" and "ignoredWrites".`);
|
|
237
|
+
errors.push(`"ultracache.${key}" for target ${where} is not an ultracache option. Supported options are "mode", "collectStats", "ignoredReads" and "ignoredWrites".`);
|
|
237
238
|
}
|
|
238
239
|
}
|
|
239
240
|
if (ultracache.mode !== undefined &&
|
|
@@ -243,6 +244,10 @@ function validateTargetUltracache(ultracache, projectName, projectRoot, targetNa
|
|
|
243
244
|
? `"${ultracache.mode}"`
|
|
244
245
|
: describeUltracacheValue(ultracache.mode)}.`);
|
|
245
246
|
}
|
|
247
|
+
if (ultracache.collectStats !== undefined &&
|
|
248
|
+
typeof ultracache.collectStats !== 'boolean') {
|
|
249
|
+
errors.push(`"ultracache.collectStats" for target ${where} must be a boolean, but it is ${describeUltracacheValue(ultracache.collectStats)}.`);
|
|
250
|
+
}
|
|
246
251
|
for (const key of ['ignoredReads', 'ignoredWrites']) {
|
|
247
252
|
const value = ultracache[key];
|
|
248
253
|
if (value === undefined)
|
|
@@ -12,6 +12,7 @@ const angular_json_1 = require("../../adapter/angular-json");
|
|
|
12
12
|
const nx_json_1 = require("../../config/nx-json");
|
|
13
13
|
const project_configuration_utils_1 = require("./project-configuration-utils");
|
|
14
14
|
const workspace_context_1 = require("../../utils/workspace-context");
|
|
15
|
+
const minimatch_1 = require("minimatch");
|
|
15
16
|
const path_1 = require("path");
|
|
16
17
|
const get_plugins_1 = require("../plugins/get-plugins");
|
|
17
18
|
/**
|
|
@@ -45,11 +46,9 @@ async function retrieveWorkspaceFiles(workspaceRoot, projectRootMap) {
|
|
|
45
46
|
async function retrieveProjectConfigurations(separatedPlugins, workspaceRoot, nxJson) {
|
|
46
47
|
const specifiedWithCreateNodes = separatedPlugins.specifiedPlugins.filter((p) => !!p.createNodes);
|
|
47
48
|
const defaultWithCreateNodes = separatedPlugins.defaultPlugins.filter((p) => !!p.createNodes);
|
|
48
|
-
const specifiedGlobPatterns = getGlobPatternsOfPlugins(specifiedWithCreateNodes);
|
|
49
|
-
const defaultGlobPatterns = getGlobPatternsOfPlugins(defaultWithCreateNodes);
|
|
50
49
|
const [specifiedPluginFiles, defaultPluginFiles] = await Promise.all([
|
|
51
|
-
(
|
|
52
|
-
(
|
|
50
|
+
globPluginFiles(workspaceRoot, specifiedWithCreateNodes),
|
|
51
|
+
globPluginFiles(workspaceRoot, defaultWithCreateNodes),
|
|
53
52
|
]);
|
|
54
53
|
return (0, project_configuration_utils_1.createProjectConfigurationsWithPlugins)(workspaceRoot, nxJson, {
|
|
55
54
|
specifiedPluginFiles: specifiedPluginFiles ?? [],
|
|
@@ -71,8 +70,7 @@ async function retrieveProjectConfigurationsWithAngularProjects(workspaceRoot, n
|
|
|
71
70
|
return res;
|
|
72
71
|
}
|
|
73
72
|
async function retrieveProjectConfigurationPaths(root, plugins) {
|
|
74
|
-
const
|
|
75
|
-
const pluginConfigFiles = await (0, workspace_context_1.multiGlobWithWorkspaceContext)(root, projectGlobPatterns);
|
|
73
|
+
const pluginConfigFiles = await globPluginFiles(root, plugins);
|
|
76
74
|
return pluginConfigFiles.flat();
|
|
77
75
|
}
|
|
78
76
|
const projectsWithoutPluginCache = new Map();
|
|
@@ -105,6 +103,29 @@ async function retrieveProjectConfigurationsWithoutPluginInference(root) {
|
|
|
105
103
|
function clearProjectsWithoutPluginInferenceCache() {
|
|
106
104
|
projectsWithoutPluginCache.clear();
|
|
107
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Files each plugin's createNodes pattern matches, in plugin order. A plugin
|
|
108
|
+
* scoped by `include` globs only those patterns natively, so a broad pattern
|
|
109
|
+
* (every `.ts` file, say) is never matched against the whole workspace in JS.
|
|
110
|
+
*/
|
|
111
|
+
async function globPluginFiles(root, plugins) {
|
|
112
|
+
const unscoped = plugins.filter((p) => !canGlobInclude(p));
|
|
113
|
+
const unscopedFiles = (await (0, workspace_context_1.multiGlobWithWorkspaceContext)(root, unscoped.map((p) => p.createNodes[0]))) ?? [];
|
|
114
|
+
return Promise.all(plugins.map(async (plugin) => {
|
|
115
|
+
if (!canGlobInclude(plugin)) {
|
|
116
|
+
return unscopedFiles[unscoped.indexOf(plugin)] ?? [];
|
|
117
|
+
}
|
|
118
|
+
const pattern = new minimatch_1.Minimatch(plugin.createNodes[0], { dot: true });
|
|
119
|
+
const included = await (0, workspace_context_1.globWithWorkspaceContext)(root, plugin.include);
|
|
120
|
+
return included.filter((file) => pattern.match(file));
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
// Only narrow includes: a `**` scope can return more files than the plugin
|
|
124
|
+
// pattern does. Negations and extglobs keep the JS matcher's semantics.
|
|
125
|
+
function canGlobInclude(plugin) {
|
|
126
|
+
return (plugin.include?.length > 0 &&
|
|
127
|
+
plugin.include.every((pattern) => !pattern.includes('**') && !/[!()]/.test(pattern)));
|
|
128
|
+
}
|
|
108
129
|
function getGlobPatternsOfPlugins(plugins) {
|
|
109
130
|
return plugins.map((p) => p.createNodes[0]);
|
|
110
131
|
}
|
|
@@ -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', context.taskGraph, 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', context.taskGraph, context.hashingDeferredTaskIds);
|
|
72
72
|
return orchestrator.run();
|
|
73
73
|
}
|
|
74
74
|
exports.default = exports.defaultTasksRunner;
|
|
@@ -720,7 +720,7 @@ async function invokeTasksRunner({ tasks, projectGraph, taskGraph, lifeCycle, nx
|
|
|
720
720
|
// this is used for two reasons: to fetch all remote cache hits AND
|
|
721
721
|
// to submit everything that is known in advance to Nx Cloud to run in
|
|
722
722
|
// a distributed fashion
|
|
723
|
-
const
|
|
723
|
+
const hashingDeferredTaskIds = await (0, hash_task_1.hashTasksThatDoNotDependOnOutputsOfOtherTasks)(hasher, projectGraph, taskGraph, nxJson, taskDetails, ultracacheConfigurations);
|
|
724
724
|
reportUltracacheConfigurations(ultracacheConfigurationOutcome, projectGraph, taskGraph, nxArgs);
|
|
725
725
|
const taskResultsLifecycle = new task_results_life_cycle_1.TaskResultsLifeCycle();
|
|
726
726
|
const compositedLifeCycle = new life_cycle_1.CompositeLifeCycle([
|
|
@@ -791,7 +791,7 @@ async function invokeTasksRunner({ tasks, projectGraph, taskGraph, lifeCycle, nx
|
|
|
791
791
|
},
|
|
792
792
|
},
|
|
793
793
|
daemon: client_1.daemonClient,
|
|
794
|
-
|
|
794
|
+
hashingDeferredTaskIds,
|
|
795
795
|
});
|
|
796
796
|
if (promiseOrObservable.subscribe) {
|
|
797
797
|
promiseOrObservable = convertObservableToPromise(promiseOrObservable);
|
|
@@ -80,6 +80,12 @@ class BatchProcess {
|
|
|
80
80
|
for (const cb of this.batchResultsCallbacks) {
|
|
81
81
|
cb(message.results);
|
|
82
82
|
}
|
|
83
|
+
// The worker runs a single batch. Its IPC channel would otherwise
|
|
84
|
+
// keep it alive until cleanup kills it, which waits out the grace
|
|
85
|
+
// period on Windows, where SIGTERM is a no-op.
|
|
86
|
+
if (this.childProcess.connected) {
|
|
87
|
+
this.childProcess.disconnect();
|
|
88
|
+
}
|
|
83
89
|
break;
|
|
84
90
|
}
|
|
85
91
|
case batch_messages_1.BatchMessageType.RunTasks: {
|
|
@@ -28,7 +28,7 @@ export declare class TaskOrchestrator {
|
|
|
28
28
|
private readonly resolvedOutputStyle;
|
|
29
29
|
private readonly fullTaskGraph;
|
|
30
30
|
/** Tasks the up-front pass left to hash once the tasks they read from have run. */
|
|
31
|
-
private readonly
|
|
31
|
+
private readonly hashingDeferredTaskIds?;
|
|
32
32
|
private taskDetails;
|
|
33
33
|
private cache;
|
|
34
34
|
private readonly tuiEnabled;
|
|
@@ -79,7 +79,7 @@ export declare class TaskOrchestrator {
|
|
|
79
79
|
/** What this run renders with, after defaults. */
|
|
80
80
|
resolvedOutputStyle: OutputStyle, fullTaskGraph?: TaskGraph,
|
|
81
81
|
/** Tasks the up-front pass left to hash once the tasks they read from have run. */
|
|
82
|
-
|
|
82
|
+
hashingDeferredTaskIds?: ReadonlySet<string>);
|
|
83
83
|
init(): Promise<void>;
|
|
84
84
|
run(): Promise<{
|
|
85
85
|
[k: string]: TaskStatus;
|
|
@@ -146,14 +146,18 @@ export declare class TaskOrchestrator {
|
|
|
146
146
|
/**
|
|
147
147
|
* Hash all batch tasks and resolve cache hits topologically.
|
|
148
148
|
*
|
|
149
|
-
* Walks the task graph level by level.
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
149
|
+
* Walks the task graph level by level. A deferred task (its hash reads
|
|
150
|
+
* another task's outputs) behind an upstream batch task that still has to
|
|
151
|
+
* run is never looked up yet:
|
|
152
|
+
* - behind a non-cacheable task only, it is held back for a later wave,
|
|
153
|
+
* where it hashes after that task ran;
|
|
154
|
+
* - behind a cache miss, it runs in this wave and re-hashes after execution.
|
|
155
|
+
* Every task run in this wave gets a hash first, for Cloud's startTasks.
|
|
153
156
|
*/
|
|
154
157
|
private applyBatchCachedResults;
|
|
155
158
|
private hashBatchTasks;
|
|
156
159
|
applyFromCacheOrRunBatch(doNotSkipCache: boolean, batch: Batch, groupId: number): Promise<TaskResult[]>;
|
|
160
|
+
private runBatchWave;
|
|
157
161
|
private runBatch;
|
|
158
162
|
/**
|
|
159
163
|
* Tells the life cycle where the batch worker's own log is, when something
|
|
@@ -80,7 +80,7 @@ class TaskOrchestrator {
|
|
|
80
80
|
/** What this run renders with, after defaults. */
|
|
81
81
|
resolvedOutputStyle, fullTaskGraph = taskGraph,
|
|
82
82
|
/** Tasks the up-front pass left to hash once the tasks they read from have run. */
|
|
83
|
-
|
|
83
|
+
hashingDeferredTaskIds) {
|
|
84
84
|
this.hasher = hasher;
|
|
85
85
|
this.initiatingProject = initiatingProject;
|
|
86
86
|
this.initiatingTasks = initiatingTasks;
|
|
@@ -93,7 +93,7 @@ class TaskOrchestrator {
|
|
|
93
93
|
this.specifiedOutputStyle = specifiedOutputStyle;
|
|
94
94
|
this.resolvedOutputStyle = resolvedOutputStyle;
|
|
95
95
|
this.fullTaskGraph = fullTaskGraph;
|
|
96
|
-
this.
|
|
96
|
+
this.hashingDeferredTaskIds = hashingDeferredTaskIds;
|
|
97
97
|
this.taskDetails = (0, hash_task_1.getTaskDetails)();
|
|
98
98
|
this.cache = (0, cache_1.getCache)(this.options);
|
|
99
99
|
this.tuiEnabled = (0, is_tui_enabled_1.isTuiEnabled)();
|
|
@@ -573,37 +573,63 @@ class TaskOrchestrator {
|
|
|
573
573
|
/**
|
|
574
574
|
* Hash all batch tasks and resolve cache hits topologically.
|
|
575
575
|
*
|
|
576
|
-
* Walks the task graph level by level.
|
|
577
|
-
*
|
|
578
|
-
*
|
|
579
|
-
*
|
|
576
|
+
* Walks the task graph level by level. A deferred task (its hash reads
|
|
577
|
+
* another task's outputs) behind an upstream batch task that still has to
|
|
578
|
+
* run is never looked up yet:
|
|
579
|
+
* - behind a non-cacheable task only, it is held back for a later wave,
|
|
580
|
+
* where it hashes after that task ran;
|
|
581
|
+
* - behind a cache miss, it runs in this wave and re-hashes after execution.
|
|
582
|
+
* Every task run in this wave gets a hash first, for Cloud's startTasks.
|
|
580
583
|
*/
|
|
581
584
|
async applyBatchCachedResults(batch, doNotSkipCache, groupId) {
|
|
582
585
|
const cachedResults = [];
|
|
583
|
-
const
|
|
586
|
+
const rehashAfterRun = new Set();
|
|
584
587
|
const tasks = Object.values(batch.taskGraph.tasks);
|
|
585
588
|
if (!doNotSkipCache) {
|
|
586
589
|
// Cache skipped — just hash so startTasks has valid hashes
|
|
587
590
|
await this.hashBatchTasks(tasks);
|
|
588
|
-
return { cachedResults,
|
|
589
|
-
}
|
|
590
|
-
const
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
591
|
+
return { cachedResults, tasksToRun: tasks, rehashAfterRun };
|
|
592
|
+
}
|
|
593
|
+
const toRun = new Map();
|
|
594
|
+
// Neither restored nor run in this wave; a later wave picks them up.
|
|
595
|
+
const heldBack = new Set();
|
|
596
|
+
// Tasks with a cache miss / a non-cacheable task upstream that still has
|
|
597
|
+
// to run in this wave.
|
|
598
|
+
const behindMiss = new Set();
|
|
599
|
+
const behindUncacheable = new Set();
|
|
600
|
+
const pending = (id, cacheable) => toRun.has(id) && !!toRun.get(id).cache === cacheable;
|
|
601
|
+
await (0, task_graph_utils_1.walkTaskGraph)(batch.taskGraph, async (levelIds) => {
|
|
602
|
+
const runNow = [];
|
|
603
|
+
const lookUp = [];
|
|
604
|
+
for (const id of levelIds) {
|
|
605
|
+
const task = batch.taskGraph.tasks[id];
|
|
606
|
+
const deps = batch.taskGraph.dependencies[id].map((dep) => dep.id);
|
|
607
|
+
if (deps.some((dep) => heldBack.has(dep))) {
|
|
608
|
+
heldBack.add(id);
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
const afterMiss = deps.some((dep) => behindMiss.has(dep) || pending(dep, true));
|
|
612
|
+
const afterUncacheable = deps.some((dep) => behindUncacheable.has(dep) || pending(dep, false));
|
|
613
|
+
if (afterMiss)
|
|
614
|
+
behindMiss.add(id);
|
|
615
|
+
if (afterUncacheable)
|
|
616
|
+
behindUncacheable.add(id);
|
|
617
|
+
const deferred = this.hashingDeferredTaskIds?.has(id);
|
|
618
|
+
if (deferred && afterMiss) {
|
|
619
|
+
runNow.push(task);
|
|
620
|
+
toRun.set(id, task);
|
|
621
|
+
rehashAfterRun.add(id);
|
|
622
|
+
}
|
|
623
|
+
else if (deferred && afterUncacheable) {
|
|
624
|
+
heldBack.add(id);
|
|
600
625
|
}
|
|
601
626
|
else {
|
|
602
|
-
|
|
627
|
+
lookUp.push(task);
|
|
603
628
|
}
|
|
604
629
|
}
|
|
605
|
-
|
|
606
|
-
|
|
630
|
+
await this.hashBatchTasks([...runNow, ...lookUp]);
|
|
631
|
+
if (lookUp.length > 0) {
|
|
632
|
+
const cacheResults = await this.applyCachedResults(lookUp);
|
|
607
633
|
const cachedIds = new Set(cacheResults.map((r) => r.task.id));
|
|
608
634
|
cachedResults.push(...cacheResults);
|
|
609
635
|
if (cacheResults.length > 0) {
|
|
@@ -613,14 +639,14 @@ class TaskOrchestrator {
|
|
|
613
639
|
// Replayed from the cache — don't write the results back.
|
|
614
640
|
await this.postRunSteps(cacheResults, false, groupId);
|
|
615
641
|
}
|
|
616
|
-
for (const task of
|
|
642
|
+
for (const task of lookUp) {
|
|
617
643
|
if (!cachedIds.has(task.id)) {
|
|
618
|
-
|
|
644
|
+
toRun.set(task.id, task);
|
|
619
645
|
}
|
|
620
646
|
}
|
|
621
647
|
}
|
|
622
648
|
});
|
|
623
|
-
return { cachedResults,
|
|
649
|
+
return { cachedResults, tasksToRun: [...toRun.values()], rehashAfterRun };
|
|
624
650
|
}
|
|
625
651
|
async hashBatchTasks(tasks) {
|
|
626
652
|
// Batch executors run every task in the same forked process, but
|
|
@@ -634,29 +660,52 @@ class TaskOrchestrator {
|
|
|
634
660
|
await (0, hash_task_1.hashTasks)(this.hasher, this.projectGraph, this.fullTaskGraph, perTaskEnvs, this.taskDetails, tasks);
|
|
635
661
|
}
|
|
636
662
|
async applyFromCacheOrRunBatch(doNotSkipCache, batch, groupId) {
|
|
663
|
+
if (this.bailed || this.stopRequested) {
|
|
664
|
+
return [];
|
|
665
|
+
}
|
|
637
666
|
const applyFromCacheOrRunBatchStart = perf_hooks_1.performance.mark('TaskOrchestrator-apply-from-cache-or-run-batch:start');
|
|
638
|
-
const
|
|
639
|
-
const tasks = taskEntries.map(([, task]) => task);
|
|
667
|
+
const taskIds = Object.keys(batch.taskGraph.tasks);
|
|
640
668
|
this.options.lifeCycle.registerRunningBatch?.(batch.id, {
|
|
641
669
|
executorName: batch.executorName,
|
|
642
|
-
taskIds
|
|
670
|
+
taskIds,
|
|
671
|
+
});
|
|
672
|
+
// Each wave restores or runs what it can; tasks held back for a later
|
|
673
|
+
// wave (or left out by the executor) stay incomplete and go again.
|
|
674
|
+
const results = [];
|
|
675
|
+
let taskGraph = batch.taskGraph;
|
|
676
|
+
while (Object.keys(taskGraph.tasks).length > 0 &&
|
|
677
|
+
!this.bailed &&
|
|
678
|
+
!this.stopRequested) {
|
|
679
|
+
results.push(...(await this.runBatchWave(doNotSkipCache, { ...batch, taskGraph }, groupId)));
|
|
680
|
+
this.forkedProcessTaskRunner.cleanUpBatchProcesses();
|
|
681
|
+
taskGraph = (0, utils_1.removeTasksFromTaskGraph)(taskGraph, Object.keys(taskGraph.tasks).filter((id) => this.completedTasks.has(id)));
|
|
682
|
+
}
|
|
683
|
+
// Once, after every wave: an earlier status would end the batch in the TUI.
|
|
684
|
+
const hasFailures = taskIds.some((taskId) => {
|
|
685
|
+
const status = this.completedTasks.get(taskId);
|
|
686
|
+
return status === 'failure' || status === 'skipped';
|
|
643
687
|
});
|
|
644
|
-
|
|
688
|
+
this.options.lifeCycle.setBatchStatus?.(batch.id, hasFailures ? "Failure" /* BatchStatus.Failure */ : "Success" /* BatchStatus.Success */);
|
|
689
|
+
const applyFromCacheOrRunBatchEnd = perf_hooks_1.performance.mark('TaskOrchestrator-apply-from-cache-or-run-batch:end');
|
|
690
|
+
perf_hooks_1.performance.measure('TaskOrchestrator-apply-from-cache-or-run-batch', applyFromCacheOrRunBatchStart.name, applyFromCacheOrRunBatchEnd.name);
|
|
691
|
+
return results;
|
|
692
|
+
}
|
|
693
|
+
async runBatchWave(doNotSkipCache, batch, groupId) {
|
|
694
|
+
const tasks = Object.values(batch.taskGraph.tasks);
|
|
695
|
+
const { cachedResults, tasksToRun: wave, rehashAfterRun, } = await this.applyBatchCachedResults(batch, doNotSkipCache, groupId);
|
|
645
696
|
// Cached tasks were started and completed inside applyBatchCachedResults;
|
|
646
697
|
// a member skipped meanwhile (a dependency failed) must not run either
|
|
647
|
-
const
|
|
648
|
-
if (
|
|
649
|
-
await Promise.all(
|
|
650
|
-
await this.preRunSteps(
|
|
698
|
+
const tasksToRun = wave.filter((t) => !this.completedTasks.has(t.id));
|
|
699
|
+
if (tasksToRun.length > 0) {
|
|
700
|
+
await Promise.all(tasksToRun.map((task) => this.options.lifeCycle.scheduleTask(task)));
|
|
701
|
+
await this.preRunSteps(tasksToRun, { groupId });
|
|
651
702
|
}
|
|
652
|
-
// Phase 2: Run
|
|
703
|
+
// Phase 2: Run this wave's tasks, then re-hash those run without a lookup
|
|
653
704
|
let batchResults = [];
|
|
654
705
|
// Read again: a member can be skipped while the hooks above ran
|
|
655
|
-
const
|
|
656
|
-
|
|
657
|
-
.map((t) => t.id);
|
|
658
|
-
if (taskIdsToSkip.length < tasks.length) {
|
|
659
|
-
const runGraph = (0, utils_1.removeTasksFromTaskGraph)(batch.taskGraph, taskIdsToSkip);
|
|
706
|
+
const runIds = new Set(tasksToRun.filter((t) => !this.completedTasks.has(t.id)).map((t) => t.id));
|
|
707
|
+
if (runIds.size > 0) {
|
|
708
|
+
const runGraph = (0, utils_1.removeTasksFromTaskGraph)(batch.taskGraph, tasks.filter((t) => !runIds.has(t.id)).map((t) => t.id));
|
|
660
709
|
for (const task of Object.values(runGraph.tasks)) {
|
|
661
710
|
this.detectTaskInvocationLoop(task);
|
|
662
711
|
}
|
|
@@ -667,7 +716,7 @@ class TaskOrchestrator {
|
|
|
667
716
|
}, this.batchEnv, groupId);
|
|
668
717
|
// Re-hash tasks that read dep outputs — those outputs are now on disk
|
|
669
718
|
const tasksToRehash = batchResults
|
|
670
|
-
.filter((r) =>
|
|
719
|
+
.filter((r) => rehashAfterRun.has(r.task.id) &&
|
|
671
720
|
(r.status === 'success' || r.status === 'failure'))
|
|
672
721
|
.map((r) => r.task);
|
|
673
722
|
if (tasksToRehash.length > 0) {
|
|
@@ -683,25 +732,6 @@ class TaskOrchestrator {
|
|
|
683
732
|
if (batchResults.length > 0) {
|
|
684
733
|
await this.postRunSteps(batchResults, doNotSkipCache, groupId);
|
|
685
734
|
}
|
|
686
|
-
// Update batch status based on all task results
|
|
687
|
-
const hasFailures = taskEntries.some(([taskId]) => {
|
|
688
|
-
const status = this.completedTasks.get(taskId);
|
|
689
|
-
return status === 'failure' || status === 'skipped';
|
|
690
|
-
});
|
|
691
|
-
this.options.lifeCycle.setBatchStatus?.(batch.id, hasFailures ? "Failure" /* BatchStatus.Failure */ : "Success" /* BatchStatus.Success */);
|
|
692
|
-
this.forkedProcessTaskRunner.cleanUpBatchProcesses();
|
|
693
|
-
const tasksCompleted = taskEntries.filter(([taskId]) => this.completedTasks.has(taskId));
|
|
694
|
-
// Batch is still not done, run it again
|
|
695
|
-
if (tasksCompleted.length !== taskEntries.length) {
|
|
696
|
-
await this.applyFromCacheOrRunBatch(doNotSkipCache, {
|
|
697
|
-
id: batch.id,
|
|
698
|
-
executorName: batch.executorName,
|
|
699
|
-
taskGraph: (0, utils_1.removeTasksFromTaskGraph)(batch.taskGraph, tasksCompleted.map(([taskId]) => taskId)),
|
|
700
|
-
}, groupId);
|
|
701
|
-
}
|
|
702
|
-
// Batch is done, mark it as completed
|
|
703
|
-
const applyFromCacheOrRunBatchEnd = perf_hooks_1.performance.mark('TaskOrchestrator-apply-from-cache-or-run-batch:end');
|
|
704
|
-
perf_hooks_1.performance.measure('TaskOrchestrator-apply-from-cache-or-run-batch', applyFromCacheOrRunBatchStart.name, applyFromCacheOrRunBatchEnd.name);
|
|
705
735
|
return [...cachedResults, ...batchResults];
|
|
706
736
|
}
|
|
707
737
|
async runBatch(batch, env, groupId) {
|
|
@@ -20,7 +20,7 @@ export type TasksRunner<T = unknown> = (tasks: Task[], options: T, context?: {
|
|
|
20
20
|
hasher?: TaskHasher;
|
|
21
21
|
daemon?: DaemonClient;
|
|
22
22
|
/** Tasks hashed only once the tasks they read outputs from have run. */
|
|
23
|
-
|
|
23
|
+
hashingDeferredTaskIds?: ReadonlySet<string>;
|
|
24
24
|
}) => any | Promise<{
|
|
25
25
|
[id: string]: TaskStatus;
|
|
26
26
|
}>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nx",
|
|
3
|
-
"version": "24.0.0-beta.
|
|
3
|
+
"version": "24.0.0-beta.2",
|
|
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.",
|
|
@@ -150,16 +150,16 @@
|
|
|
150
150
|
}
|
|
151
151
|
},
|
|
152
152
|
"optionalDependencies": {
|
|
153
|
-
"@nx/nx-darwin-arm64": "24.0.0-beta.
|
|
154
|
-
"@nx/nx-darwin-x64": "24.0.0-beta.
|
|
155
|
-
"@nx/nx-freebsd-x64": "24.0.0-beta.
|
|
156
|
-
"@nx/nx-linux-arm-gnueabihf": "24.0.0-beta.
|
|
157
|
-
"@nx/nx-linux-arm64-gnu": "24.0.0-beta.
|
|
158
|
-
"@nx/nx-linux-arm64-musl": "24.0.0-beta.
|
|
159
|
-
"@nx/nx-linux-x64-gnu": "24.0.0-beta.
|
|
160
|
-
"@nx/nx-linux-x64-musl": "24.0.0-beta.
|
|
161
|
-
"@nx/nx-win32-arm64-msvc": "24.0.0-beta.
|
|
162
|
-
"@nx/nx-win32-x64-msvc": "24.0.0-beta.
|
|
153
|
+
"@nx/nx-darwin-arm64": "24.0.0-beta.2",
|
|
154
|
+
"@nx/nx-darwin-x64": "24.0.0-beta.2",
|
|
155
|
+
"@nx/nx-freebsd-x64": "24.0.0-beta.2",
|
|
156
|
+
"@nx/nx-linux-arm-gnueabihf": "24.0.0-beta.2",
|
|
157
|
+
"@nx/nx-linux-arm64-gnu": "24.0.0-beta.2",
|
|
158
|
+
"@nx/nx-linux-arm64-musl": "24.0.0-beta.2",
|
|
159
|
+
"@nx/nx-linux-x64-gnu": "24.0.0-beta.2",
|
|
160
|
+
"@nx/nx-linux-x64-musl": "24.0.0-beta.2",
|
|
161
|
+
"@nx/nx-win32-arm64-msvc": "24.0.0-beta.2",
|
|
162
|
+
"@nx/nx-win32-x64-msvc": "24.0.0-beta.2"
|
|
163
163
|
},
|
|
164
164
|
"nx-migrations": {
|
|
165
165
|
"migrations": "./migrations.json",
|
|
@@ -192,6 +192,7 @@
|
|
|
192
192
|
"@nx/plugin",
|
|
193
193
|
"@nx/react",
|
|
194
194
|
"@nx/react-native",
|
|
195
|
+
"@nx/rolldown",
|
|
195
196
|
"@nx/rollup",
|
|
196
197
|
"@nx/remix",
|
|
197
198
|
"@nx/rspack",
|