nx 20.5.0-canary.20250204-bc4ded0 → 21.0.0-beta.0
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/package.json +12 -11
- package/src/command-line/graph/graph.js +2 -0
- package/src/commands-runner/get-command-projects.js +17 -2
- package/src/config/task-graph.d.ts +5 -0
- package/src/config/workspace-json-project-json.d.ts +4 -0
- package/src/executors/run-commands/run-commands.impl.d.ts +16 -13
- package/src/executors/run-commands/run-commands.impl.js +24 -263
- package/src/executors/run-commands/running-tasks.d.ts +38 -0
- package/src/executors/run-commands/running-tasks.js +349 -0
- package/src/native/index.d.ts +1 -0
- package/src/native/nx.wasm32-wasi.wasm +0 -0
- package/src/tasks-runner/create-task-graph.d.ts +3 -0
- package/src/tasks-runner/create-task-graph.js +36 -5
- package/src/tasks-runner/forked-process-task-runner.d.ts +6 -12
- package/src/tasks-runner/forked-process-task-runner.js +110 -263
- package/src/tasks-runner/init-tasks-runner.js +4 -0
- package/src/tasks-runner/pseudo-terminal.d.ts +7 -1
- package/src/tasks-runner/pseudo-terminal.js +26 -12
- package/src/tasks-runner/running-tasks/batch-process.d.ts +14 -0
- package/src/tasks-runner/running-tasks/batch-process.js +70 -0
- package/src/tasks-runner/running-tasks/node-child-process.d.ts +36 -0
- package/src/tasks-runner/running-tasks/node-child-process.js +184 -0
- package/src/tasks-runner/running-tasks/noop-child-process.d.ts +15 -0
- package/src/tasks-runner/running-tasks/noop-child-process.js +19 -0
- package/src/tasks-runner/running-tasks/running-task.d.ts +8 -0
- package/src/tasks-runner/running-tasks/running-task.js +6 -0
- package/src/tasks-runner/task-orchestrator.d.ts +7 -1
- package/src/tasks-runner/task-orchestrator.js +137 -82
- package/src/tasks-runner/tasks-schedule.js +5 -1
- package/src/tasks-runner/utils.d.ts +0 -8
- package/src/tasks-runner/utils.js +12 -4
@@ -0,0 +1,184 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.NodeChildProcessWithDirectOutput = exports.NodeChildProcessWithNonDirectOutput = void 0;
|
4
|
+
const exit_codes_1 = require("../../utils/exit-codes");
|
5
|
+
const stream_1 = require("stream");
|
6
|
+
const chalk = require("chalk");
|
7
|
+
const fs_1 = require("fs");
|
8
|
+
class NodeChildProcessWithNonDirectOutput {
|
9
|
+
constructor(childProcess, { streamOutput, prefix }) {
|
10
|
+
this.childProcess = childProcess;
|
11
|
+
this.terminalOutput = '';
|
12
|
+
this.exitCallbacks = [];
|
13
|
+
if (streamOutput) {
|
14
|
+
if (process.env.NX_PREFIX_OUTPUT === 'true') {
|
15
|
+
const color = getColor(prefix);
|
16
|
+
const prefixText = `${prefix}:`;
|
17
|
+
this.childProcess.stdout
|
18
|
+
.pipe(logClearLineToPrefixTransformer(color.bold(prefixText) + ' '))
|
19
|
+
.pipe(addPrefixTransformer(color.bold(prefixText)))
|
20
|
+
.pipe(process.stdout);
|
21
|
+
this.childProcess.stderr
|
22
|
+
.pipe(logClearLineToPrefixTransformer(color(prefixText) + ' '))
|
23
|
+
.pipe(addPrefixTransformer(color(prefixText)))
|
24
|
+
.pipe(process.stderr);
|
25
|
+
}
|
26
|
+
else {
|
27
|
+
this.childProcess.stdout
|
28
|
+
.pipe(addPrefixTransformer())
|
29
|
+
.pipe(process.stdout);
|
30
|
+
this.childProcess.stderr
|
31
|
+
.pipe(addPrefixTransformer())
|
32
|
+
.pipe(process.stderr);
|
33
|
+
}
|
34
|
+
}
|
35
|
+
this.childProcess.on('exit', (code, signal) => {
|
36
|
+
if (code === null)
|
37
|
+
code = (0, exit_codes_1.signalToCode)(signal);
|
38
|
+
for (const cb of this.exitCallbacks) {
|
39
|
+
cb(code, this.terminalOutput);
|
40
|
+
}
|
41
|
+
});
|
42
|
+
// Re-emit any messages from the task process
|
43
|
+
this.childProcess.on('message', (message) => {
|
44
|
+
if (process.send) {
|
45
|
+
process.send(message);
|
46
|
+
}
|
47
|
+
});
|
48
|
+
this.childProcess.stdout.on('data', (chunk) => {
|
49
|
+
this.terminalOutput += chunk.toString();
|
50
|
+
});
|
51
|
+
this.childProcess.stderr.on('data', (chunk) => {
|
52
|
+
this.terminalOutput += chunk.toString();
|
53
|
+
});
|
54
|
+
}
|
55
|
+
onExit(cb) {
|
56
|
+
this.exitCallbacks.push(cb);
|
57
|
+
}
|
58
|
+
async getResults() {
|
59
|
+
return new Promise((res) => {
|
60
|
+
this.onExit((code, terminalOutput) => {
|
61
|
+
res({ code, terminalOutput });
|
62
|
+
});
|
63
|
+
});
|
64
|
+
}
|
65
|
+
send(message) {
|
66
|
+
if (this.childProcess.connected) {
|
67
|
+
this.childProcess.send(message);
|
68
|
+
}
|
69
|
+
}
|
70
|
+
kill(signal) {
|
71
|
+
if (this.childProcess.connected) {
|
72
|
+
this.childProcess.kill(signal);
|
73
|
+
}
|
74
|
+
}
|
75
|
+
}
|
76
|
+
exports.NodeChildProcessWithNonDirectOutput = NodeChildProcessWithNonDirectOutput;
|
77
|
+
function addPrefixTransformer(prefix) {
|
78
|
+
const newLineSeparator = process.platform.startsWith('win') ? '\r\n' : '\n';
|
79
|
+
return new stream_1.Transform({
|
80
|
+
transform(chunk, _encoding, callback) {
|
81
|
+
const list = chunk.toString().split(/\r\n|[\n\v\f\r\x85\u2028\u2029]/g);
|
82
|
+
list
|
83
|
+
.filter(Boolean)
|
84
|
+
.forEach((m) => this.push(prefix ? prefix + ' ' + m + newLineSeparator : m + newLineSeparator));
|
85
|
+
callback();
|
86
|
+
},
|
87
|
+
});
|
88
|
+
}
|
89
|
+
const colors = [
|
90
|
+
chalk.green,
|
91
|
+
chalk.greenBright,
|
92
|
+
chalk.red,
|
93
|
+
chalk.redBright,
|
94
|
+
chalk.cyan,
|
95
|
+
chalk.cyanBright,
|
96
|
+
chalk.yellow,
|
97
|
+
chalk.yellowBright,
|
98
|
+
chalk.magenta,
|
99
|
+
chalk.magentaBright,
|
100
|
+
];
|
101
|
+
function getColor(projectName) {
|
102
|
+
let code = 0;
|
103
|
+
for (let i = 0; i < projectName.length; ++i) {
|
104
|
+
code += projectName.charCodeAt(i);
|
105
|
+
}
|
106
|
+
const colorIndex = code % colors.length;
|
107
|
+
return colors[colorIndex];
|
108
|
+
}
|
109
|
+
/**
|
110
|
+
* Prevents terminal escape sequence from clearing line prefix.
|
111
|
+
*/
|
112
|
+
function logClearLineToPrefixTransformer(prefix) {
|
113
|
+
let prevChunk = null;
|
114
|
+
return new stream_1.Transform({
|
115
|
+
transform(chunk, _encoding, callback) {
|
116
|
+
if (prevChunk && prevChunk.toString() === '\x1b[2K') {
|
117
|
+
chunk = chunk.toString().replace(/\x1b\[1G/g, (m) => m + prefix);
|
118
|
+
}
|
119
|
+
this.push(chunk);
|
120
|
+
prevChunk = chunk;
|
121
|
+
callback();
|
122
|
+
},
|
123
|
+
});
|
124
|
+
}
|
125
|
+
class NodeChildProcessWithDirectOutput {
|
126
|
+
constructor(childProcess, temporaryOutputPath) {
|
127
|
+
this.childProcess = childProcess;
|
128
|
+
this.temporaryOutputPath = temporaryOutputPath;
|
129
|
+
this.exitCallbacks = [];
|
130
|
+
this.exited = false;
|
131
|
+
// Re-emit any messages from the task process
|
132
|
+
this.childProcess.on('message', (message) => {
|
133
|
+
if (process.send) {
|
134
|
+
process.send(message);
|
135
|
+
}
|
136
|
+
});
|
137
|
+
this.childProcess.on('exit', (code, signal) => {
|
138
|
+
if (code === null)
|
139
|
+
code = (0, exit_codes_1.signalToCode)(signal);
|
140
|
+
this.exited = true;
|
141
|
+
this.exitCode = code;
|
142
|
+
for (const cb of this.exitCallbacks) {
|
143
|
+
cb(code, signal);
|
144
|
+
}
|
145
|
+
});
|
146
|
+
}
|
147
|
+
send(message) {
|
148
|
+
if (this.childProcess.connected) {
|
149
|
+
this.childProcess.send(message);
|
150
|
+
}
|
151
|
+
}
|
152
|
+
onExit(cb) {
|
153
|
+
this.exitCallbacks.push(cb);
|
154
|
+
}
|
155
|
+
async getResults() {
|
156
|
+
const terminalOutput = this.getTerminalOutput();
|
157
|
+
if (this.exited) {
|
158
|
+
return Promise.resolve({
|
159
|
+
code: this.exitCode,
|
160
|
+
terminalOutput,
|
161
|
+
});
|
162
|
+
}
|
163
|
+
await this.waitForExit();
|
164
|
+
return Promise.resolve({
|
165
|
+
code: this.exitCode,
|
166
|
+
terminalOutput,
|
167
|
+
});
|
168
|
+
}
|
169
|
+
waitForExit() {
|
170
|
+
return new Promise((res) => {
|
171
|
+
this.onExit(() => res());
|
172
|
+
});
|
173
|
+
}
|
174
|
+
getTerminalOutput() {
|
175
|
+
this.terminalOutput ??= (0, fs_1.readFileSync)(this.temporaryOutputPath).toString();
|
176
|
+
return this.terminalOutput;
|
177
|
+
}
|
178
|
+
kill(signal) {
|
179
|
+
if (this.childProcess.connected) {
|
180
|
+
this.childProcess.kill(signal);
|
181
|
+
}
|
182
|
+
}
|
183
|
+
}
|
184
|
+
exports.NodeChildProcessWithDirectOutput = NodeChildProcessWithDirectOutput;
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import { RunningTask } from './running-task';
|
2
|
+
export declare class NoopChildProcess implements RunningTask {
|
3
|
+
private results;
|
4
|
+
constructor(results: {
|
5
|
+
code: number;
|
6
|
+
terminalOutput: string;
|
7
|
+
});
|
8
|
+
send(): void;
|
9
|
+
getResults(): Promise<{
|
10
|
+
code: number;
|
11
|
+
terminalOutput: string;
|
12
|
+
}>;
|
13
|
+
kill(): void;
|
14
|
+
onExit(cb: (code: number) => void): void;
|
15
|
+
}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.NoopChildProcess = void 0;
|
4
|
+
class NoopChildProcess {
|
5
|
+
constructor(results) {
|
6
|
+
this.results = results;
|
7
|
+
}
|
8
|
+
send() { }
|
9
|
+
async getResults() {
|
10
|
+
return this.results;
|
11
|
+
}
|
12
|
+
kill() {
|
13
|
+
return;
|
14
|
+
}
|
15
|
+
onExit(cb) {
|
16
|
+
cb(this.results.code);
|
17
|
+
}
|
18
|
+
}
|
19
|
+
exports.NoopChildProcess = NoopChildProcess;
|
@@ -5,6 +5,7 @@ import { ProjectGraph } from '../config/project-graph';
|
|
5
5
|
import { TaskGraph } from '../config/task-graph';
|
6
6
|
import { DaemonClient } from '../daemon/client/client';
|
7
7
|
import { NxJsonConfiguration } from '../config/nx-json';
|
8
|
+
import { NxArgs } from '../utils/command-line-utils';
|
8
9
|
export declare class TaskOrchestrator {
|
9
10
|
private readonly hasher;
|
10
11
|
private readonly initiatingProject;
|
@@ -27,7 +28,9 @@ export declare class TaskOrchestrator {
|
|
27
28
|
private waitingForTasks;
|
28
29
|
private groups;
|
29
30
|
private bailed;
|
30
|
-
|
31
|
+
private runningContinuousTasks;
|
32
|
+
private cleaningUp;
|
33
|
+
constructor(hasher: TaskHasher, initiatingProject: string | undefined, projectGraph: ProjectGraph, taskGraph: TaskGraph, nxJson: NxJsonConfiguration, options: NxArgs & DefaultTasksRunnerOptions, bail: boolean, daemon: DaemonClient, outputStyle: string);
|
31
34
|
run(): Promise<{
|
32
35
|
[id: string]: TaskStatus;
|
33
36
|
}>;
|
@@ -40,7 +43,9 @@ export declare class TaskOrchestrator {
|
|
40
43
|
private applyFromCacheOrRunBatch;
|
41
44
|
private runBatch;
|
42
45
|
private applyFromCacheOrRunTask;
|
46
|
+
private runTask;
|
43
47
|
private runTaskInForkedProcess;
|
48
|
+
private startContinuousTask;
|
44
49
|
private preRunSteps;
|
45
50
|
private postRunSteps;
|
46
51
|
private complete;
|
@@ -50,4 +55,5 @@ export declare class TaskOrchestrator {
|
|
50
55
|
private openGroup;
|
51
56
|
private shouldCopyOutputsFromCache;
|
52
57
|
private recordOutputsHash;
|
58
|
+
private cleanup;
|
53
59
|
}
|
@@ -15,6 +15,7 @@ const task_env_1 = require("./task-env");
|
|
15
15
|
const workspace_root_1 = require("../utils/workspace-root");
|
16
16
|
const output_1 = require("../utils/output");
|
17
17
|
const params_1 = require("../utils/params");
|
18
|
+
const noop_child_process_1 = require("./running-tasks/noop-child-process");
|
18
19
|
class TaskOrchestrator {
|
19
20
|
// endregion internal state
|
20
21
|
constructor(hasher, initiatingProject, projectGraph, taskGraph, nxJson, options, bail, daemon, outputStyle) {
|
@@ -40,6 +41,8 @@ class TaskOrchestrator {
|
|
40
41
|
this.waitingForTasks = [];
|
41
42
|
this.groups = [];
|
42
43
|
this.bailed = false;
|
44
|
+
this.runningContinuousTasks = new Map();
|
45
|
+
this.cleaningUp = false;
|
43
46
|
}
|
44
47
|
async run() {
|
45
48
|
// Init the ForkedProcessTaskRunner, TasksSchedule, and Cache
|
@@ -51,17 +54,20 @@ class TaskOrchestrator {
|
|
51
54
|
// initial scheduling
|
52
55
|
await this.tasksSchedule.scheduleNextTasks();
|
53
56
|
perf_hooks_1.performance.mark('task-execution:start');
|
57
|
+
const threadCount = this.options.parallel +
|
58
|
+
Object.values(this.taskGraph.tasks).filter((t) => t.continuous).length;
|
54
59
|
const threads = [];
|
55
|
-
process.stdout.setMaxListeners(
|
56
|
-
process.stderr.setMaxListeners(
|
60
|
+
process.stdout.setMaxListeners(threadCount + events_1.defaultMaxListeners);
|
61
|
+
process.stderr.setMaxListeners(threadCount + events_1.defaultMaxListeners);
|
57
62
|
// initial seeding of the queue
|
58
|
-
for (let i = 0; i <
|
63
|
+
for (let i = 0; i < threadCount; ++i) {
|
59
64
|
threads.push(this.executeNextBatchOfTasksUsingTaskSchedule());
|
60
65
|
}
|
61
66
|
await Promise.all(threads);
|
62
67
|
perf_hooks_1.performance.mark('task-execution:end');
|
63
68
|
perf_hooks_1.performance.measure('task-execution', 'task-execution:start', 'task-execution:end');
|
64
69
|
this.cache.removeOldCacheRecords();
|
70
|
+
await this.cleanup();
|
65
71
|
return this.completedTasks;
|
66
72
|
}
|
67
73
|
async executeNextBatchOfTasksUsingTaskSchedule() {
|
@@ -82,7 +88,12 @@ class TaskOrchestrator {
|
|
82
88
|
const task = this.tasksSchedule.nextTask();
|
83
89
|
if (task) {
|
84
90
|
const groupId = this.closeGroup();
|
85
|
-
|
91
|
+
if (task.continuous) {
|
92
|
+
await this.startContinuousTask(task, groupId);
|
93
|
+
}
|
94
|
+
else {
|
95
|
+
await this.applyFromCacheOrRunTask(doNotSkipCache, task, groupId);
|
96
|
+
}
|
86
97
|
this.openGroup(groupId);
|
87
98
|
return this.executeNextBatchOfTasksUsingTaskSchedule();
|
88
99
|
}
|
@@ -177,7 +188,8 @@ class TaskOrchestrator {
|
|
177
188
|
}
|
178
189
|
async runBatch(batch, env) {
|
179
190
|
try {
|
180
|
-
const
|
191
|
+
const batchProcess = await this.forkedProcessTaskRunner.forkProcessForBatch(batch, this.taskGraph, env);
|
192
|
+
const results = await batchProcess.getResults();
|
181
193
|
const batchResultEntries = Object.entries(results);
|
182
194
|
return batchResultEntries.map(([taskId, result]) => ({
|
183
195
|
...result,
|
@@ -217,88 +229,84 @@ class TaskOrchestrator {
|
|
217
229
|
let results = doNotSkipCache ? await this.applyCachedResults([task]) : [];
|
218
230
|
// the task wasn't cached
|
219
231
|
if (results.length === 0) {
|
220
|
-
const
|
221
|
-
const
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
env,
|
243
|
-
|
244
|
-
|
245
|
-
}, {
|
246
|
-
root: workspace_root_1.workspaceRoot, // only root is needed in runCommandsImpl
|
247
|
-
});
|
248
|
-
const status = success ? 'success' : 'failure';
|
249
|
-
if (!streamOutput) {
|
250
|
-
this.options.lifeCycle.printTaskTerminalOutput(task, status, terminalOutput);
|
251
|
-
}
|
252
|
-
(0, fs_1.writeFileSync)(temporaryOutputPath, terminalOutput);
|
253
|
-
results.push({
|
254
|
-
task,
|
255
|
-
status,
|
256
|
-
terminalOutput,
|
257
|
-
});
|
232
|
+
const childProcess = await this.runTask(task, streamOutput, env, temporaryOutputPath, pipeOutput);
|
233
|
+
const { code, terminalOutput } = await childProcess.getResults();
|
234
|
+
results.push({
|
235
|
+
task,
|
236
|
+
status: code === 0 ? 'success' : 'failure',
|
237
|
+
terminalOutput,
|
238
|
+
});
|
239
|
+
}
|
240
|
+
await this.postRunSteps([task], results, doNotSkipCache, { groupId });
|
241
|
+
}
|
242
|
+
async runTask(task, streamOutput, env, temporaryOutputPath, pipeOutput) {
|
243
|
+
const shouldPrefix = streamOutput && process.env.NX_PREFIX_OUTPUT === 'true';
|
244
|
+
const targetConfiguration = (0, utils_1.getTargetConfigurationForTask)(task, this.projectGraph);
|
245
|
+
if (process.env.NX_RUN_COMMANDS_DIRECTLY !== 'false' &&
|
246
|
+
targetConfiguration.executor === 'nx:run-commands' &&
|
247
|
+
!shouldPrefix) {
|
248
|
+
try {
|
249
|
+
const { schema } = (0, utils_1.getExecutorForTask)(task, this.projectGraph);
|
250
|
+
const isRunOne = this.initiatingProject != null;
|
251
|
+
const combinedOptions = (0, params_1.combineOptionsForExecutor)(task.overrides, task.target.configuration ?? targetConfiguration.defaultConfiguration, targetConfiguration, schema, task.target.project, (0, path_1.relative)(task.projectRoot ?? workspace_root_1.workspaceRoot, process.cwd()), process.env.NX_VERBOSE_LOGGING === 'true');
|
252
|
+
if (combinedOptions.env) {
|
253
|
+
env = {
|
254
|
+
...env,
|
255
|
+
...combinedOptions.env,
|
256
|
+
};
|
258
257
|
}
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
}
|
263
|
-
else {
|
264
|
-
console.error(e.message);
|
265
|
-
}
|
266
|
-
const terminalOutput = e.stack ?? e.message ?? '';
|
267
|
-
(0, fs_1.writeFileSync)(temporaryOutputPath, terminalOutput);
|
268
|
-
results.push({
|
269
|
-
task,
|
270
|
-
status: 'failure',
|
271
|
-
terminalOutput,
|
272
|
-
});
|
258
|
+
if (streamOutput) {
|
259
|
+
const args = (0, utils_1.getPrintableCommandArgsForTask)(task);
|
260
|
+
output_1.output.logCommand(args.join(' '));
|
273
261
|
}
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
262
|
+
const runningTask = await (0, run_commands_impl_1.runCommands)({
|
263
|
+
...combinedOptions,
|
264
|
+
env,
|
265
|
+
usePty: isRunOne &&
|
266
|
+
!this.tasksSchedule.hasTasks() &&
|
267
|
+
this.runningContinuousTasks.size === 0,
|
268
|
+
streamOutput,
|
269
|
+
}, {
|
270
|
+
root: workspace_root_1.workspaceRoot, // only root is needed in runCommands
|
281
271
|
});
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
task,
|
288
|
-
status: code === 0 ? 'success' : 'failure',
|
289
|
-
terminalOutput,
|
272
|
+
runningTask.onExit((code, terminalOutput) => {
|
273
|
+
if (!streamOutput) {
|
274
|
+
this.options.lifeCycle.printTaskTerminalOutput(task, code === 0 ? 'success' : 'failure', terminalOutput);
|
275
|
+
(0, fs_1.writeFileSync)(temporaryOutputPath, terminalOutput);
|
276
|
+
}
|
290
277
|
});
|
278
|
+
return runningTask;
|
279
|
+
}
|
280
|
+
catch (e) {
|
281
|
+
if (process.env.NX_VERBOSE_LOGGING === 'true') {
|
282
|
+
console.error(e);
|
283
|
+
}
|
284
|
+
else {
|
285
|
+
console.error(e.message);
|
286
|
+
}
|
287
|
+
const terminalOutput = e.stack ?? e.message ?? '';
|
288
|
+
(0, fs_1.writeFileSync)(temporaryOutputPath, terminalOutput);
|
291
289
|
}
|
292
290
|
}
|
293
|
-
|
291
|
+
else if (targetConfiguration.executor === 'nx:noop') {
|
292
|
+
(0, fs_1.writeFileSync)(temporaryOutputPath, '');
|
293
|
+
return new noop_child_process_1.NoopChildProcess({
|
294
|
+
code: 0,
|
295
|
+
terminalOutput: '',
|
296
|
+
});
|
297
|
+
}
|
298
|
+
else {
|
299
|
+
// cache prep
|
300
|
+
return await this.runTaskInForkedProcess(task, env, pipeOutput, temporaryOutputPath, streamOutput);
|
301
|
+
}
|
294
302
|
}
|
295
303
|
async runTaskInForkedProcess(task, env, pipeOutput, temporaryOutputPath, streamOutput) {
|
296
304
|
try {
|
297
305
|
const usePtyFork = process.env.NX_NATIVE_COMMAND_RUNNER !== 'false';
|
298
|
-
// Disable the pseudo terminal if this is a run-many
|
299
|
-
const disablePseudoTerminal = !this.initiatingProject;
|
306
|
+
// Disable the pseudo terminal if this is a run-many or when running a continuous task as part of a run-one
|
307
|
+
const disablePseudoTerminal = !this.initiatingProject || task.continuous;
|
300
308
|
// execution
|
301
|
-
const
|
309
|
+
const childProcess = usePtyFork
|
302
310
|
? await this.forkedProcessTaskRunner.forkProcess(task, {
|
303
311
|
temporaryOutputPath,
|
304
312
|
streamOutput,
|
@@ -314,19 +322,54 @@ class TaskOrchestrator {
|
|
314
322
|
taskGraph: this.taskGraph,
|
315
323
|
env,
|
316
324
|
});
|
317
|
-
return
|
318
|
-
code,
|
319
|
-
terminalOutput,
|
320
|
-
};
|
325
|
+
return childProcess;
|
321
326
|
}
|
322
327
|
catch (e) {
|
323
328
|
if (process.env.NX_VERBOSE_LOGGING === 'true') {
|
324
329
|
console.error(e);
|
325
330
|
}
|
326
|
-
return {
|
331
|
+
return new noop_child_process_1.NoopChildProcess({
|
327
332
|
code: 1,
|
328
|
-
|
333
|
+
terminalOutput: undefined,
|
334
|
+
});
|
335
|
+
}
|
336
|
+
}
|
337
|
+
async startContinuousTask(task, groupId) {
|
338
|
+
const taskSpecificEnv = await this.processedTasks.get(task.id);
|
339
|
+
await this.preRunSteps([task], { groupId });
|
340
|
+
const pipeOutput = await this.pipeOutputCapture(task);
|
341
|
+
// obtain metadata
|
342
|
+
const temporaryOutputPath = this.cache.temporaryOutputPath(task);
|
343
|
+
const streamOutput = this.outputStyle === 'static'
|
344
|
+
? false
|
345
|
+
: (0, utils_1.shouldStreamOutput)(task, this.initiatingProject);
|
346
|
+
let env = pipeOutput
|
347
|
+
? (0, task_env_1.getEnvVariablesForTask)(task, taskSpecificEnv, process.env.FORCE_COLOR === undefined
|
348
|
+
? 'true'
|
349
|
+
: process.env.FORCE_COLOR, this.options.skipNxCache, this.options.captureStderr, null, null)
|
350
|
+
: (0, task_env_1.getEnvVariablesForTask)(task, taskSpecificEnv, undefined, this.options.skipNxCache, this.options.captureStderr, temporaryOutputPath, streamOutput);
|
351
|
+
const childProcess = await this.runTask(task, streamOutput, env, temporaryOutputPath, pipeOutput);
|
352
|
+
this.runningContinuousTasks.set(task.id, childProcess);
|
353
|
+
childProcess.onExit((code) => {
|
354
|
+
if (!this.cleaningUp) {
|
355
|
+
console.error(`Task "${task.id}" is continuous but exited with code ${code}`);
|
356
|
+
this.cleanup().then(() => {
|
357
|
+
process.exit(1);
|
358
|
+
});
|
359
|
+
}
|
360
|
+
});
|
361
|
+
if (this.initiatingProject === task.target.project &&
|
362
|
+
this.options.targets.length === 1 &&
|
363
|
+
this.options.targets[0] === task.target.target) {
|
364
|
+
await childProcess.getResults();
|
365
|
+
}
|
366
|
+
else {
|
367
|
+
await this.tasksSchedule.scheduleNextTasks();
|
368
|
+
// release blocked threads
|
369
|
+
this.waitingForTasks.forEach((f) => f(null));
|
370
|
+
this.waitingForTasks.length = 0;
|
329
371
|
}
|
372
|
+
return childProcess;
|
330
373
|
}
|
331
374
|
// endregion Single Task
|
332
375
|
// region Lifecycle
|
@@ -458,5 +501,17 @@ class TaskOrchestrator {
|
|
458
501
|
return this.daemon.recordOutputsHash(task.outputs, task.hash);
|
459
502
|
}
|
460
503
|
}
|
504
|
+
// endregion utils
|
505
|
+
async cleanup() {
|
506
|
+
this.cleaningUp = true;
|
507
|
+
await Promise.all(Array.from(this.runningContinuousTasks).map(async ([taskId, t]) => {
|
508
|
+
try {
|
509
|
+
return t.kill();
|
510
|
+
}
|
511
|
+
catch (e) {
|
512
|
+
console.error(`Unable to terminate ${taskId}\nError:`, e);
|
513
|
+
}
|
514
|
+
}));
|
515
|
+
}
|
461
516
|
}
|
462
517
|
exports.TaskOrchestrator = TaskOrchestrator;
|
@@ -147,11 +147,14 @@ class TasksSchedule {
|
|
147
147
|
{
|
148
148
|
tasks: {},
|
149
149
|
dependencies: {},
|
150
|
+
continuousDependencies: {},
|
150
151
|
roots: [],
|
151
152
|
});
|
152
153
|
batch.tasks[task.id] = task;
|
153
154
|
batch.dependencies[task.id] =
|
154
155
|
this.notScheduledTaskGraph.dependencies[task.id];
|
156
|
+
batch.continuousDependencies[task.id] =
|
157
|
+
this.notScheduledTaskGraph.continuousDependencies[task.id];
|
155
158
|
if (isRoot) {
|
156
159
|
batch.roots.push(task.id);
|
157
160
|
}
|
@@ -168,8 +171,9 @@ class TasksSchedule {
|
|
168
171
|
}
|
169
172
|
canBeScheduled(taskId) {
|
170
173
|
const hasDependenciesCompleted = this.taskGraph.dependencies[taskId].every((id) => this.completedTasks.has(id));
|
174
|
+
const hasContinuousDependenciesStarted = this.taskGraph.continuousDependencies[taskId].every((id) => this.runningTasks.has(id));
|
171
175
|
// if dependencies have not completed, cannot schedule
|
172
|
-
if (!hasDependenciesCompleted) {
|
176
|
+
if (!hasDependenciesCompleted || !hasContinuousDependenciesStarted) {
|
173
177
|
return false;
|
174
178
|
}
|
175
179
|
// if there are no running tasks, can schedule anything
|
@@ -37,14 +37,6 @@ export declare function getExecutorForTask(task: Task, projectGraph: ProjectGrap
|
|
37
37
|
};
|
38
38
|
export declare function getCustomHasher(task: Task, projectGraph: ProjectGraph): CustomHasher | null;
|
39
39
|
export declare function removeTasksFromTaskGraph(graph: TaskGraph, ids: string[]): TaskGraph;
|
40
|
-
export declare function removeIdsFromGraph<T>(graph: {
|
41
|
-
roots: string[];
|
42
|
-
dependencies: Record<string, string[]>;
|
43
|
-
}, ids: string[], mapWithIds: Record<string, T>): {
|
44
|
-
mapWithIds: Record<string, T>;
|
45
|
-
roots: string[];
|
46
|
-
dependencies: Record<string, string[]>;
|
47
|
-
};
|
48
40
|
export declare function calculateReverseDeps(taskGraph: TaskGraph): Record<string, string[]>;
|
49
41
|
export declare function getCliPath(): string;
|
50
42
|
export declare function getPrintableCommandArgsForTask(task: Task): string[];
|
@@ -17,7 +17,6 @@ exports.getExecutorNameForTask = getExecutorNameForTask;
|
|
17
17
|
exports.getExecutorForTask = getExecutorForTask;
|
18
18
|
exports.getCustomHasher = getCustomHasher;
|
19
19
|
exports.removeTasksFromTaskGraph = removeTasksFromTaskGraph;
|
20
|
-
exports.removeIdsFromGraph = removeIdsFromGraph;
|
21
20
|
exports.calculateReverseDeps = calculateReverseDeps;
|
22
21
|
exports.getCliPath = getCliPath;
|
23
22
|
exports.getPrintableCommandArgsForTask = getPrintableCommandArgsForTask;
|
@@ -302,27 +301,31 @@ function getCustomHasher(task, projectGraph) {
|
|
302
301
|
return factory ? factory() : null;
|
303
302
|
}
|
304
303
|
function removeTasksFromTaskGraph(graph, ids) {
|
305
|
-
const newGraph =
|
304
|
+
const newGraph = removeIdsFromTaskGraph(graph, ids, graph.tasks);
|
306
305
|
return {
|
307
306
|
dependencies: newGraph.dependencies,
|
307
|
+
continuousDependencies: newGraph.continuousDependencies,
|
308
308
|
roots: newGraph.roots,
|
309
309
|
tasks: newGraph.mapWithIds,
|
310
310
|
};
|
311
311
|
}
|
312
|
-
function
|
312
|
+
function removeIdsFromTaskGraph(graph, ids, mapWithIds) {
|
313
313
|
const filteredMapWithIds = {};
|
314
314
|
const dependencies = {};
|
315
|
+
const continuousDependencies = {};
|
315
316
|
const removedSet = new Set(ids);
|
316
317
|
for (let id of Object.keys(mapWithIds)) {
|
317
318
|
if (!removedSet.has(id)) {
|
318
319
|
filteredMapWithIds[id] = mapWithIds[id];
|
319
320
|
dependencies[id] = graph.dependencies[id].filter((depId) => !removedSet.has(depId));
|
321
|
+
continuousDependencies[id] = graph.continuousDependencies[id].filter((depId) => !removedSet.has(depId));
|
320
322
|
}
|
321
323
|
}
|
322
324
|
return {
|
323
325
|
mapWithIds: filteredMapWithIds,
|
324
326
|
dependencies: dependencies,
|
325
|
-
|
327
|
+
continuousDependencies,
|
328
|
+
roots: Object.keys(filteredMapWithIds).filter((k) => dependencies[k].length === 0 && continuousDependencies[k].length === 0),
|
326
329
|
};
|
327
330
|
}
|
328
331
|
function calculateReverseDeps(taskGraph) {
|
@@ -335,6 +338,11 @@ function calculateReverseDeps(taskGraph) {
|
|
335
338
|
reverseTaskDeps[d].push(taskId);
|
336
339
|
});
|
337
340
|
});
|
341
|
+
Object.keys(taskGraph.continuousDependencies).forEach((taskId) => {
|
342
|
+
taskGraph.continuousDependencies[taskId].forEach((d) => {
|
343
|
+
reverseTaskDeps[d].push(taskId);
|
344
|
+
});
|
345
|
+
});
|
338
346
|
return reverseTaskDeps;
|
339
347
|
}
|
340
348
|
function getCliPath() {
|