deno.land / std@0.157.0 / node / child_process.ts
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
// This module implements 'child_process' module of Node.JS API.// ref: https://nodejs.org/api/child_process.htmlimport { ChildProcess, ChildProcessOptions, spawnSync as _spawnSync, type SpawnSyncOptions, type SpawnSyncResult, stdioStringToArray,} from "./internal/child_process.ts";import { validateString } from "./internal/validators.mjs";import { ERR_CHILD_PROCESS_IPC_REQUIRED, ERR_CHILD_PROCESS_STDIO_MAXBUFFER, ERR_INVALID_ARG_VALUE, ERR_OUT_OF_RANGE,} from "./internal/errors.ts";import { getSystemErrorName } from "./util.ts";import { process } from "./process.ts";import { Buffer } from "./buffer.ts";import { notImplemented } from "./_utils.ts";
const MAX_BUFFER = 1024 * 1024;
/** * Spawns a new Node.js process + fork. Not implmeneted yet. * @param modulePath * @param args * @param option * @returns */export function fork( modulePath: string, /* args?: string[], options?: ForkOptions*/) { validateString(modulePath, "modulePath");
// Get options and args arguments. let execArgv; let options: SpawnOptions & { execArgv?: string; execPath?: string; silent?: boolean; } = {}; let args: string[] = []; let pos = 1; if (pos < arguments.length && Array.isArray(arguments[pos])) { args = arguments[pos++]; }
if (pos < arguments.length && arguments[pos] == null) { pos++; }
if (pos < arguments.length && arguments[pos] != null) { if (typeof arguments[pos] !== "object") { throw new ERR_INVALID_ARG_VALUE(`arguments[${pos}]`, arguments[pos]); }
options = { ...arguments[pos++] }; }
// Prepare arguments for fork: execArgv = options.execArgv || process.execArgv;
if (execArgv === process.execArgv && process._eval != null) { const index = execArgv.lastIndexOf(process._eval); if (index > 0) { // Remove the -e switch to avoid fork bombing ourselves. execArgv = execArgv.slice(0); execArgv.splice(index - 1, 2); } }
// TODO(bartlomieju): this is incomplete, currently only handling a single // V8 flag to get Prisma integration running, we should fill this out with // more const v8Flags: string[] = []; if (Array.isArray(execArgv)) { for (let index = 0; index < execArgv.length; index++) { const flag = execArgv[index]; if (flag.startsWith("--max-old-space-size")) { execArgv.splice(index, 1); v8Flags.push(flag); } } } const stringifiedV8Flags: string[] = []; if (v8Flags.length > 0) { stringifiedV8Flags.push("--v8-flags=" + v8Flags.join(",")); } args = [ // TODO(kt3k): Find corrct args for `fork` execution ...[], ...stringifiedV8Flags, ...execArgv, modulePath, ...args, ];
if (typeof options.stdio === "string") { options.stdio = stdioStringToArray(options.stdio, "ipc"); } else if (!Array.isArray(options.stdio)) { // Use a separate fd=3 for the IPC channel. Inherit stdin, stdout, // and stderr from the parent if silent isn't set. options.stdio = stdioStringToArray( options.silent ? "pipe" : "inherit", "ipc", ); } else if (!options.stdio.includes("ipc")) { throw new ERR_CHILD_PROCESS_IPC_REQUIRED("options.stdio"); }
options.execPath = options.execPath || Deno.execPath(); options.shell = false;
notImplemented("child_process.fork");}
// deno-lint-ignore no-empty-interfaceinterface SpawnOptions extends ChildProcessOptions {}export function spawn(command: string): ChildProcess;export function spawn(command: string, options: SpawnOptions): ChildProcess;export function spawn(command: string, args: string[]): ChildProcess;export function spawn( command: string, args: string[], options: SpawnOptions,): ChildProcess;/** * Spawns a child process using `command`. */export function spawn( command: string, argsOrOptions?: string[] | SpawnOptions, maybeOptions?: SpawnOptions,): ChildProcess { const args = Array.isArray(argsOrOptions) ? argsOrOptions : []; const options = !Array.isArray(argsOrOptions) && argsOrOptions != null ? argsOrOptions : maybeOptions; return new ChildProcess(command, args, options);}
function validateTimeout(timeout?: number) { if (timeout != null && !(Number.isInteger(timeout) && timeout >= 0)) { throw new ERR_OUT_OF_RANGE("timeout", "an unsigned integer", timeout); }}
function validateMaxBuffer(maxBuffer?: number) { if ( maxBuffer != null && !(typeof maxBuffer === "number" && maxBuffer >= 0) ) { throw new ERR_OUT_OF_RANGE( "options.maxBuffer", "a positive number", maxBuffer, ); }}
export function spawnSync( command: string, argsOrOptions?: string[] | SpawnSyncOptions, maybeOptions?: SpawnSyncOptions,): SpawnSyncResult { const args = Array.isArray(argsOrOptions) ? argsOrOptions : []; let options = !Array.isArray(argsOrOptions) && argsOrOptions ? argsOrOptions : maybeOptions as SpawnSyncOptions;
options = { maxBuffer: MAX_BUFFER, ...options, };
// Validate the timeout, if present. validateTimeout(options.timeout);
// Validate maxBuffer, if present. validateMaxBuffer(options.maxBuffer);
return _spawnSync(command, args, options);}
interface ExecFileOptions extends ChildProcessOptions { encoding?: string; timeout?: number; maxBuffer?: number; killSignal?: string;}interface ChildProcessError extends Error { code?: string | number; killed?: boolean; signal?: string; cmd?: string;}class ExecFileError extends Error implements ChildProcessError { code?: string | number;
constructor(message: string) { super(message); this.code = "UNKNOWN"; }}type ExecFileCallback = ( error: ChildProcessError | null, stdout?: string | Buffer, stderr?: string | Buffer,) => void;export function execFile(file: string): ChildProcess;export function execFile( file: string, callback: ExecFileCallback,): ChildProcess;export function execFile(file: string, args: string[]): ChildProcess;export function execFile( file: string, args: string[], callback: ExecFileCallback,): ChildProcess;export function execFile(file: string, options: ExecFileOptions): ChildProcess;export function execFile( file: string, options: ExecFileOptions, callback: ExecFileCallback,): ChildProcess;export function execFile( file: string, args: string[], options: ExecFileOptions, callback: ExecFileCallback,): ChildProcess;export function execFile( file: string, argsOrOptionsOrCallback?: string[] | ExecFileOptions | ExecFileCallback, optionsOrCallback?: ExecFileOptions | ExecFileCallback, maybeCallback?: ExecFileCallback,): ChildProcess { let args: string[] = []; let options: ExecFileOptions = {}; let callback: ExecFileCallback | undefined;
if (Array.isArray(argsOrOptionsOrCallback)) { args = argsOrOptionsOrCallback; } else if (argsOrOptionsOrCallback instanceof Function) { callback = argsOrOptionsOrCallback; } else if (argsOrOptionsOrCallback) { options = argsOrOptionsOrCallback; } if (optionsOrCallback instanceof Function) { callback = optionsOrCallback; } else if (optionsOrCallback) { options = optionsOrCallback; callback = maybeCallback; }
const execOptions = { encoding: "utf8", timeout: 0, maxBuffer: MAX_BUFFER, killSignal: "SIGTERM", ...options, }; if (!Number.isInteger(execOptions.timeout) || execOptions.timeout < 0) { // In Node source, the first argument to error constructor is "timeout" instead of "options.timeout". // timeout is indeed a member of options object. throw new ERR_OUT_OF_RANGE( "timeout", "an unsigned integer", execOptions.timeout, ); } if (execOptions.maxBuffer < 0) { throw new ERR_OUT_OF_RANGE( "options.maxBuffer", "a positive number", execOptions.maxBuffer, ); } const spawnOptions = { shell: false, ...options, };
const child = spawn(file, args, spawnOptions);
let encoding: string | null; const _stdout: Uint8Array[] = []; const _stderr: Uint8Array[] = []; if ( execOptions.encoding !== "buffer" && Buffer.isEncoding(execOptions.encoding) ) { encoding = execOptions.encoding; } else { encoding = null; } let stdoutLen = 0; let stderrLen = 0; let exited = false; let timeoutId: number | null;
let ex: ChildProcessError | null = null;
let cmd = file;
function exithandler(code = 0, signal?: string) { if (exited) return; exited = true;
if (timeoutId) { clearTimeout(timeoutId); timeoutId = null; }
if (!callback) return;
// merge chunks let stdout; let stderr; if ( encoding || ( child.stdout && child.stdout.readableEncoding ) ) { stdout = _stdout.join(""); } else { stdout = Buffer.concat(_stdout); } if ( encoding || ( child.stderr && child.stderr.readableEncoding ) ) { stderr = _stderr.join(""); } else { stderr = Buffer.concat(_stderr); }
if (!ex && code === 0 && signal === null) { callback(null, stdout, stderr); return; }
if (args?.length) { cmd += ` ${args.join(" ")}`; }
if (!ex) { ex = new ExecFileError( "Command failed: " + cmd + "\n" + stderr, ); ex.code = code < 0 ? getSystemErrorName(code) : code; ex.killed = child.killed; ex.signal = signal; }
ex.cmd = cmd; callback(ex, stdout, stderr); }
function errorhandler(e: ExecFileError) { ex = e;
if (child.stdout) { child.stdout.destroy(); }
if (child.stderr) { child.stderr.destroy(); }
exithandler(); }
function kill() { if (child.stdout) { child.stdout.destroy(); }
if (child.stderr) { child.stderr.destroy(); }
try { child.kill(/** TODO use execOptions.killSignal */); } catch (e) { if (e) { ex = e as ChildProcessError; } exithandler(); } }
if (execOptions.timeout > 0) { timeoutId = setTimeout(function delayedKill() { kill(); timeoutId = null; }, execOptions.timeout); }
if (child.stdout) { if (encoding) { child.stdout.setEncoding(encoding); }
child.stdout.on("data", function onChildStdout(chunk: string | Buffer) { const encoding = child.stdout?.readableEncoding;
let chunkBuffer: Buffer; if (Buffer.isBuffer(chunk)) { chunkBuffer = chunk; } else { if (encoding) { chunkBuffer = Buffer.from(chunk as string, encoding); } else { // TODO choose what to do if encoding is not set but chunk is a string (should not happen) chunkBuffer = Buffer.from(""); } }
const length = chunkBuffer.length; stdoutLen += length;
if (stdoutLen > execOptions.maxBuffer) { const truncatedLen = execOptions.maxBuffer - (stdoutLen - length); const truncatedSlice = chunkBuffer.slice(0, truncatedLen).valueOf(); _stdout.push(truncatedSlice);
ex = new ERR_CHILD_PROCESS_STDIO_MAXBUFFER("stdout"); kill(); } else { _stdout.push(chunkBuffer.valueOf()); } }); }
if (child.stderr) { if (encoding) { child.stderr.setEncoding(encoding); }
child.stderr.on("data", function onChildStderr(chunk: string | Buffer) { const encoding = child.stderr?.readableEncoding;
let chunkBuffer: Buffer; if (Buffer.isBuffer(chunk)) { chunkBuffer = chunk; } else { if (encoding) { chunkBuffer = Buffer.from(chunk as string, encoding); } else { // TODO choose what to do if encoding is not set but chunk is a string (should not happen) chunkBuffer = Buffer.from(""); } }
const length = chunkBuffer.length; stderrLen += length;
if (stderrLen > execOptions.maxBuffer) { const truncatedLen = execOptions.maxBuffer - (stderrLen - length); const truncatedSlice = chunkBuffer.slice(0, truncatedLen).valueOf(); _stderr.push(truncatedSlice);
ex = new ERR_CHILD_PROCESS_STDIO_MAXBUFFER("stderr"); kill(); } else { _stderr.push(chunkBuffer.valueOf()); } }); }
child.addListener("close", exithandler); child.addListener("error", errorhandler);
return child;}
export function execSync() { throw new Error("execSync is currently not supported");}
export default { fork, spawn, execFile, execSync, ChildProcess, spawnSync };export { ChildProcess };
Version Info