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.
Files changed (31) hide show
  1. package/package.json +12 -11
  2. package/src/command-line/graph/graph.js +2 -0
  3. package/src/commands-runner/get-command-projects.js +17 -2
  4. package/src/config/task-graph.d.ts +5 -0
  5. package/src/config/workspace-json-project-json.d.ts +4 -0
  6. package/src/executors/run-commands/run-commands.impl.d.ts +16 -13
  7. package/src/executors/run-commands/run-commands.impl.js +24 -263
  8. package/src/executors/run-commands/running-tasks.d.ts +38 -0
  9. package/src/executors/run-commands/running-tasks.js +349 -0
  10. package/src/native/index.d.ts +1 -0
  11. package/src/native/nx.wasm32-wasi.wasm +0 -0
  12. package/src/tasks-runner/create-task-graph.d.ts +3 -0
  13. package/src/tasks-runner/create-task-graph.js +36 -5
  14. package/src/tasks-runner/forked-process-task-runner.d.ts +6 -12
  15. package/src/tasks-runner/forked-process-task-runner.js +110 -263
  16. package/src/tasks-runner/init-tasks-runner.js +4 -0
  17. package/src/tasks-runner/pseudo-terminal.d.ts +7 -1
  18. package/src/tasks-runner/pseudo-terminal.js +26 -12
  19. package/src/tasks-runner/running-tasks/batch-process.d.ts +14 -0
  20. package/src/tasks-runner/running-tasks/batch-process.js +70 -0
  21. package/src/tasks-runner/running-tasks/node-child-process.d.ts +36 -0
  22. package/src/tasks-runner/running-tasks/node-child-process.js +184 -0
  23. package/src/tasks-runner/running-tasks/noop-child-process.d.ts +15 -0
  24. package/src/tasks-runner/running-tasks/noop-child-process.js +19 -0
  25. package/src/tasks-runner/running-tasks/running-task.d.ts +8 -0
  26. package/src/tasks-runner/running-tasks/running-task.js +6 -0
  27. package/src/tasks-runner/task-orchestrator.d.ts +7 -1
  28. package/src/tasks-runner/task-orchestrator.js +137 -82
  29. package/src/tasks-runner/tasks-schedule.js +5 -1
  30. package/src/tasks-runner/utils.d.ts +0 -8
  31. 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;
@@ -0,0 +1,8 @@
1
+ export declare abstract class RunningTask {
2
+ abstract getResults(): Promise<{
3
+ code: number;
4
+ terminalOutput: string;
5
+ }>;
6
+ abstract onExit(cb: (code: number) => void): void;
7
+ abstract kill(signal?: NodeJS.Signals | number): Promise<void> | void;
8
+ }
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RunningTask = void 0;
4
+ class RunningTask {
5
+ }
6
+ exports.RunningTask = RunningTask;
@@ -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
- constructor(hasher: TaskHasher, initiatingProject: string | undefined, projectGraph: ProjectGraph, taskGraph: TaskGraph, nxJson: NxJsonConfiguration, options: DefaultTasksRunnerOptions, bail: boolean, daemon: DaemonClient, outputStyle: string);
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(this.options.parallel + events_1.defaultMaxListeners);
56
- process.stderr.setMaxListeners(this.options.parallel + events_1.defaultMaxListeners);
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 < this.options.parallel; ++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
- await this.applyFromCacheOrRunTask(doNotSkipCache, task, groupId);
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 results = await this.forkedProcessTaskRunner.forkProcessForBatch(batch, this.taskGraph, env);
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 shouldPrefix = streamOutput && process.env.NX_PREFIX_OUTPUT === 'true';
221
- const targetConfiguration = (0, utils_1.getTargetConfigurationForTask)(task, this.projectGraph);
222
- if (process.env.NX_RUN_COMMANDS_DIRECTLY !== 'false' &&
223
- targetConfiguration.executor === 'nx:run-commands' &&
224
- !shouldPrefix) {
225
- try {
226
- const { schema } = (0, utils_1.getExecutorForTask)(task, this.projectGraph);
227
- const isRunOne = this.initiatingProject != null;
228
- const combinedOptions = (0, params_1.combineOptionsForExecutor)(task.overrides, task.target.configuration ??
229
- 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');
230
- if (combinedOptions.env) {
231
- env = {
232
- ...env,
233
- ...combinedOptions.env,
234
- };
235
- }
236
- if (streamOutput) {
237
- const args = (0, utils_1.getPrintableCommandArgsForTask)(task);
238
- output_1.output.logCommand(args.join(' '));
239
- }
240
- const { success, terminalOutput } = await (0, run_commands_impl_1.default)({
241
- ...combinedOptions,
242
- env,
243
- usePty: isRunOne && !this.tasksSchedule.hasTasks(),
244
- streamOutput,
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
- catch (e) {
260
- if (process.env.NX_VERBOSE_LOGGING === 'true') {
261
- console.error(e);
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
- else if (targetConfiguration.executor === 'nx:noop') {
276
- (0, fs_1.writeFileSync)(temporaryOutputPath, '');
277
- results.push({
278
- task,
279
- status: 'success',
280
- terminalOutput: '',
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
- else {
284
- // cache prep
285
- const { code, terminalOutput } = await this.runTaskInForkedProcess(task, env, pipeOutput, temporaryOutputPath, streamOutput);
286
- results.push({
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
- await this.postRunSteps([task], results, doNotSkipCache, { groupId });
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 { code, terminalOutput } = usePtyFork
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 = removeIdsFromGraph(graph, ids, graph.tasks);
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 removeIdsFromGraph(graph, ids, mapWithIds) {
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
- roots: Object.keys(dependencies).filter((k) => dependencies[k].length === 0),
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() {