UNPKG

nx

Version:

Smart, Fast and Extensible Build System

273 lines (272 loc) • 12.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ForkedProcessTaskRunner = void 0; const fs_1 = require("fs"); const dotenv = require("dotenv"); const child_process_1 = require("child_process"); const workspace_root_1 = require("../utils/workspace-root"); const output_1 = require("../utils/output"); const utils_1 = require("./utils"); const path_1 = require("path"); const batch_messages_1 = require("./batch/batch-messages"); const strip_indents_1 = require("../utils/strip-indents"); const add_command_prefix_1 = require("../utils/add-command-prefix"); const workerPath = (0, path_1.join)(__dirname, './batch/run-batch.js'); class ForkedProcessTaskRunner { constructor(options) { this.options = options; this.workspaceRoot = workspace_root_1.workspaceRoot; this.cliPath = (0, utils_1.getCliPath)(); this.processes = new Set(); this.setupOnProcessExitListener(); } // TODO: vsavkin delegate terminal output printing forkProcessForBatch({ executorName, taskGraph }) { return new Promise((res, rej) => { try { const count = Object.keys(taskGraph.tasks).length; if (count > 1) { output_1.output.logSingleLine(`Running ${output_1.output.bold(count)} ${output_1.output.bold('tasks')} with ${output_1.output.bold(executorName)}`); } else { const args = (0, utils_1.getPrintableCommandArgsForTask)(Object.values(taskGraph.tasks)[0]); output_1.output.logCommand(args.join(' ')); output_1.output.addNewline(); } const p = (0, child_process_1.fork)(workerPath, { stdio: ['inherit', 'inherit', 'inherit', 'ipc'], env: this.getEnvVariablesForProcess(), }); this.processes.add(p); p.once('exit', (code, signal) => { if (code === null) code = this.signalToCode(signal); if (code !== 0) { const results = {}; for (const rootTaskId of taskGraph.roots) { results[rootTaskId] = { success: false, }; } rej(new Error(`"${executorName}" exited unexpectedly with code: ${code}`)); } }); p.on('message', (message) => { switch (message.type) { case batch_messages_1.BatchMessageType.Complete: { res(message.results); } } }); // Start the tasks p.send({ type: batch_messages_1.BatchMessageType.Tasks, taskGraph, executorName, }); } catch (e) { rej(e); } }); } forkProcessPipeOutputCapture(task, { streamOutput, temporaryOutputPath, }) { return new Promise((res, rej) => { try { const args = (0, utils_1.getPrintableCommandArgsForTask)(task); const serializedArgs = (0, utils_1.getSerializedArgsForTask)(task, task.overrides['verbose'] === true); if (streamOutput) { output_1.output.logCommand(args.join(' ')); output_1.output.addNewline(); } const p = (0, child_process_1.fork)(this.cliPath, serializedArgs, { stdio: ['inherit', 'pipe', 'pipe', 'ipc'], env: this.getEnvVariablesForTask(task, process.env.FORCE_COLOR === undefined ? 'true' : process.env.FORCE_COLOR, null, null), }); this.processes.add(p); let out = []; let outWithErr = []; p.stdout.on('data', (chunk) => { if (streamOutput) { process.stdout.write((0, add_command_prefix_1.addCommandPrefixIfNeeded)(task.target.project, chunk, 'utf-8') .content); } out.push(chunk.toString()); outWithErr.push(chunk.toString()); }); p.stderr.on('data', (chunk) => { if (streamOutput) { process.stderr.write((0, add_command_prefix_1.addCommandPrefixIfNeeded)(task.target.project, chunk, 'utf-8') .content); } outWithErr.push(chunk.toString()); }); p.on('exit', (code, signal) => { if (code === null) code = this.signalToCode(signal); // we didn't print any output as we were running the command // print all the collected output| const terminalOutput = outWithErr.join(''); if (!streamOutput) { this.options.lifeCycle.printTaskTerminalOutput(task, code === 0 ? 'success' : 'failure', terminalOutput); } this.writeTerminalOutput(temporaryOutputPath, terminalOutput); res({ code, terminalOutput }); }); } catch (e) { console.error(e); rej(e); } }); } forkProcessDirectOutputCapture(task, { streamOutput, temporaryOutputPath, }) { return new Promise((res, rej) => { try { const args = (0, utils_1.getPrintableCommandArgsForTask)(task); const serializedArgs = (0, utils_1.getSerializedArgsForTask)(task, task.overrides['verbose'] === true); if (streamOutput) { output_1.output.logCommand(args.join(' ')); output_1.output.addNewline(); } const p = (0, child_process_1.fork)(this.cliPath, serializedArgs, { stdio: ['inherit', 'inherit', 'inherit', 'ipc'], env: this.getEnvVariablesForTask(task, undefined, temporaryOutputPath, streamOutput), }); this.processes.add(p); p.on('exit', (code, signal) => { if (code === null) code = this.signalToCode(signal); // we didn't print any output as we were running the command // print all the collected output let terminalOutput = ''; try { terminalOutput = this.readTerminalOutput(temporaryOutputPath); if (!streamOutput) { this.options.lifeCycle.printTaskTerminalOutput(task, code === 0 ? 'success' : 'failure', terminalOutput); } } catch (e) { console.log((0, strip_indents_1.stripIndents) ` Unable to print terminal output for Task "${task.id}". Task failed with Exit Code ${code} and Signal "${signal}". Received error message: ${e.message} `); } res({ code, terminalOutput, }); }); } catch (e) { console.error(e); rej(e); } }); } readTerminalOutput(outputPath) { return (0, fs_1.readFileSync)(outputPath).toString(); } writeTerminalOutput(outputPath, content) { (0, fs_1.writeFileSync)(outputPath, content); } // region Environment Variables getEnvVariablesForProcess() { return Object.assign(Object.assign(Object.assign({}, this.getDotenvVariablesForForkedProcess()), process.env), this.getNxEnvVariablesForForkedProcess(process.env.FORCE_COLOR === undefined ? 'true' : process.env.FORCE_COLOR)); } getEnvVariablesForTask(task, forceColor, outputPath, streamOutput) { const res = Object.assign(Object.assign(Object.assign({}, this.getDotenvVariablesForTask(task)), process.env), this.getNxEnvVariablesForTask(task, forceColor, outputPath, streamOutput)); // we have to delete it because if we invoke Nx from within Nx, we need to reset those values if (!outputPath) { delete res.NX_TERMINAL_OUTPUT_PATH; delete res.NX_STREAM_OUTPUT; delete res.NX_PREFIX_OUTPUT; } delete res.NX_BASE; delete res.NX_HEAD; delete res.NX_SET_CLI; return res; } getNxEnvVariablesForForkedProcess(forceColor, outputPath, streamOutput) { const env = { FORCE_COLOR: forceColor, NX_WORKSPACE_ROOT: this.workspaceRoot, NX_SKIP_NX_CACHE: this.options.skipNxCache ? 'true' : undefined, }; if (outputPath) { env.NX_TERMINAL_OUTPUT_PATH = outputPath; if (this.options.captureStderr) { env.NX_TERMINAL_CAPTURE_STDERR = 'true'; } if (streamOutput) { env.NX_STREAM_OUTPUT = 'true'; } } return env; } getNxEnvVariablesForTask(task, forceColor, outputPath, streamOutput) { const env = { NX_TASK_TARGET_PROJECT: task.target.project, NX_TASK_HASH: task.hash, // used when Nx is invoked via Lerna LERNA_PACKAGE_NAME: task.target.project, }; // TODO: remove this once we have a reasonable way to configure it if (task.target.target === 'test') { env.NX_TERMINAL_CAPTURE_STDERR = 'true'; } return Object.assign(Object.assign({}, this.getNxEnvVariablesForForkedProcess(forceColor, outputPath, streamOutput)), env); } getDotenvVariablesForForkedProcess() { return Object.assign(Object.assign(Object.assign({}, parseEnv('.env')), parseEnv('.local.env')), parseEnv('.env.local')); } getDotenvVariablesForTask(task) { return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, this.getDotenvVariablesForForkedProcess()), parseEnv(`.${task.target.target}.env`)), parseEnv(`.env.${task.target.target}`)), parseEnv(`${task.projectRoot}/.env`)), parseEnv(`${task.projectRoot}/.local.env`)), parseEnv(`${task.projectRoot}/.env.local`)), parseEnv(`${task.projectRoot}/.${task.target.target}.env`)), parseEnv(`${task.projectRoot}/.env.${task.target.target}`)); } // endregion Environment Variables signalToCode(signal) { if (signal === 'SIGHUP') return 128 + 1; if (signal === 'SIGINT') return 128 + 2; if (signal === 'SIGTERM') return 128 + 15; return 128; } setupOnProcessExitListener() { process.on('SIGINT', () => { this.processes.forEach((p) => { p.kill('SIGTERM'); }); // we exit here because we don't need to write anything to cache. process.exit(); }); process.on('SIGTERM', () => { this.processes.forEach((p) => { p.kill('SIGTERM'); }); // no exit here because we expect child processes to terminate which // will store results to the cache and will terminate this process }); process.on('SIGHUP', () => { this.processes.forEach((p) => { p.kill('SIGTERM'); }); // no exit here because we expect child processes to terminate which // will store results to the cache and will terminate this process }); } } exports.ForkedProcessTaskRunner = ForkedProcessTaskRunner; function parseEnv(path) { try { const envContents = (0, fs_1.readFileSync)(path); return dotenv.parse(envContents); } catch (e) { } } //# sourceMappingURL=forked-process-task-runner.js.map