Skip to main content
Module

std/node/child_process.ts

Deno standard library
Go to Latest
File
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782
// Copyright 2018-2023 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 { core } from "./_core.ts";import { ChildProcess, ChildProcessOptions, normalizeSpawnArguments, spawnSync as _spawnSync, type SpawnSyncOptions, type SpawnSyncResult, stdioStringToArray,} from "./internal/child_process.ts";import { validateAbortSignal, validateFunction, validateObject, validateString,} from "./internal/validators.mjs";import { ERR_CHILD_PROCESS_IPC_REQUIRED, ERR_CHILD_PROCESS_STDIO_MAXBUFFER, ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE, ERR_OUT_OF_RANGE, genericNodeError,} from "./internal/errors.ts";import { ArrayIsArray, ArrayPrototypeJoin, ArrayPrototypePush, ArrayPrototypeSlice, ObjectAssign, StringPrototypeSlice,} from "./internal/primordials.mjs";import { getSystemErrorName, promisify } from "./util.ts";import { createDeferredPromise } from "./internal/util.mjs";import { process } from "./process.ts";import { Buffer } from "./buffer.ts";import { convertToValidSignal, kEmptyObject } from "./internal/util.mjs";
const MAX_BUFFER = 1024 * 1024;
type ForkOptions = ChildProcessOptions;
/** * Spawns a new Node.js process + fork. * @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 = [ "run", "--unstable", // TODO(kt3k): Remove when npm: is stable "--node-modules-dir", "-A", ...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;
Object.assign(options.env ??= {}, { DENO_DONT_USE_INTERNAL_NODE_COMPAT_STATE: core.ops .op_npm_process_state(), });
return spawn(options.execPath, args, options);}
// 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; validateAbortSignal(options?.signal, "options.signal"); 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, ); }}
function sanitizeKillSignal(killSignal?: string | number) { if (typeof killSignal === "string" || typeof killSignal === "number") { return convertToValidSignal(killSignal); } else if (killSignal != null) { throw new ERR_INVALID_ARG_TYPE( "options.killSignal", ["string", "number"], killSignal, ); }}
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, ...normalizeSpawnArguments(command, args, options), };
// Validate the timeout, if present. validateTimeout(options.timeout);
// Validate maxBuffer, if present. validateMaxBuffer(options.maxBuffer);
// Validate and translate the kill signal, if present. sanitizeKillSignal(options.killSignal);
return _spawnSync(command, args, options);}
interface ExecOptions extends Pick< ChildProcessOptions, | "env" | "signal" | "uid" | "gid" | "windowsHide" > { cwd?: string | URL; encoding?: string; /** * Shell to execute the command with. */ shell?: string; timeout?: number; /** * Largest amount of data in bytes allowed on stdout or stderr. If exceeded, the child process is terminated and any output is truncated. */ maxBuffer?: number; killSignal?: string | number;}type ExecException = ChildProcessError;type ExecCallback = ( error: ExecException | null, stdout?: string | Buffer, stderr?: string | Buffer,) => void;type ExecSyncOptions = SpawnSyncOptions;type ExecFileSyncOptions = SpawnSyncOptions;function normalizeExecArgs( command: string, optionsOrCallback?: ExecOptions | ExecSyncOptions | ExecCallback, maybeCallback?: ExecCallback,) { let callback: ExecFileCallback | undefined = maybeCallback;
if (typeof optionsOrCallback === "function") { callback = optionsOrCallback; optionsOrCallback = undefined; }
// Make a shallow copy so we don't clobber the user's options object. const options: ExecOptions | ExecSyncOptions = { ...optionsOrCallback }; options.shell = typeof options.shell === "string" ? options.shell : true;
return { file: command, options: options!, callback: callback!, };}
/** * Spawns a shell executing the given command. */export function exec(command: string): ChildProcess;export function exec(command: string, options: ExecOptions): ChildProcess;export function exec(command: string, callback: ExecCallback): ChildProcess;export function exec( command: string, options: ExecOptions, callback: ExecCallback,): ChildProcess;export function exec( command: string, optionsOrCallback?: ExecOptions | ExecCallback, maybeCallback?: ExecCallback,): ChildProcess { const opts = normalizeExecArgs(command, optionsOrCallback, maybeCallback); return execFile(opts.file, opts.options as ExecFileOptions, opts.callback);}
interface PromiseWithChild<T> extends Promise<T> { child: ChildProcess;}type ExecOutputForPromisify = { stdout?: string | Buffer; stderr?: string | Buffer;};type ExecExceptionForPromisify = ExecException & ExecOutputForPromisify;
const customPromiseExecFunction = (orig: typeof exec) => { return (...args: [command: string, options: ExecOptions]) => { const { promise, resolve, reject } = createDeferredPromise() as unknown as { promise: PromiseWithChild<ExecOutputForPromisify>; resolve?: (value: ExecOutputForPromisify) => void; reject?: (reason?: ExecExceptionForPromisify) => void; };
promise.child = orig(...args, (err, stdout, stderr) => { if (err !== null) { const _err: ExecExceptionForPromisify = err; _err.stdout = stdout; _err.stderr = stderr; reject && reject(_err); } else { resolve && resolve({ stdout, stderr }); } });
return promise; };};
Object.defineProperty(exec, promisify.custom, { enumerable: false, value: customPromiseExecFunction(exec),});
interface ExecFileOptions extends ChildProcessOptions { encoding?: string; timeout?: number; maxBuffer?: number; killSignal?: string | number;}interface ChildProcessError extends Error { code?: string | number; killed?: boolean; signal?: AbortSignal; 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", shell: false, ...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: SpawnOptions = { cwd: execOptions.cwd, env: execOptions.env, gid: execOptions.gid, shell: execOptions.shell, signal: execOptions.signal, uid: execOptions.uid, windowsHide: !!execOptions.windowsHide, windowsVerbatimArguments: !!execOptions.windowsVerbatimArguments, };
const child = spawn(file, args, spawnOptions);
let encoding: string | null; const _stdout: (string | Uint8Array)[] = []; const _stderr: (string | Uint8Array)[] = []; if ( execOptions.encoding !== "buffer" && Buffer.isEncoding(execOptions.encoding) ) { encoding = execOptions.encoding; } else { encoding = null; } let stdoutLen = 0; let stderrLen = 0; let killed = false; let exited = false; let timeoutId: number | null;
let ex: ChildProcessError | null = null;
let cmd = file;
function exithandler(code = 0, signal?: AbortSignal) { 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 as Buffer[]); } if ( encoding || ( child.stderr && child.stderr.readableEncoding ) ) { stderr = _stderr.join(""); } else { stderr = Buffer.concat(_stderr as Buffer[]); }
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 || 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(); }
killed = true; try { child.kill(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) { // Do not need to count the length if (execOptions.maxBuffer === Infinity) { ArrayPrototypePush(_stdout, chunk); return; }
const encoding = child.stdout?.readableEncoding; const length = encoding ? Buffer.byteLength(chunk, encoding) : chunk.length; const slice = encoding ? StringPrototypeSlice : (buf: string | Buffer, ...args: number[]) => buf.slice(...args); stdoutLen += length;
if (stdoutLen > execOptions.maxBuffer) { const truncatedLen = execOptions.maxBuffer - (stdoutLen - length); ArrayPrototypePush(_stdout, slice(chunk, 0, truncatedLen));
ex = new ERR_CHILD_PROCESS_STDIO_MAXBUFFER("stdout"); kill(); } else { ArrayPrototypePush(_stdout, chunk); } }); }
if (child.stderr) { if (encoding) { child.stderr.setEncoding(encoding); }
child.stderr.on("data", function onChildStderr(chunk: string | Buffer) { // Do not need to count the length if (execOptions.maxBuffer === Infinity) { ArrayPrototypePush(_stderr, chunk); return; }
const encoding = child.stderr?.readableEncoding; const length = encoding ? Buffer.byteLength(chunk, encoding) : chunk.length; const slice = encoding ? StringPrototypeSlice : (buf: string | Buffer, ...args: number[]) => buf.slice(...args); stderrLen += length;
if (stderrLen > execOptions.maxBuffer) { const truncatedLen = execOptions.maxBuffer - (stderrLen - length); ArrayPrototypePush(_stderr, slice(chunk, 0, truncatedLen));
ex = new ERR_CHILD_PROCESS_STDIO_MAXBUFFER("stderr"); kill(); } else { ArrayPrototypePush(_stderr, chunk); } }); }
child.addListener("close", exithandler); child.addListener("error", errorhandler);
return child;}
function checkExecSyncError( ret: SpawnSyncResult, args: string[], cmd?: string,) { let err; if (ret.error) { err = ret.error; ObjectAssign(err, ret); } else if (ret.status !== 0) { let msg = "Command failed: "; msg += cmd || ArrayPrototypeJoin(args, " "); if (ret.stderr && ret.stderr.length > 0) { msg += `\n${ret.stderr.toString()}`; } err = genericNodeError(msg, ret); } return err;}
export function execSync(command: string, options: ExecSyncOptions) { const opts = normalizeExecArgs(command, options); const inheritStderr = !(opts.options as ExecSyncOptions).stdio;
const ret = spawnSync(opts.file, opts.options as SpawnSyncOptions);
if (inheritStderr && ret.stderr) { process.stderr.write(ret.stderr); }
const err = checkExecSyncError(ret, [], command);
if (err) { throw err; }
return ret.stdout;}
function normalizeExecFileArgs( file: string, args?: string[] | null | ExecFileSyncOptions | ExecFileCallback, options?: ExecFileSyncOptions | null | ExecFileCallback, callback?: ExecFileCallback,): { file: string; args: string[]; options: ExecFileSyncOptions; callback?: ExecFileCallback;} { if (ArrayIsArray(args)) { args = ArrayPrototypeSlice(args); } else if (args != null && typeof args === "object") { callback = options as ExecFileCallback; options = args as ExecFileSyncOptions; args = null; } else if (typeof args === "function") { callback = args; options = null; args = null; }
if (args == null) { args = []; }
if (typeof options === "function") { callback = options as ExecFileCallback; } else if (options != null) { validateObject(options, "options"); }
if (options == null) { options = kEmptyObject; }
args = args as string[]; options = options as ExecFileSyncOptions;
if (callback != null) { validateFunction(callback, "callback"); }
// Validate argv0, if present. if (options.argv0 != null) { validateString(options.argv0, "options.argv0"); }
return { file, args, options, callback };}
export function execFileSync(file: string): string | Buffer;export function execFileSync(file: string, args: string[]): string | Buffer;export function execFileSync( file: string, options: ExecFileSyncOptions,): string | Buffer;export function execFileSync( file: string, args: string[], options: ExecFileSyncOptions,): string | Buffer;export function execFileSync( file: string, args?: string[] | ExecFileSyncOptions, options?: ExecFileSyncOptions,): string | Buffer { ({ file, args, options } = normalizeExecFileArgs(file, args, options));
const inheritStderr = !options.stdio; const ret = spawnSync(file, args, options);
if (inheritStderr && ret.stderr) { process.stderr.write(ret.stderr); }
const errArgs: string[] = [options.argv0 || file, ...(args as string[])]; const err = checkExecSyncError(ret, errArgs);
if (err) { throw err; }
return ret.stdout as string | Buffer;}
export default { fork, spawn, exec, execFile, execFileSync, execSync, ChildProcess, spawnSync,};export { ChildProcess };