nx 24.0.0-beta.0 → 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/nx-cloud-internals.d.ts +1 -0
- package/dist/nx-cloud-internals.js +7 -1
- package/dist/src/command-line/graph/graph.js +2 -1
- package/dist/src/command-line/migrate/agentic/close-agent-session.d.ts +1 -1
- package/dist/src/command-line/migrate/agentic/close-agent-session.js +4 -3
- 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 +82 -22
- 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/execute-migration.d.ts +2 -2
- package/dist/src/command-line/migrate/execute-migration.js +18 -10
- 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-commits.d.ts +4 -1
- package/dist/src/command-line/migrate/migrate-commits.js +10 -3
- 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 +206 -237
- package/dist/src/command-line/migrate/run/agent-output.d.ts +1 -0
- package/dist/src/command-line/migrate/run/agent-output.js +5 -0
- package/dist/src/command-line/migrate/run/broker.d.ts +14 -5
- package/dist/src/command-line/migrate/run/broker.js +68 -20
- 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 +6 -5
- package/dist/src/command-line/migrate/run/index.js +12 -1
- package/dist/src/command-line/migrate/run/orchestrator.d.ts +21 -0
- package/dist/src/command-line/migrate/run/orchestrator.js +149 -32
- 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/run/runbook.js +19 -0
- package/dist/src/command-line/migrate/run/state-machine.js +4 -1
- package/dist/src/command-line/migrate/run/util.d.ts +3 -0
- package/dist/src/command-line/migrate/run/util.js +7 -1
- 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/command-line/show/show-target/info.js +1 -1
- package/dist/src/config/nx-json.d.ts +13 -4
- package/dist/src/config/task-graph.d.ts +1 -1
- package/dist/src/config/workspace-json-project-json.d.ts +72 -0
- package/dist/src/core/graph/main.js +1 -1
- package/dist/src/devkit-exports.d.ts +1 -1
- package/dist/src/hasher/check-task-files.js +4 -4
- package/dist/src/hasher/native-task-hasher-impl.js +3 -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 +92 -14
- package/dist/src/native/native-bindings.js +3 -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/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/create-task-graph.d.ts +7 -16
- package/dist/src/tasks-runner/create-task-graph.js +43 -57
- package/dist/src/tasks-runner/default-tasks-runner.js +1 -1
- package/dist/src/tasks-runner/init-tasks-runner.js +13 -0
- package/dist/src/tasks-runner/life-cycle.d.ts +4 -1
- package/dist/src/tasks-runner/life-cycle.js +7 -0
- package/dist/src/tasks-runner/life-cycles/performance-analysis.d.ts +5 -2
- package/dist/src/tasks-runner/life-cycles/performance-analysis.js +13 -7
- package/dist/src/tasks-runner/life-cycles/performance-life-cycle.d.ts +4 -3
- package/dist/src/tasks-runner/life-cycles/performance-life-cycle.js +9 -2
- package/dist/src/tasks-runner/readiness/probes.d.ts +9 -0
- package/dist/src/tasks-runner/readiness/probes.js +62 -0
- package/dist/src/tasks-runner/readiness/ready-when.d.ts +38 -0
- package/dist/src/tasks-runner/readiness/ready-when.js +190 -0
- package/dist/src/tasks-runner/run-command.js +7 -6
- package/dist/src/tasks-runner/running-tasks/batch-process.js +6 -0
- package/dist/src/tasks-runner/running-tasks/node-child-process.js +5 -4
- package/dist/src/tasks-runner/task-graph-utils.d.ts +9 -6
- package/dist/src/tasks-runner/task-graph-utils.js +17 -12
- package/dist/src/tasks-runner/task-orchestrator.d.ts +37 -10
- package/dist/src/tasks-runner/task-orchestrator.js +459 -79
- package/dist/src/tasks-runner/tasks-runner.d.ts +1 -1
- package/dist/src/tasks-runner/tasks-schedule.d.ts +18 -1
- package/dist/src/tasks-runner/tasks-schedule.js +64 -6
- package/dist/src/tasks-runner/utils.js +6 -22
- package/dist/src/utils/child-process.js +1 -0
- package/dist/src/utils/git-utils.d.ts +6 -1
- package/dist/src/utils/git-utils.js +26 -5
- package/dist/src/utils/plugins/core-plugins.js +4 -0
- package/dist/src/utils/spawn-without-terminal.d.ts +27 -0
- package/dist/src/utils/spawn-without-terminal.js +110 -0
- package/package.json +12 -11
- package/schemas/nx-schema.json +93 -4
- package/schemas/project-schema.json +84 -2
|
@@ -13,6 +13,7 @@ const shared_running_task_1 = require("./running-tasks/shared-running-task");
|
|
|
13
13
|
const task_results_life_cycle_1 = require("./life-cycles/task-results-life-cycle");
|
|
14
14
|
async function createOrchestrator(tasks, projectGraph, fullTaskGraph, nxJson, lifeCycle, ultracacheConfigurations) {
|
|
15
15
|
(0, dotenv_1.loadRootEnvFiles)();
|
|
16
|
+
fullTaskGraph = withEdgeObjects(fullTaskGraph);
|
|
16
17
|
const invokeRunnerTerminalLifecycle = new invoke_runner_terminal_output_life_cycle_1.InvokeRunnerTerminalOutputLifeCycle(tasks);
|
|
17
18
|
const taskResultsLifecycle = new task_results_life_cycle_1.TaskResultsLifeCycle();
|
|
18
19
|
const compositedLifeCycle = new life_cycle_1.CompositeLifeCycle([
|
|
@@ -52,6 +53,18 @@ async function createOrchestrator(tasks, projectGraph, fullTaskGraph, nxJson, li
|
|
|
52
53
|
orchestrator.processAllScheduledTasks();
|
|
53
54
|
return orchestrator;
|
|
54
55
|
}
|
|
56
|
+
// A client bundle built before edges became objects hands plain task ids
|
|
57
|
+
function withEdgeObjects(taskGraph) {
|
|
58
|
+
const normalize = (edges) => Object.fromEntries(Object.entries(edges).map(([taskId, taskEdges]) => [
|
|
59
|
+
taskId,
|
|
60
|
+
taskEdges.map((edge) => typeof edge === 'string' ? { id: edge } : edge),
|
|
61
|
+
]));
|
|
62
|
+
return {
|
|
63
|
+
...taskGraph,
|
|
64
|
+
dependencies: normalize(taskGraph.dependencies),
|
|
65
|
+
continuousDependencies: normalize(taskGraph.continuousDependencies),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
55
68
|
// Nothing awaits the dispose chains below, so an unhandled rejection would take
|
|
56
69
|
// down a long-lived agent process.
|
|
57
70
|
function logDisposeFailure(e) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Task } from '../config/task-graph';
|
|
2
|
-
import { BatchInfo, BatchStatus, ExternalObject, PerformanceSummaryPayload, TaskResult, TaskStatus as NativeTaskStatus } from '../native';
|
|
2
|
+
import { BatchInfo, BatchStatus, ExternalObject, PerformanceSummaryPayload, TaskReadiness, TaskResult, TaskStatus as NativeTaskStatus } from '../native';
|
|
3
3
|
import { TaskStatus } from './tasks-runner';
|
|
4
4
|
/**
|
|
5
5
|
* The result of a completed {@link Task}.
|
|
@@ -48,6 +48,8 @@ export interface LifeCycle {
|
|
|
48
48
|
registerRunningTaskWithEmptyParser?(taskId: string): void;
|
|
49
49
|
appendTaskOutput?(taskId: string, output: string, isPtyTask: boolean): void;
|
|
50
50
|
setTaskStatus?(taskId: string, status: NativeTaskStatus): void;
|
|
51
|
+
/** Only continuous tasks that declare a `readyWhen` report readiness. */
|
|
52
|
+
setTaskReadiness?(taskId: string, readiness: TaskReadiness): void;
|
|
51
53
|
setTaskTiming?(taskId: string, startTime: number, endTime: number): void;
|
|
52
54
|
registerForcedShutdownCallback?(callback: () => void): void;
|
|
53
55
|
setEstimatedTaskTimings?(timings: Record<string, number>): void;
|
|
@@ -82,6 +84,7 @@ export declare class CompositeLifeCycle implements LifeCycle {
|
|
|
82
84
|
registerRunningTaskWithEmptyParser(taskId: string): void;
|
|
83
85
|
appendTaskOutput(taskId: string, output: string, isPtyTask: boolean): void;
|
|
84
86
|
setTaskStatus(taskId: string, status: NativeTaskStatus): void;
|
|
87
|
+
setTaskReadiness(taskId: string, readiness: TaskReadiness): void;
|
|
85
88
|
setTaskTiming(taskId: string, startTime: number, endTime: number): void;
|
|
86
89
|
registerForcedShutdownCallback(callback: () => void): void;
|
|
87
90
|
setEstimatedTaskTimings(timings: Record<string, number>): void;
|
|
@@ -95,6 +95,13 @@ class CompositeLifeCycle {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
+
setTaskReadiness(taskId, readiness) {
|
|
99
|
+
for (let l of this.lifeCycles) {
|
|
100
|
+
if (l.setTaskReadiness) {
|
|
101
|
+
l.setTaskReadiness(taskId, readiness);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
98
105
|
setTaskTiming(taskId, startTime, endTime) {
|
|
99
106
|
for (let l of this.lifeCycles) {
|
|
100
107
|
if (l.setTaskTiming) {
|
|
@@ -4,6 +4,8 @@ import { TaskResult } from '../life-cycle';
|
|
|
4
4
|
export interface TaskTiming {
|
|
5
5
|
startTime?: number;
|
|
6
6
|
endTime?: number;
|
|
7
|
+
/** When a continuous task passed its `readyWhen` check. */
|
|
8
|
+
readyTime?: number;
|
|
7
9
|
continuous: boolean;
|
|
8
10
|
}
|
|
9
11
|
/** A discrete task that ran, with its window and whether it monopolizes the pool (`parallelism: false`). */
|
|
@@ -122,8 +124,9 @@ export declare class PerformanceAnalysis {
|
|
|
122
124
|
private finishEstimate;
|
|
123
125
|
/**
|
|
124
126
|
* Earliest this task became eligible, independent of slots: the latest of run
|
|
125
|
-
* start, dependency ends, any continuous dependency's *start
|
|
126
|
-
*
|
|
127
|
+
* start, dependency ends, any continuous dependency's *start*, or its
|
|
128
|
+
* readiness when the edge waits for it (an ordering constraint, not
|
|
129
|
+
* contention), and any earlier batch sibling's end.
|
|
127
130
|
*/
|
|
128
131
|
private readyTime;
|
|
129
132
|
/**
|
|
@@ -101,7 +101,9 @@ class PerformanceAnalysis {
|
|
|
101
101
|
*/
|
|
102
102
|
predecessorsFor(id, durations) {
|
|
103
103
|
const start = this.timings.get(id)?.startTime ?? Infinity;
|
|
104
|
-
const deps = (this.taskGraph.dependencies[id] ?? [])
|
|
104
|
+
const deps = (this.taskGraph.dependencies[id] ?? [])
|
|
105
|
+
.map((d) => d.id)
|
|
106
|
+
.filter((d) => durations.has(d));
|
|
105
107
|
const siblings = (this.batchSiblings.get(id) ?? []).filter((s) => {
|
|
106
108
|
const sEnd = this.timings.get(s)?.endTime;
|
|
107
109
|
return durations.has(s) && sEnd != null && sEnd <= start;
|
|
@@ -142,22 +144,26 @@ class PerformanceAnalysis {
|
|
|
142
144
|
}
|
|
143
145
|
/**
|
|
144
146
|
* Earliest this task became eligible, independent of slots: the latest of run
|
|
145
|
-
* start, dependency ends, any continuous dependency's *start
|
|
146
|
-
*
|
|
147
|
+
* start, dependency ends, any continuous dependency's *start*, or its
|
|
148
|
+
* readiness when the edge waits for it (an ordering constraint, not
|
|
149
|
+
* contention), and any earlier batch sibling's end.
|
|
147
150
|
*/
|
|
148
151
|
readyTime(id, runStart) {
|
|
149
152
|
const start = this.timings.get(id)?.startTime;
|
|
150
153
|
let result = runStart;
|
|
151
154
|
for (const dep of this.taskGraph.dependencies[id] ?? []) {
|
|
152
|
-
const end = this.timings.get(dep)?.endTime;
|
|
155
|
+
const end = this.timings.get(dep.id)?.endTime;
|
|
153
156
|
if (end != null) {
|
|
154
157
|
result = Math.max(result, end);
|
|
155
158
|
}
|
|
156
159
|
}
|
|
157
160
|
for (const cdep of this.taskGraph.continuousDependencies?.[id] ?? []) {
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
+
const producer = this.timings.get(cdep.id);
|
|
162
|
+
const usable = cdep.waitFor === 'ready'
|
|
163
|
+
? (producer?.readyTime ?? producer?.startTime)
|
|
164
|
+
: producer?.startTime;
|
|
165
|
+
if (usable != null) {
|
|
166
|
+
result = Math.max(result, usable);
|
|
161
167
|
}
|
|
162
168
|
}
|
|
163
169
|
for (const sibling of this.batchSiblings.get(id) ?? []) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { TaskReadiness, type BatchInfo, type PerformanceSummaryPayload } from '../../native';
|
|
2
2
|
import { TaskGraph } from '../../config/task-graph';
|
|
3
3
|
import { LifeCycle, TaskResult } from '../life-cycle';
|
|
4
4
|
import { type PerformanceLifeCycleOptions, type PerformanceSummary } from './performance-analysis';
|
|
@@ -14,8 +14,8 @@ import { type PerformanceLifeCycleOptions, type PerformanceSummary } from './per
|
|
|
14
14
|
* coordinator time (hashing, scheduling, continuous-dep waits).
|
|
15
15
|
*
|
|
16
16
|
* Scope: discrete tasks only. Continuous tasks (no end time) are excluded; a
|
|
17
|
-
* discrete task's wait for a continuous dependency to start
|
|
18
|
-
* contention.
|
|
17
|
+
* discrete task's wait for a continuous dependency to start, or to be ready, is
|
|
18
|
+
* eligibility, not contention.
|
|
19
19
|
*/
|
|
20
20
|
export declare class PerformanceLifeCycle implements LifeCycle {
|
|
21
21
|
private readonly taskGraph;
|
|
@@ -34,6 +34,7 @@ export declare class PerformanceLifeCycle implements LifeCycle {
|
|
|
34
34
|
*/
|
|
35
35
|
startCommand(_threadCount?: number, parallel?: number): void;
|
|
36
36
|
registerRunningBatch(_batchId: string, batchInfo: BatchInfo): void;
|
|
37
|
+
setTaskReadiness(taskId: string, readiness: TaskReadiness): void;
|
|
37
38
|
endTasks(taskResults: TaskResult[]): void;
|
|
38
39
|
private entry;
|
|
39
40
|
/** Analyze the collected timings into a structured summary, or `null` when no discrete task timings were recorded. */
|
|
@@ -20,8 +20,8 @@ const performance_analysis_1 = require("./performance-analysis");
|
|
|
20
20
|
* coordinator time (hashing, scheduling, continuous-dep waits).
|
|
21
21
|
*
|
|
22
22
|
* Scope: discrete tasks only. Continuous tasks (no end time) are excluded; a
|
|
23
|
-
* discrete task's wait for a continuous dependency to start
|
|
24
|
-
* contention.
|
|
23
|
+
* discrete task's wait for a continuous dependency to start, or to be ready, is
|
|
24
|
+
* eligibility, not contention.
|
|
25
25
|
*/
|
|
26
26
|
class PerformanceLifeCycle {
|
|
27
27
|
constructor(taskGraph, options = {}) {
|
|
@@ -51,6 +51,13 @@ class PerformanceLifeCycle {
|
|
|
51
51
|
this.batchSiblings.set(id, batchInfo.taskIds.filter((other) => other !== id));
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
+
setTaskReadiness(taskId, readiness) {
|
|
55
|
+
const entry = this.entry(taskId);
|
|
56
|
+
// Each waiter polling a shared producer reports Ready; the first one counts.
|
|
57
|
+
if (readiness === 1 /* TaskReadiness.Ready */ && entry.readyTime == null) {
|
|
58
|
+
entry.readyTime = Date.now();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
54
61
|
endTasks(taskResults) {
|
|
55
62
|
// Called incrementally (per group/batch); accumulate so the last call sees every timing.
|
|
56
63
|
for (const { task, status } of taskResults) {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RunningTask } from '../running-tasks/running-task';
|
|
2
|
+
import { type NormalizedReadyWhen } from './ready-when';
|
|
3
|
+
export interface ReadinessProbeContext {
|
|
4
|
+
taskId: string;
|
|
5
|
+
runningTask: RunningTask;
|
|
6
|
+
cwd: string;
|
|
7
|
+
signal: AbortSignal;
|
|
8
|
+
}
|
|
9
|
+
export declare function waitForReadiness(readyWhen: NormalizedReadyWhen, context: ReadinessProbeContext): Promise<void>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.waitForReadiness = waitForReadiness;
|
|
4
|
+
const native_1 = require("../../native");
|
|
5
|
+
const ready_when_1 = require("./ready-when");
|
|
6
|
+
// Never rejects for a transient probe error: every failure is retried until
|
|
7
|
+
// the deadline.
|
|
8
|
+
async function waitForReadiness(readyWhen, context) {
|
|
9
|
+
if (native_1.IS_WASM) {
|
|
10
|
+
throw new Error(`The WASM build of Nx does not support "readyWhen", so "${context.taskId}" cannot be probed for readiness.`);
|
|
11
|
+
}
|
|
12
|
+
if (readyWhen.kind === 'logMatches') {
|
|
13
|
+
return waitForLogMatch(readyWhen, context);
|
|
14
|
+
}
|
|
15
|
+
const probe = new native_1.ReadinessProbe({
|
|
16
|
+
timeout: readyWhen.timeout,
|
|
17
|
+
interval: readyWhen.interval,
|
|
18
|
+
...(readyWhen.kind === 'url'
|
|
19
|
+
? { url: readyWhen.url }
|
|
20
|
+
: readyWhen.kind === 'port'
|
|
21
|
+
? { port: readyWhen.port, host: readyWhen.host }
|
|
22
|
+
: { command: readyWhen.command }),
|
|
23
|
+
}, context.cwd);
|
|
24
|
+
const cancel = () => probe.cancel();
|
|
25
|
+
context.signal.addEventListener('abort', cancel);
|
|
26
|
+
if (context.signal.aborted) {
|
|
27
|
+
probe.cancel();
|
|
28
|
+
}
|
|
29
|
+
let ready;
|
|
30
|
+
try {
|
|
31
|
+
ready = await probe.wait();
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
context.signal.removeEventListener('abort', cancel);
|
|
35
|
+
}
|
|
36
|
+
if (!ready) {
|
|
37
|
+
throw context.signal.aborted
|
|
38
|
+
? (0, ready_when_1.notReadyError)(context.taskId, 'exited')
|
|
39
|
+
: (0, ready_when_1.readinessTimeoutError)(context.taskId, readyWhen);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function waitForLogMatch(readyWhen, context) {
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
const matcher = new native_1.LogMatcher(readyWhen.logMatches);
|
|
45
|
+
let settled = false;
|
|
46
|
+
const settle = (error) => {
|
|
47
|
+
settled = true;
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
context.signal.removeEventListener('abort', onAbort);
|
|
50
|
+
error ? reject(error) : resolve();
|
|
51
|
+
};
|
|
52
|
+
const onAbort = () => settle((0, ready_when_1.notReadyError)(context.taskId, 'exited'));
|
|
53
|
+
const timer = setTimeout(() => settle((0, ready_when_1.readinessTimeoutError)(context.taskId, readyWhen)), readyWhen.timeout);
|
|
54
|
+
timer.unref();
|
|
55
|
+
context.signal.addEventListener('abort', onAbort);
|
|
56
|
+
context.runningTask.onOutput((chunk) => {
|
|
57
|
+
if (!settled && matcher.feed(chunk)) {
|
|
58
|
+
settle();
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ProjectGraph } from '../../config/project-graph';
|
|
2
|
+
import type { Task, TaskGraph } from '../../config/task-graph';
|
|
3
|
+
import type { ProjectConfiguration, ReadyWhen } from '../../config/workspace-json-project-json';
|
|
4
|
+
export type NormalizedReadyWhen = {
|
|
5
|
+
timeout: number;
|
|
6
|
+
interval?: number;
|
|
7
|
+
} & ({
|
|
8
|
+
kind: 'url';
|
|
9
|
+
url: string;
|
|
10
|
+
} | {
|
|
11
|
+
kind: 'port';
|
|
12
|
+
port: number;
|
|
13
|
+
host?: string;
|
|
14
|
+
} | {
|
|
15
|
+
kind: 'command';
|
|
16
|
+
command: string;
|
|
17
|
+
} | {
|
|
18
|
+
kind: 'logMatches';
|
|
19
|
+
logMatches: string[];
|
|
20
|
+
});
|
|
21
|
+
export interface ReadyWhenData {
|
|
22
|
+
workspaceRoot: string;
|
|
23
|
+
projectRoot: string;
|
|
24
|
+
projectName: string;
|
|
25
|
+
options: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
export declare function normalizeReadyWhen(readyWhen: ReadyWhen, taskId: string, data: ReadyWhenData): NormalizedReadyWhen;
|
|
28
|
+
export declare function readinessTimeoutError(taskId: string, readyWhen: NormalizedReadyWhen): Error;
|
|
29
|
+
export declare class ProducerFailedError extends Error {
|
|
30
|
+
constructor(taskId: string);
|
|
31
|
+
}
|
|
32
|
+
export declare class NotReadyError extends Error {
|
|
33
|
+
}
|
|
34
|
+
export declare function notReadyError(taskId: string, reason: 'exited' | 'was stopped' | 'failed'): Error;
|
|
35
|
+
export declare function getReadyWhenConfig(task: Task, projectGraph: ProjectGraph): ReadyWhen | undefined;
|
|
36
|
+
export declare function getReadyWhen(task: Task, projectGraph: ProjectGraph, projects: Record<string, ProjectConfiguration>, workspaceRoot: string): NormalizedReadyWhen | null;
|
|
37
|
+
export declare function getReadyProducerIds(task: Task, taskGraph: TaskGraph): string[];
|
|
38
|
+
export declare function getReadyDependencies(taskGraph: TaskGraph, projectGraph: ProjectGraph): Record<string, string[]>;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NotReadyError = exports.ProducerFailedError = void 0;
|
|
4
|
+
exports.normalizeReadyWhen = normalizeReadyWhen;
|
|
5
|
+
exports.readinessTimeoutError = readinessTimeoutError;
|
|
6
|
+
exports.notReadyError = notReadyError;
|
|
7
|
+
exports.getReadyWhenConfig = getReadyWhenConfig;
|
|
8
|
+
exports.getReadyWhen = getReadyWhen;
|
|
9
|
+
exports.getReadyProducerIds = getReadyProducerIds;
|
|
10
|
+
exports.getReadyDependencies = getReadyDependencies;
|
|
11
|
+
const path_1 = require("path");
|
|
12
|
+
const params_1 = require("../../utils/params");
|
|
13
|
+
const utils_1 = require("../utils");
|
|
14
|
+
const DEFAULT_READY_TIMEOUT = 60_000;
|
|
15
|
+
// Node timers take at most a signed 32-bit count of milliseconds
|
|
16
|
+
const MAX_DURATION = 2_147_483_647;
|
|
17
|
+
const PROBE_KEYS = ['url', 'port', 'command', 'logMatches'];
|
|
18
|
+
// The placeholders `outputs` resolve, matched by name so that braces a shell
|
|
19
|
+
// command needs (`{{.State.Health.Status}}`, `${VAR}`, `{a,b}`) stay literal
|
|
20
|
+
const PLACEHOLDER = /{(workspaceRoot|projectRoot|projectName|options\.[^}]+)}/g;
|
|
21
|
+
function normalizeReadyWhen(readyWhen, taskId, data) {
|
|
22
|
+
const invalid = (reason) => new Error(`Task "${taskId}" has an invalid "readyWhen": ${reason}.`);
|
|
23
|
+
if (typeof readyWhen !== 'object' || readyWhen === null) {
|
|
24
|
+
throw invalid('expected an object');
|
|
25
|
+
}
|
|
26
|
+
const config = readyWhen;
|
|
27
|
+
const resolve = (value) => typeof value === 'string'
|
|
28
|
+
? value.replace(PLACEHOLDER, (match, name) => {
|
|
29
|
+
let resolved = data;
|
|
30
|
+
for (const key of name.split('.')) {
|
|
31
|
+
resolved = resolved?.[key];
|
|
32
|
+
}
|
|
33
|
+
if (resolved == null) {
|
|
34
|
+
throw invalid(`"${match}" has no value`);
|
|
35
|
+
}
|
|
36
|
+
if (!['string', 'number', 'boolean'].includes(typeof resolved)) {
|
|
37
|
+
throw invalid(`"${match}" is not a string or a number`);
|
|
38
|
+
}
|
|
39
|
+
return String(resolved);
|
|
40
|
+
})
|
|
41
|
+
: value;
|
|
42
|
+
// Braces are never valid in a host or port once resolved, so a leftover is
|
|
43
|
+
// a typo. A url keeps them: they are legal in its path and query.
|
|
44
|
+
const resolveWithoutBraces = (key) => {
|
|
45
|
+
const value = resolve(config[key]);
|
|
46
|
+
if (typeof value === 'string' && /[{}]/.test(value)) {
|
|
47
|
+
throw invalid(`"${key}" contains an unknown placeholder`);
|
|
48
|
+
}
|
|
49
|
+
return value;
|
|
50
|
+
};
|
|
51
|
+
const present = PROBE_KEYS.filter((key) => config[key] != null);
|
|
52
|
+
if (present.length !== 1) {
|
|
53
|
+
throw invalid(`expected exactly one of ${PROBE_KEYS.map((k) => `"${k}"`).join(', ')}`);
|
|
54
|
+
}
|
|
55
|
+
const timeout = config.timeout ?? DEFAULT_READY_TIMEOUT;
|
|
56
|
+
if (!isDuration(timeout)) {
|
|
57
|
+
throw invalid(`"timeout" must be an integer from 1 to ${MAX_DURATION}`);
|
|
58
|
+
}
|
|
59
|
+
const shared = { timeout };
|
|
60
|
+
if (config.interval != null) {
|
|
61
|
+
if (!isDuration(config.interval)) {
|
|
62
|
+
throw invalid(`"interval" must be an integer from 1 to ${MAX_DURATION}`);
|
|
63
|
+
}
|
|
64
|
+
shared.interval = config.interval;
|
|
65
|
+
}
|
|
66
|
+
switch (present[0]) {
|
|
67
|
+
case 'url': {
|
|
68
|
+
const url = resolve(config.url);
|
|
69
|
+
if (typeof url !== 'string' || !isHttpUrl(url)) {
|
|
70
|
+
throw invalid('"url" must be an http or https URL');
|
|
71
|
+
}
|
|
72
|
+
return { kind: 'url', url, ...shared };
|
|
73
|
+
}
|
|
74
|
+
case 'port': {
|
|
75
|
+
let port = resolveWithoutBraces('port');
|
|
76
|
+
if (typeof port === 'string' && /^\d+$/.test(port)) {
|
|
77
|
+
port = Number(port);
|
|
78
|
+
}
|
|
79
|
+
if (!isPositiveInteger(port) || port > 65535) {
|
|
80
|
+
throw invalid('"port" must be an integer from 1 to 65535');
|
|
81
|
+
}
|
|
82
|
+
const host = resolveWithoutBraces('host');
|
|
83
|
+
if (host == null) {
|
|
84
|
+
return { kind: 'port', port, ...shared };
|
|
85
|
+
}
|
|
86
|
+
if (typeof host !== 'string' || !host) {
|
|
87
|
+
throw invalid('"host" must be a non-empty string');
|
|
88
|
+
}
|
|
89
|
+
return { kind: 'port', port, host, ...shared };
|
|
90
|
+
}
|
|
91
|
+
case 'command': {
|
|
92
|
+
const command = resolve(config.command);
|
|
93
|
+
if (typeof command !== 'string' || !command) {
|
|
94
|
+
throw invalid('"command" must be a non-empty string');
|
|
95
|
+
}
|
|
96
|
+
return { kind: 'command', command, ...shared };
|
|
97
|
+
}
|
|
98
|
+
case 'logMatches': {
|
|
99
|
+
const logMatches = (Array.isArray(config.logMatches)
|
|
100
|
+
? config.logMatches
|
|
101
|
+
: [config.logMatches]).map(resolve);
|
|
102
|
+
if (logMatches.length === 0 ||
|
|
103
|
+
!logMatches.every((m) => typeof m === 'string' && !!m)) {
|
|
104
|
+
throw invalid('"logMatches" must be a non-empty string or an array of them');
|
|
105
|
+
}
|
|
106
|
+
return { kind: 'logMatches', logMatches, ...shared };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function readinessTimeoutError(taskId, readyWhen) {
|
|
111
|
+
return new Error(`Task "${taskId}" did not become ready within ${readyWhen.timeout}ms (readyWhen: ${describeReadyWhen(readyWhen)}).`);
|
|
112
|
+
}
|
|
113
|
+
// The row carries only the status; the owner reports the reason
|
|
114
|
+
class ProducerFailedError extends Error {
|
|
115
|
+
constructor(taskId) {
|
|
116
|
+
super(`Task "${taskId}" failed its readiness check in the process that started it.`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
exports.ProducerFailedError = ProducerFailedError;
|
|
120
|
+
// An exit or a stop is completed by its own handler, not as a verdict
|
|
121
|
+
class NotReadyError extends Error {
|
|
122
|
+
}
|
|
123
|
+
exports.NotReadyError = NotReadyError;
|
|
124
|
+
function notReadyError(taskId, reason) {
|
|
125
|
+
return new NotReadyError(`Task "${taskId}" ${reason} before it became ready.`);
|
|
126
|
+
}
|
|
127
|
+
function describeReadyWhen(readyWhen) {
|
|
128
|
+
switch (readyWhen.kind) {
|
|
129
|
+
case 'url':
|
|
130
|
+
return `url ${readyWhen.url}`;
|
|
131
|
+
case 'port':
|
|
132
|
+
return readyWhen.host
|
|
133
|
+
? `port ${readyWhen.host}:${readyWhen.port}`
|
|
134
|
+
: `port ${readyWhen.port}`;
|
|
135
|
+
case 'command':
|
|
136
|
+
return `command "${readyWhen.command}"`;
|
|
137
|
+
case 'logMatches':
|
|
138
|
+
return `logMatches ${readyWhen.logMatches.map((m) => `"${m}"`).join(', ')}`;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Raw config: scheduling must not throw on an invalid value, the producer's
|
|
142
|
+
// own start reports that
|
|
143
|
+
function getReadyWhenConfig(task, projectGraph) {
|
|
144
|
+
if (!task.continuous) {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
return projectGraph.nodes[task.target.project]?.data?.targets?.[task.target.target]?.readyWhen;
|
|
148
|
+
}
|
|
149
|
+
// Placeholders resolve against the options the executor receives: aliases,
|
|
150
|
+
// schema defaults and the spelling of command-line overrides applied
|
|
151
|
+
function getReadyWhen(task, projectGraph, projects, workspaceRoot) {
|
|
152
|
+
const readyWhen = getReadyWhenConfig(task, projectGraph);
|
|
153
|
+
if (readyWhen == null) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
const node = projectGraph.nodes[task.target.project];
|
|
157
|
+
const target = node.data.targets[task.target.target];
|
|
158
|
+
const { schema } = (0, utils_1.getExecutorForTask)(task, projects);
|
|
159
|
+
return normalizeReadyWhen(readyWhen, task.id, {
|
|
160
|
+
workspaceRoot,
|
|
161
|
+
projectRoot: node.data.root,
|
|
162
|
+
projectName: node.name,
|
|
163
|
+
options: (0, params_1.combineOptionsForExecutor)(task.overrides, task.target.configuration ?? target.defaultConfiguration, target, schema, task.target.project, (0, path_1.relative)(task.projectRoot ?? workspaceRoot, process.cwd()), process.env.NX_VERBOSE_LOGGING === 'true'),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
function getReadyProducerIds(task, taskGraph) {
|
|
167
|
+
return (taskGraph.continuousDependencies[task.id] ?? [])
|
|
168
|
+
.filter((edge) => edge.waitFor === 'ready')
|
|
169
|
+
.map((edge) => edge.id);
|
|
170
|
+
}
|
|
171
|
+
// Producers with a probe that each task waits on; tasks with none are left out
|
|
172
|
+
function getReadyDependencies(taskGraph, projectGraph) {
|
|
173
|
+
const readyDependencies = {};
|
|
174
|
+
for (const task of Object.values(taskGraph.tasks)) {
|
|
175
|
+
const producerIds = getReadyProducerIds(task, taskGraph).filter((id) => getReadyWhenConfig(taskGraph.tasks[id], projectGraph) != null);
|
|
176
|
+
if (producerIds.length > 0) {
|
|
177
|
+
readyDependencies[task.id] = producerIds;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return readyDependencies;
|
|
181
|
+
}
|
|
182
|
+
function isPositiveInteger(value) {
|
|
183
|
+
return Number.isInteger(value) && value > 0;
|
|
184
|
+
}
|
|
185
|
+
function isDuration(value) {
|
|
186
|
+
return isPositiveInteger(value) && value <= MAX_DURATION;
|
|
187
|
+
}
|
|
188
|
+
function isHttpUrl(value) {
|
|
189
|
+
return (URL.canParse(value) && ['http:', 'https:'].includes(new URL(value).protocol));
|
|
190
|
+
}
|
|
@@ -50,6 +50,7 @@ const task_results_life_cycle_1 = require("./life-cycles/task-results-life-cycle
|
|
|
50
50
|
const task_telemetry_life_cycle_1 = require("./life-cycles/task-telemetry-life-cycle");
|
|
51
51
|
const task_timings_life_cycle_1 = require("./life-cycles/task-timings-life-cycle");
|
|
52
52
|
const tui_summary_life_cycle_1 = require("./life-cycles/tui-summary-life-cycle");
|
|
53
|
+
const ready_when_1 = require("./readiness/ready-when");
|
|
53
54
|
const task_graph_utils_1 = require("./task-graph-utils");
|
|
54
55
|
const utils_1 = require("./utils");
|
|
55
56
|
const exit_codes_1 = require("../utils/exit-codes");
|
|
@@ -80,7 +81,7 @@ const SYNC_SKIPPED_FIX_MESSAGE_LINES = [
|
|
|
80
81
|
'',
|
|
81
82
|
'Run `nx sync` (no flags) later to sync the workspace.',
|
|
82
83
|
];
|
|
83
|
-
async function getTerminalOutputLifeCycle(initiatingProject, initiatingTasks, projectNames, tasks, taskGraph, nxArgs, nxJson, overrides) {
|
|
84
|
+
async function getTerminalOutputLifeCycle(initiatingProject, initiatingTasks, projectNames, tasks, taskGraph, projectGraph, nxArgs, nxJson, overrides) {
|
|
84
85
|
const overridesWithoutHidden = { ...overrides };
|
|
85
86
|
delete overridesWithoutHidden['__overrides_unparsed__'];
|
|
86
87
|
const isRunOne = initiatingProject != null;
|
|
@@ -133,7 +134,7 @@ async function getTerminalOutputLifeCycle(initiatingProject, initiatingTasks, pr
|
|
|
133
134
|
pinnedTasks.push(mainTaskId);
|
|
134
135
|
const mainContinuousDependencies = taskGraph.continuousDependencies[mainTaskId];
|
|
135
136
|
if (mainContinuousDependencies.length > 0) {
|
|
136
|
-
pinnedTasks.push(mainContinuousDependencies[0]);
|
|
137
|
+
pinnedTasks.push(mainContinuousDependencies[0].id);
|
|
137
138
|
}
|
|
138
139
|
const [, target] = mainTaskId.split(':');
|
|
139
140
|
titleText = `1 ${target} task`;
|
|
@@ -179,7 +180,7 @@ async function getTerminalOutputLifeCycle(initiatingProject, initiatingTasks, pr
|
|
|
179
180
|
const lifeCycles = [tsLifeCycle];
|
|
180
181
|
// Only run the TUI if there are tasks to run
|
|
181
182
|
if (tasks.length > 0) {
|
|
182
|
-
appLifeCycle = new AppLifeCycle(tasks, initiatingTasks.map((t) => t.id), isRunOne ? 0 /* RunMode.RunOne */ : 1 /* RunMode.RunMany */, pinnedTasks, nxArgs ?? {}, nxJson.tui ?? {}, titleText, workspace_root_1.workspaceRoot, taskGraph, (0, nx_cloud_utils_1.isNxCloudUsed)(nxJson));
|
|
183
|
+
appLifeCycle = new AppLifeCycle(tasks, initiatingTasks.map((t) => t.id), isRunOne ? 0 /* RunMode.RunOne */ : 1 /* RunMode.RunMany */, pinnedTasks, nxArgs ?? {}, nxJson.tui ?? {}, titleText, workspace_root_1.workspaceRoot, taskGraph, (0, ready_when_1.getReadyDependencies)(taskGraph, projectGraph), (0, nx_cloud_utils_1.isNxCloudUsed)(nxJson));
|
|
183
184
|
// The native endCommand renders the perf report in the exit popup; the runner
|
|
184
185
|
// sources the payload and CompositeLifeCycle forwards it here.
|
|
185
186
|
lifeCycles.unshift(appLifeCycle);
|
|
@@ -401,7 +402,7 @@ async function runTasksForCommand(taskSelection, currentProjectGraph, { nxJson }
|
|
|
401
402
|
const projectNames = [
|
|
402
403
|
...new Set(initiatingTasks.map((t) => t.target.project)),
|
|
403
404
|
];
|
|
404
|
-
const { lifeCycle, renderIsDone, printSummary, restoreTerminal } = await getTerminalOutputLifeCycle(initiatingProject, initiatingTasks, projectNames, tasks, taskGraph, nxArgs, nxJson, overrides);
|
|
405
|
+
const { lifeCycle, renderIsDone, printSummary, restoreTerminal } = await getTerminalOutputLifeCycle(initiatingProject, initiatingTasks, projectNames, tasks, taskGraph, projectGraph, nxArgs, nxJson, overrides);
|
|
405
406
|
try {
|
|
406
407
|
const taskResults = await invokeTasksRunner({
|
|
407
408
|
tasks,
|
|
@@ -719,7 +720,7 @@ async function invokeTasksRunner({ tasks, projectGraph, taskGraph, lifeCycle, nx
|
|
|
719
720
|
// this is used for two reasons: to fetch all remote cache hits AND
|
|
720
721
|
// to submit everything that is known in advance to Nx Cloud to run in
|
|
721
722
|
// a distributed fashion
|
|
722
|
-
const
|
|
723
|
+
const hashingDeferredTaskIds = await (0, hash_task_1.hashTasksThatDoNotDependOnOutputsOfOtherTasks)(hasher, projectGraph, taskGraph, nxJson, taskDetails, ultracacheConfigurations);
|
|
723
724
|
reportUltracacheConfigurations(ultracacheConfigurationOutcome, projectGraph, taskGraph, nxArgs);
|
|
724
725
|
const taskResultsLifecycle = new task_results_life_cycle_1.TaskResultsLifeCycle();
|
|
725
726
|
const compositedLifeCycle = new life_cycle_1.CompositeLifeCycle([
|
|
@@ -790,7 +791,7 @@ async function invokeTasksRunner({ tasks, projectGraph, taskGraph, lifeCycle, nx
|
|
|
790
791
|
},
|
|
791
792
|
},
|
|
792
793
|
daemon: client_1.daemonClient,
|
|
793
|
-
|
|
794
|
+
hashingDeferredTaskIds,
|
|
794
795
|
});
|
|
795
796
|
if (promiseOrObservable.subscribe) {
|
|
796
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: {
|
|
@@ -14,6 +14,9 @@ class NodeChildProcessWithNonDirectOutput {
|
|
|
14
14
|
this.terminalOutputChunks = [];
|
|
15
15
|
this.exitCallbacks = [];
|
|
16
16
|
this.outputCallbacks = [];
|
|
17
|
+
// Decode as a stream so a code point split across chunks stays intact
|
|
18
|
+
this.childProcess.stdout.setEncoding('utf8');
|
|
19
|
+
this.childProcess.stderr.setEncoding('utf8');
|
|
17
20
|
if (streamOutput) {
|
|
18
21
|
if (process.env.NX_PREFIX_OUTPUT === 'true') {
|
|
19
22
|
const color = (0, output_prefix_1.getColor)(prefix);
|
|
@@ -54,16 +57,14 @@ class NodeChildProcessWithNonDirectOutput {
|
|
|
54
57
|
process.send(message);
|
|
55
58
|
}
|
|
56
59
|
});
|
|
57
|
-
this.childProcess.stdout.on('data', (
|
|
58
|
-
const output = chunk.toString();
|
|
60
|
+
this.childProcess.stdout.on('data', (output) => {
|
|
59
61
|
this.terminalOutputChunks.push(output);
|
|
60
62
|
// Stream output to TUI via callbacks
|
|
61
63
|
for (const cb of this.outputCallbacks) {
|
|
62
64
|
cb(output);
|
|
63
65
|
}
|
|
64
66
|
});
|
|
65
|
-
this.childProcess.stderr.on('data', (
|
|
66
|
-
const output = chunk.toString();
|
|
67
|
+
this.childProcess.stderr.on('data', (output) => {
|
|
67
68
|
this.terminalOutputChunks.push(output);
|
|
68
69
|
// Stream output to TUI via callbacks
|
|
69
70
|
for (const cb of this.outputCallbacks) {
|
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
import { ProjectGraph } from '../config/project-graph';
|
|
2
|
-
import { TaskGraph } from '../config/task-graph';
|
|
2
|
+
import { TaskGraph, TaskGraphEdge } from '../config/task-graph';
|
|
3
|
+
/** Task graph edges, or the project ids of the command graph. */
|
|
4
|
+
type Edge = string | TaskGraphEdge;
|
|
3
5
|
/**
|
|
4
6
|
* This function finds a cycle in the graph.
|
|
5
7
|
* @returns the first cycle found, or null if no cycle is found.
|
|
6
8
|
*/
|
|
7
9
|
export declare function findCycle(graph: {
|
|
8
|
-
dependencies: Record<string,
|
|
9
|
-
continuousDependencies?: Record<string,
|
|
10
|
+
dependencies: Record<string, Edge[]>;
|
|
11
|
+
continuousDependencies?: Record<string, Edge[]>;
|
|
10
12
|
}): string[] | null;
|
|
11
13
|
/**
|
|
12
14
|
* This function finds all cycles in the graph.
|
|
13
15
|
* @returns a list of unique task ids in all cycles found, or null if no cycle is found.
|
|
14
16
|
*/
|
|
15
17
|
export declare function findCycles(graph: {
|
|
16
|
-
dependencies: Record<string,
|
|
17
|
-
continuousDependencies?: Record<string,
|
|
18
|
+
dependencies: Record<string, Edge[]>;
|
|
19
|
+
continuousDependencies?: Record<string, Edge[]>;
|
|
18
20
|
}): Set<string> | null;
|
|
19
21
|
export declare function makeAcyclic(graph: {
|
|
20
22
|
roots: string[];
|
|
21
|
-
dependencies: Record<string,
|
|
23
|
+
dependencies: Record<string, Edge[]>;
|
|
22
24
|
}): void;
|
|
23
25
|
export declare function validateNoAtomizedTasks(taskGraph: TaskGraph, projectGraph: ProjectGraph): void;
|
|
24
26
|
export declare function assertTaskGraphDoesNotContainInvalidTargets(taskGraph: TaskGraph): void;
|
|
@@ -28,3 +30,4 @@ export declare function getLeafTasks(taskGraph: TaskGraph): Set<string>;
|
|
|
28
30
|
* Considers both dependencies and continuousDependencies.
|
|
29
31
|
*/
|
|
30
32
|
export declare function walkTaskGraph(taskGraph: TaskGraph, walkFn: (rootTaskIds: string[]) => Promise<void>): Promise<void>;
|
|
33
|
+
export {};
|