// Hardcoded module "node:child_process" const EventEmitter = require("node:events"); const OsModule = require("node:os"); const { kHandle } = require("internal/shared"); const { validateBoolean, validateFunction, validateString, validateAbortSignal, validateArray, validateObject, validateOneOf, } = require("internal/validators"); var NetModule; var ObjectCreate = Object.create; var ObjectAssign = Object.assign; var BufferConcat = Buffer.concat; var BufferIsEncoding = Buffer.isEncoding; var kEmptyObject = ObjectCreate(null); var signals = OsModule.constants.signals; var ArrayPrototypeJoin = Array.prototype.join; var ArrayPrototypeIncludes = Array.prototype.includes; var ArrayPrototypeSlice = Array.prototype.slice; var ArrayPrototypeUnshift = Array.prototype.unshift; const ArrayPrototypeFilter = Array.prototype.filter; const ArrayPrototypeSort = Array.prototype.sort; const StringPrototypeToUpperCase = String.prototype.toUpperCase; const ArrayPrototypePush = Array.prototype.push; const ArrayPrototypeLastIndexOf = Array.prototype.lastIndexOf; const ArrayPrototypeSplice = Array.prototype.splice; var ArrayBufferIsView = ArrayBuffer.isView; var NumberIsInteger = Number.isInteger; var StringPrototypeIncludes = String.prototype.includes; var Uint8ArrayPrototypeIncludes = Uint8Array.prototype.includes; const MAX_BUFFER = 1024 * 1024; const kFromNode = Symbol("kFromNode"); // Pass DEBUG_CHILD_PROCESS=1 to enable debug output if ($debug) { $debug("child_process: debug mode on"); globalThis.__lastId = null; globalThis.__getId = () => { return globalThis.__lastId !== null ? globalThis.__lastId++ : 0; }; } // Sections: // 1. Exported child_process functions // 2. child_process helpers // 3. ChildProcess "class" // 4. ChildProcess helpers // 5. Validators // 6. Random utilities // 7. Node errors / error polyfills // TODO: // Port rest of node tests // Fix exit codes with Bun.spawn // ------------------------------ // Fix errors // Support file descriptors being passed in for stdio // ------------------------------ // TODO: Look at Pipe to see if we can support passing Node Pipe objects to stdio param // TODO: Add these params after support added in Bun.spawn // uid Sets the user identity of the process (see setuid(2)). // gid Sets the group identity of the process (see setgid(2)). // stdio | Child's stdio configuration (see options.stdio). // Support wrapped ipc types (e.g. net.Socket, dgram.Socket, TTY, etc.) // IPC FD passing support // From node child_process docs(https://nodejs.org/api/child_process.html#optionsstdio): // 'ipc': Create an IPC channel for passing messages/file descriptors between parent and child. // A ChildProcess may have at most one IPC stdio file descriptor. Setting this option enables the subprocess.send() method. // If the child is a Node.js process, the presence of an IPC channel will enable process.send() and process.disconnect() methods, // as well as 'disconnect' and 'message' events within the child. //------------------------------------------------------------------------------ // Section 1. Exported child_process functions //------------------------------------------------------------------------------ // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit // persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. /** * Spawns a new process using the given `file`. * @param {string} file * @param {string[]} [args] * @param {{ * cwd?: string; * env?: Record; * argv0?: string; * stdio?: Array | string; * detached?: boolean; * uid?: number; * gid?: number; * serialization?: string; * shell?: boolean | string; * windowsHide?: boolean; * windowsVerbatimArguments?: boolean; * signal?: AbortSignal; * timeout?: number; * killSignal?: string | number; * }} [options] * @returns {ChildProcess} */ function spawn(file, args, options) { options = normalizeSpawnArguments(file, args, options); validateTimeout(options.timeout); validateAbortSignal(options.signal, "options.signal"); const killSignal = sanitizeKillSignal(options.killSignal); const child = new ChildProcess(); $debug("spawn", options); options[kFromNode] = true; child.spawn(options); const timeout = options.timeout; if (timeout && timeout > 0) { let timeoutId: Timer | null = setTimeout(() => { if (timeoutId) { timeoutId = null; try { child.kill(killSignal); } catch (err) { child.emit("error", err); } } }, timeout).unref(); child.once("exit", () => { if (timeoutId) { clearTimeout(timeoutId); timeoutId = null; } }); } const signal = options.signal; if (signal) { if (signal.aborted) { process.nextTick(onAbortListener); } else { signal.addEventListener("abort", onAbortListener, { once: true }); child.once("exit", () => signal.removeEventListener("abort", onAbortListener)); } function onAbortListener() { abortChildProcess(child, killSignal, signal.reason); } } return child; } /** * Spawns the specified file as a shell. * @param {string} file * @param {string[]} [args] * @param {{ * cwd?: string; * env?: Record; * encoding?: string; * timeout?: number; * maxBuffer?: number; * killSignal?: string | number; * uid?: number; * gid?: number; * windowsHide?: boolean; * windowsVerbatimArguments?: boolean; * shell?: boolean | string; * signal?: AbortSignal; * }} [options] * @param {( * error?: Error, * stdout?: string | Buffer, * stderr?: string | Buffer * ) => any} [callback] * @returns {ChildProcess} */ function execFile(file, args, options, callback) { ({ file, args, options, callback } = normalizeExecFileArgs(file, args, options, callback)); options = { __proto__: null, encoding: "utf8", timeout: 0, maxBuffer: MAX_BUFFER, killSignal: "SIGTERM", cwd: null, env: null, shell: false, ...options, }; const maxBuffer = options.maxBuffer; // Validate the timeout, if present. validateTimeout(options.timeout); // Validate maxBuffer, if present. validateMaxBuffer(maxBuffer); options.killSignal = sanitizeKillSignal(options.killSignal); const child = spawn(file, args, { cwd: options.cwd, env: options.env, timeout: options.timeout, killSignal: options.killSignal, uid: options.uid, gid: options.gid, windowsHide: options.windowsHide, windowsVerbatimArguments: options.windowsVerbatimArguments, shell: options.shell, signal: options.signal, }); let encoding; const _stdout = []; const _stderr = []; if (options.encoding !== "buffer" && BufferIsEncoding(options.encoding)) { encoding = options.encoding; } else { encoding = null; } let killed = false; let exited = false; let timeoutId; let ex: Error | null = null; let cmd = file; function exitHandler(code = 0, signal?: number | null) { if (exited) return; exited = true; if (timeoutId) { clearTimeout(timeoutId); timeoutId = null; } if (!callback) return; // merge chunks let stdout; let stderr; if (encoding || child.stdout?.readableEncoding) { stdout = ArrayPrototypeJoin.$call(_stdout, ""); } else { stdout = BufferConcat(_stdout); } if (encoding || child.stderr?.readableEncoding) { stderr = ArrayPrototypeJoin.$call(_stderr, ""); } else { stderr = BufferConcat(_stderr); } if (!ex && code === 0 && signal === null) { callback(null, stdout, stderr); return; } if (args?.length) cmd += ` ${ArrayPrototypeJoin.$call(args, " ")}`; if (!ex) { const { getSystemErrorName } = require("node:util"); let message = `Command failed: ${cmd}`; if (stderr) message += `\n${stderr}`; ex = genericNodeError(message, { code: code < 0 ? getSystemErrorName(code) : code, killed: child.killed || killed, signal: signal, }); } ex.cmd = cmd; callback(ex, stdout, stderr); } function errorHandler(e) { ex = e; const { stdout, stderr } = child; if (stdout) stdout.destroy(); if (stderr) stderr.destroy(); exitHandler(); } function kill() { const { stdout, stderr } = child; if (stdout) stdout.destroy(); if (stderr) stderr.destroy(); killed = true; try { child.kill(options.killSignal); } catch (e) { ex = e; exitHandler(); } } if (options.timeout > 0) { timeoutId = setTimeout(function delayedKill() { timeoutId = null; kill(); }, options.timeout).unref(); } function addOnDataListener(child_buffer, _buffer, kind) { if (encoding) child_buffer.setEncoding(encoding); let totalLen = 0; if (maxBuffer === Infinity) { child_buffer.on("data", function onDataNoMaxBuf(chunk) { $arrayPush(_buffer, chunk); }); return; } child_buffer.on("data", function onData(chunk) { const encoding = child_buffer.readableEncoding; if (encoding) { const length = Buffer.byteLength(chunk, encoding); totalLen += length; if (totalLen > maxBuffer) { const truncatedLen = maxBuffer - (totalLen - length); $arrayPush(_buffer, String.prototype.slice.$call(chunk, 0, truncatedLen)); ex = $ERR_CHILD_PROCESS_STDIO_MAXBUFFER(kind); kill(); } else { $arrayPush(_buffer, chunk); } } else { const length = chunk.length; totalLen += length; if (totalLen > maxBuffer) { const truncatedLen = maxBuffer - (totalLen - length); $arrayPush(_buffer, chunk.slice(0, truncatedLen)); ex = $ERR_CHILD_PROCESS_STDIO_MAXBUFFER(kind); kill(); } else { $arrayPush(_buffer, chunk); } } }); } if (child.stdout) addOnDataListener(child.stdout, _stdout, "stdout"); if (child.stderr) addOnDataListener(child.stderr, _stderr, "stderr"); child.addListener("close", exitHandler); child.addListener("error", errorHandler); return child; } /** * Spawns a shell executing the given command. * @param {string} command * @param {{ * cmd?: string; * env?: Record; * encoding?: string; * shell?: string; * signal?: AbortSignal; * timeout?: number; * maxBuffer?: number; * killSignal?: string | number; * uid?: number; * gid?: number; * windowsHide?: boolean; * windowsVerbatimArguments?: boolean; * }} [options] * @param {( * error?: Error, * stdout?: string | Buffer, * stderr?: string | Buffer * ) => any} [callback] * @returns {ChildProcess} */ function exec(command, options, callback) { const opts = normalizeExecArgs(command, options, callback); return execFile(opts.file, opts.options, opts.callback); } const kCustomPromisifySymbol = Symbol.for("nodejs.util.promisify.custom"); const customPromiseExecFunction = orig => { return (...args) => { const { resolve, reject, promise } = Promise.withResolvers(); promise.child = orig(...args, (err, stdout, stderr) => { if (err !== null) { err.stdout = stdout; err.stderr = stderr; reject(err); } else { resolve({ stdout, stderr }); } }); return promise; }; }; Object.defineProperty(exec, kCustomPromisifySymbol, { __proto__: null, configurable: true, value: customPromiseExecFunction(exec), }); exec[kCustomPromisifySymbol][kCustomPromisifySymbol] = exec[kCustomPromisifySymbol]; Object.defineProperty(execFile, kCustomPromisifySymbol, { __proto__: null, configurable: true, value: customPromiseExecFunction(execFile), }); execFile[kCustomPromisifySymbol][kCustomPromisifySymbol] = execFile[kCustomPromisifySymbol]; /** * Spawns a new process synchronously using the given `file`. * @param {string} file * @param {string[]} [args] * @param {{ * cwd?: string; * input?: string | Buffer | TypedArray | DataView; * argv0?: string; * stdio?: string | Array; * env?: Record; * uid?: number; * gid?: number; * timeout?: number; * killSignal?: string | number; * maxBuffer?: number; * encoding?: string; * shell?: boolean | string; * windowsHide?: boolean; * windowsVerbatimArguments?: boolean; * }} [options] * @returns {{ * pid: number; * output: Array; * stdout: Buffer | string; * stderr: Buffer | string; * status: number | null; * signal: string | null; * error: Error; * }} */ function spawnSync(file, args, options) { options = { __proto__: null, maxBuffer: MAX_BUFFER, ...normalizeSpawnArguments(file, args, options), }; const maxBuffer = options.maxBuffer; const encoding = options.encoding; $debug("spawnSync", options); // Validate the timeout, if present. validateTimeout(options.timeout); // Validate maxBuffer, if present. validateMaxBuffer(maxBuffer); // Validate and translate the kill signal, if present. options.killSignal = sanitizeKillSignal(options.killSignal); const stdio = options.stdio || "pipe"; const bunStdio = getBunStdioFromOptions(stdio); var { input } = options; if (input) { if (ArrayBufferIsView(input)) { bunStdio[0] = input; } else if (typeof input === "string") { bunStdio[0] = Buffer.from(input, encoding || "utf8"); } else { throw $ERR_INVALID_ARG_TYPE(`options.stdio[0]`, ["string", "Buffer", "TypedArray", "DataView"], input); } } var error; try { var { stdout = null, stderr = null, exitCode, signalCode, exitedDueToTimeout, exitedDueToMaxBuffer, pid, } = Bun.spawnSync({ // normalizeSpawnargs has already prepended argv0 to the spawnargs array // Bun.spawn() expects cmd[0] to be the command to run, and argv0 to replace the first arg when running the command, // so we have to set argv0 to spawnargs[0] and cmd[0] to file cmd: [options.file, ...Array.prototype.slice.$call(options.args, 1)], env: options.env || undefined, cwd: options.cwd || undefined, stdio: bunStdio, windowsVerbatimArguments: options.windowsVerbatimArguments, windowsHide: options.windowsHide, argv0: options.args[0], timeout: options.timeout, killSignal: options.killSignal, maxBuffer: options.maxBuffer, }); } catch (err) { error = err; stdout = null; stderr = null; } // When stdio is redirected to a file descriptor, Bun.spawnSync returns the fd number // instead of the actual output. We should treat this as no output available. const outputStdout = typeof stdout === "number" ? null : stdout; const outputStderr = typeof stderr === "number" ? null : stderr; const result = { signal: signalCode ?? null, status: exitCode, // TODO: Need to expose extra pipes from Bun.spawnSync to child_process output: [null, outputStdout, outputStderr], pid, }; if (error) { result.error = error; } if (outputStdout && encoding && encoding !== "buffer") { result.output[1] = result.output[1]?.toString(encoding); } if (outputStderr && encoding && encoding !== "buffer") { result.output[2] = result.output[2]?.toString(encoding); } result.stdout = result.output[1]; result.stderr = result.output[2]; if (exitedDueToTimeout && error == null) { result.error = new SystemError( "spawnSync " + options.file + " ETIMEDOUT", options.file, "spawnSync " + options.file, etimedoutErrorCode(), "ETIMEDOUT", ); } if (exitedDueToMaxBuffer && error == null) { result.error = new SystemError( "spawnSync " + options.file + " ENOBUFS (stdout or stderr buffer reached maxBuffer size limit)", options.file, "spawnSync " + options.file, enobufsErrorCode(), "ENOBUFS", ); } if (result.error) { result.error.syscall = "spawnSync " + options.file; result.error.spawnargs = ArrayPrototypeSlice.$call(options.args, 1); } return result; } const etimedoutErrorCode = $newZigFunction("node_util_binding.zig", "etimedoutErrorCode", 0); const enobufsErrorCode = $newZigFunction("node_util_binding.zig", "enobufsErrorCode", 0); /** * Spawns a file as a shell synchronously. * @param {string} file * @param {string[]} [args] * @param {{ * cwd?: string; * input?: string | Buffer | TypedArray | DataView; * stdio?: string | Array; * env?: Record; * uid?: number; * gid?: number; * timeout?: number; * killSignal?: string | number; * maxBuffer?: number; * encoding?: string; * windowsHide?: boolean; * shell?: boolean | string; * }} [options] * @returns {Buffer | string} */ function execFileSync(file, args, options) { ({ 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 = [options.argv0 || file]; ArrayPrototypePush.$apply(errArgs, args); const err = checkExecSyncError(ret, errArgs); if (err) throw err; return ret.stdout; } /** * Spawns a shell executing the given `command` synchronously. * @param {string} command * @param {{ * cwd?: string; * input?: string | Buffer | TypedArray | DataView; * stdio?: string | Array; * env?: Record; * shell?: string; * uid?: number; * gid?: number; * timeout?: number; * killSignal?: string | number; * maxBuffer?: number; * encoding?: string; * windowsHide?: boolean; * }} [options] * @returns {Buffer | string} */ function execSync(command, options) { const opts = normalizeExecArgs(command, options, null); const inheritStderr = !opts.options.stdio; const ret = spawnSync(opts.file, opts.options); if (inheritStderr && ret.stderr) process.stderr.write(ret.stderr); const err = checkExecSyncError(ret, undefined, command); if (err) throw err; return ret.stdout; } function stdioStringToArray(stdio, channel) { let options; switch (stdio) { case "ignore": case "overlapped": case "pipe": options = [stdio, stdio, stdio]; break; case "inherit": options = [0, 1, 2]; break; default: throw $ERR_INVALID_ARG_VALUE("stdio", stdio); } if (channel) $arrayPush(options, channel); return options; } /** * Spawns a new Node.js process + fork. * @param {string|URL} modulePath * @param {string[]} [args] * @param {{ * cwd?: string; * detached?: boolean; * env?: Record; * execPath?: string; * execArgv?: string[]; * gid?: number; * serialization?: string; * signal?: AbortSignal; * killSignal?: string | number; * silent?: boolean; * stdio?: Array | string; * uid?: number; * windowsVerbatimArguments?: boolean; * timeout?: number; * }} [options] * @returns {ChildProcess} */ function fork(modulePath, args = [], options) { modulePath = getValidatedPath(modulePath, "modulePath"); // Get options and args arguments. if (args == null) { args = []; } else if (typeof args === "object" && !$isJSArray(args)) { options = args; args = []; } else { validateArray(args, "args"); } if (options != null) { validateObject(options, "options"); } options = { __proto__: null, ...options, shell: false }; options.execPath = options.execPath || process.execPath; validateArgumentNullCheck(options.execPath, "options.execPath"); // Prepare arguments for fork: let execArgv = options.execArgv || process.execArgv; validateArgumentsNullCheck(execArgv, "options.execArgv"); if (execArgv === process.execArgv && process._eval != null) { const index = ArrayPrototypeLastIndexOf.$call(execArgv, process._eval); if (index > 0) { // Remove the -e switch to avoid fork bombing ourselves. execArgv = ArrayPrototypeSlice.$call(execArgv); ArrayPrototypeSplice.$call(execArgv, index - 1, 2); } } args = [...execArgv, modulePath, ...args]; if (typeof options.stdio === "string") { options.stdio = stdioStringToArray(options.stdio, "ipc"); } else if (!$isJSArray(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 (!ArrayPrototypeIncludes.$call(options.stdio, "ipc")) { throw $ERR_CHILD_PROCESS_IPC_REQUIRED("options.stdio"); } return spawn(options.execPath, args, options); } //------------------------------------------------------------------------------ // Section 2. child_process helpers //------------------------------------------------------------------------------ function convertToValidSignal(signal) { if (typeof signal === "number" && getSignalsToNamesMapping()[signal]) return signal; if (typeof signal === "string") { const signalName = signals[StringPrototypeToUpperCase.$call(signal)]; if (signalName) return signalName; } throw ERR_UNKNOWN_SIGNAL(signal); } function sanitizeKillSignal(killSignal) { if (typeof killSignal === "string" || typeof killSignal === "number") { return convertToValidSignal(killSignal); } else if (killSignal != null) { throw $ERR_INVALID_ARG_TYPE("options.killSignal", ["string", "number"], killSignal); } } let signalsToNamesMapping; function getSignalsToNamesMapping() { if (signalsToNamesMapping !== undefined) return signalsToNamesMapping; signalsToNamesMapping = ObjectCreate(null); for (const key in signals) { signalsToNamesMapping[signals[key]] = key; } return signalsToNamesMapping; } function normalizeExecFileArgs(file, args, options, callback) { if ($isJSArray(args)) { args = ArrayPrototypeSlice.$call(args); } else if (args != null && typeof args === "object") { callback = options; options = args; args = null; } else if (typeof args === "function") { callback = args; options = null; args = null; } if (args == null) { args = []; } if (typeof options === "function") { callback = options; } else if (options != null) { validateObject(options, "options"); } if (options == null) { options = kEmptyObject; } if (callback != null) { validateFunction(callback, "callback"); } // Validate argv0, if present. if (options.argv0 != null) { validateString(options.argv0, "options.argv0"); validateArgumentNullCheck(options.argv0, "options.argv0"); } return { file, args, options, callback }; } function normalizeExecArgs(command, options, callback) { validateString(command, "command"); validateArgumentNullCheck(command, "command"); if (typeof options === "function") { callback = options; options = undefined; } // Make a shallow copy so we don't clobber the user's options object. options = { __proto__: null, ...options }; options.shell = typeof options.shell === "string" ? options.shell : true; return { file: command, options: options, callback: callback, }; } const kBunEnv = Symbol("bunEnv"); function normalizeSpawnArguments(file, args, options) { validateString(file, "file"); validateArgumentNullCheck(file, "file"); if (file.length === 0) throw $ERR_INVALID_ARG_VALUE("file", file, "cannot be empty"); if ($isJSArray(args)) { args = ArrayPrototypeSlice.$call(args); } else if (args == null) { args = []; } else if (typeof args !== "object") { throw $ERR_INVALID_ARG_TYPE("args", "object", args); } else { options = args; args = []; } validateArgumentsNullCheck(args, "args"); if (options === undefined) options = {}; else validateObject(options, "options"); options = { __proto__: null, ...options }; let cwd = options.cwd; // Validate the cwd, if present. if (cwd != null) { cwd = getValidatedPath(cwd, "options.cwd"); } // Validate detached, if present. if (options.detached != null) { validateBoolean(options.detached, "options.detached"); } // Validate the uid, if present. if (options.uid != null && !isInt32(options.uid)) { throw $ERR_INVALID_ARG_TYPE("options.uid", "int32", options.uid); } // Validate the gid, if present. if (options.gid != null && !isInt32(options.gid)) { throw $ERR_INVALID_ARG_TYPE("options.gid", "int32", options.gid); } // Validate the shell, if present. if (options.shell != null && typeof options.shell !== "boolean" && typeof options.shell !== "string") { throw $ERR_INVALID_ARG_TYPE("options.shell", ["boolean", "string"], options.shell); } // Validate argv0, if present. if (options.argv0 != null) { validateString(options.argv0, "options.argv0"); validateArgumentNullCheck(options.argv0, "options.argv0"); } // Validate windowsHide, if present. if (options.windowsHide != null) { validateBoolean(options.windowsHide, "options.windowsHide"); } let { windowsVerbatimArguments } = options; if (windowsVerbatimArguments != null) { validateBoolean(windowsVerbatimArguments, "options.windowsVerbatimArguments"); } // Handle shell if (options.shell) { validateArgumentNullCheck(options.shell, "options.shell"); const command = ArrayPrototypeJoin.$call([file, ...args], " "); // Set the shell, switches, and commands. if (process.platform === "win32") { if (typeof options.shell === "string") file = options.shell; else file = process.env.comspec || "cmd.exe"; // '/d /s /c' is used only for cmd.exe. if (/^(?:.*\\)?cmd(?:\.exe)?$/i.exec(file) !== null) { args = ["/d", "/s", "/c", `"${command}"`]; windowsVerbatimArguments = true; } else { args = ["-c", command]; } } else { if (typeof options.shell === "string") file = options.shell; else if (process.platform === "android") file = "sh"; else file = "/bin/sh"; args = ["-c", command]; } } // Handle argv0 if (typeof options.argv0 === "string") { ArrayPrototypeUnshift.$call(args, options.argv0); } else { ArrayPrototypeUnshift.$call(args, file); } const env = options.env || process.env; const bunEnv = {}; // // process.env.NODE_V8_COVERAGE always propagates, making it possible to // // collect coverage for programs that spawn with white-listed environment. // copyProcessEnvToEnv(env, "NODE_V8_COVERAGE", options.env); let envKeys: string[] = []; for (const key in env) { ArrayPrototypePush.$call(envKeys, key); } if (process.platform === "win32") { // On Windows env keys are case insensitive. Filter out duplicates, keeping only the first one (in lexicographic order) const sawKey = new Set(); envKeys = ArrayPrototypeFilter.$call(ArrayPrototypeSort.$call(envKeys), key => { const uppercaseKey = StringPrototypeToUpperCase.$call(key); if (sawKey.has(uppercaseKey)) { return false; } sawKey.add(uppercaseKey); return true; }); } for (const key of envKeys) { const value = env[key]; if (value !== undefined) { validateArgumentNullCheck(key, `options.env['${key}']`); validateArgumentNullCheck(value, `options.env['${key}']`); bunEnv[key] = value; } } return { // Make a shallow copy so we don't clobber the user's options object. __proto__: null, ...options, args, cwd, detached: !!options.detached, [kBunEnv]: bunEnv, file, windowsHide: !!options.windowsHide, windowsVerbatimArguments: !!windowsVerbatimArguments, argv0: options.argv0, }; } function checkExecSyncError(ret, args, cmd?) { let err; if (ret.error) { err = ret.error; ObjectAssign(err, ret); } else if (ret.status !== 0) { let msg = "Command failed: "; msg += cmd || ArrayPrototypeJoin.$call(args, " "); if (ret.stderr && ret.stderr.length > 0) msg += `\n${ret.stderr.toString()}`; err = genericNodeError(msg, ret); } return err; } function parseEnvPairs(envPairs: string[] | undefined): Record | undefined { if (!envPairs) return undefined; const resEnv = {}; for (const line of envPairs) { const [key, ...value] = line.split("=", 2); resEnv[key] = value.join("="); } return resEnv; } //------------------------------------------------------------------------------ // Section 3. ChildProcess class //------------------------------------------------------------------------------ class ChildProcess extends EventEmitter { #handle; #closesNeeded = 1; #closesGot = 0; signalCode = null; exitCode = null; spawnfile; spawnargs; pid; channel; killed = false; [Symbol.dispose]() { if (!this.killed) { this.kill(); } } #handleOnExit(exitCode, signalCode, err) { if (signalCode) { this.signalCode = signalCode; } else { this.exitCode = exitCode; } // Drain stdio streams { if (this.#stdin) { this.#stdin.destroy(); } else { this.#stdioOptions[0] = "destroyed"; } // If there was an error while spawning the subprocess, then we will never have any IO to drain. if (err) { this.#stdioOptions[1] = this.#stdioOptions[2] = "destroyed"; } const stdout = this.#stdout, stderr = this.#stderr; if (stdout === undefined) { this.#stdout = this.#getBunSpawnIo(1, this.#encoding, true); } else if (stdout && this.#stdioOptions[1] === "pipe" && !stdout?.destroyed) { stdout.resume?.(); } if (stderr === undefined) { this.#stderr = this.#getBunSpawnIo(2, this.#encoding, true); } else if (stderr && this.#stdioOptions[2] === "pipe" && !stderr?.destroyed) { stderr.resume?.(); } } if (err) { if (this.spawnfile) err.path = this.spawnfile; err.spawnargs = ArrayPrototypeSlice.$call(this.spawnargs, 1); err.pid = this.pid; this.emit("error", err); } else if (exitCode < 0) { const err = new SystemError( `Spawned process exited with error code: ${exitCode}`, undefined, "spawn", "EUNKNOWN", "ERR_CHILD_PROCESS_UNKNOWN_ERROR", ); err.pid = this.pid; if (this.spawnfile) err.path = this.spawnfile; err.spawnargs = ArrayPrototypeSlice.$call(this.spawnargs, 1); this.emit("error", err); } this.emit("exit", this.exitCode, this.signalCode); this.#maybeClose(); } #getBunSpawnIo(i, encoding, autoResume = false) { if ($debug && !this.#handle) { if (this.#handle === null) { $debug("ChildProcess: getBunSpawnIo: this.#handle is null. This means the subprocess already exited"); } else { $debug("ChildProcess: getBunSpawnIo: this.#handle is undefined"); } } const handle = this.#handle; const io = this.#stdioOptions[i]; switch (i) { case 0: { switch (io) { case "pipe": { const stdin = handle?.stdin; if (!stdin) { // This can happen if the process was already killed. const Writable = require("internal/streams/writable"); const stream = new Writable({ write(chunk, encoding, callback) { // Gracefully handle writes - stream acts as if it's ended if (callback) callback(); return false; }, }); // Mark as destroyed to indicate it's not usable stream.destroy(); return stream; } const result = require("internal/fs/streams").writableFromFileSink(stdin); result.readable = false; return result; } case "inherit": return null; case "destroyed": { const Writable = require("internal/streams/writable"); const stream = new Writable({ write(chunk, encoding, callback) { // Gracefully handle writes - stream acts as if it's ended if (callback) callback(); return false; }, }); // Mark as destroyed to indicate it's not usable stream.destroy(); return stream; } case "undefined": return undefined; default: return null; } } case 2: case 1: { switch (io) { case "pipe": { const value = handle?.[fdToStdioName(i as 1 | 2)!]; // This can happen if the process was already killed. if (!value) { const Readable = require("internal/streams/readable"); const stream = new Readable({ read() {} }); // Mark as destroyed to indicate it's not usable stream.destroy(); return stream; } const pipe = require("internal/streams/native-readable").constructNativeReadable(value, { encoding }); this.#closesNeeded++; pipe.once("close", () => this.#maybeClose()); if (autoResume) pipe.resume(); return pipe; } case "destroyed": { const Readable = require("internal/streams/readable"); const stream = new Readable({ read() {} }); // Mark as destroyed to indicate it's not usable stream.destroy(); return stream; } case "undefined": return undefined; default: return null; } } default: switch (io) { case "pipe": if (!NetModule) NetModule = require("node:net"); const fd = handle && handle.stdio[i]; if (!fd) return null; return NetModule.connect({ fd }); } return null; } } #stdin; #stdout; #stderr; #stdioObject; #encoding; #stdioOptions; #createStdioObject() { const opts = this.#stdioOptions; const length = opts.length; let result = new Array(length); for (let i = 0; i < length; i++) { const element = opts[i]; if (element === "undefined") { return undefined; } if (element !== "pipe") { result[i] = null; continue; } switch (i) { case 0: result[i] = this.stdin; continue; case 1: result[i] = this.stdout; continue; case 2: result[i] = this.stderr; continue; default: result[i] = this.#getBunSpawnIo(i, this.#encoding, false); continue; } } return result; } get stdin() { return (this.#stdin ??= this.#getBunSpawnIo(0, this.#encoding, false)); } get stdout() { return (this.#stdout ??= this.#getBunSpawnIo(1, this.#encoding, false)); } get stderr() { return (this.#stderr ??= this.#getBunSpawnIo(2, this.#encoding, false)); } get stdio() { return (this.#stdioObject ??= this.#createStdioObject()); } get connected() { const handle = this.#handle; if (handle === null) return false; return handle.connected ?? false; } get [kHandle]() { return this.#handle; } spawn(options) { validateObject(options, "options"); validateOneOf(options.serialization, "options.serialization", [undefined, "json", "advanced"]); const serialization = options.serialization || "json"; const stdio = options.stdio || ["pipe", "pipe", "pipe"]; const bunStdio = getBunStdioFromOptions(stdio); const has_ipc = $isJSArray(stdio) && stdio.includes("ipc"); // validate options.envPairs but only if has_ipc. for some reason. if (has_ipc) { if (options.envPairs !== undefined) { validateArray(options.envPairs, "options.envPairs"); } } var env = options[kBunEnv] || parseEnvPairs(options.envPairs) || process.env; const detachedOption = options.detached; this.#encoding = options.encoding || undefined; this.#stdioOptions = bunStdio; const stdioCount = stdio.length; const hasSocketsToEagerlyLoad = stdioCount >= 3; validateString(options.file, "options.file"); var file; file = this.spawnfile = options.file; var spawnargs; if (options.args === undefined) { spawnargs = this.spawnargs = []; // how is this allowed? } else { validateArray(options.args, "options.args"); spawnargs = this.spawnargs = options.args; } // normalizeSpawnargs has already prepended argv0 to the spawnargs array // Bun.spawn() expects cmd[0] to be the command to run, and argv0 to replace the first arg when running the command, // so we have to set argv0 to spawnargs[0] and cmd[0] to file try { this.#handle = Bun.spawn({ cmd: [file, ...Array.prototype.slice.$call(spawnargs, 1)], stdio: bunStdio, cwd: options.cwd || undefined, env: env, detached: typeof detachedOption !== "undefined" ? !!detachedOption : false, onExit: (handle, exitCode, signalCode, err) => { this.#handle = handle; this.pid = this.#handle.pid; $debug("ChildProcess: onExit", exitCode, signalCode, err, this.pid); if (hasSocketsToEagerlyLoad) { process.nextTick(() => { void this.stdio; $debug("ChildProcess: onExit", exitCode, signalCode, err, this.pid); }); } process.nextTick( (exitCode, signalCode, err) => this.#handleOnExit(exitCode, signalCode, err), exitCode, signalCode, err, ); }, lazy: true, ipc: has_ipc ? this.#emitIpcMessage.bind(this) : undefined, onDisconnect: has_ipc ? ok => this.#onDisconnect(ok) : undefined, serialization, argv0: spawnargs[0], windowsHide: !!options.windowsHide, windowsVerbatimArguments: !!options.windowsVerbatimArguments, }); this.pid = this.#handle.pid; $debug("ChildProcess: spawn", this.pid, spawnargs); process.nextTick(() => { this.emit("spawn"); }); if (has_ipc) { this.send = this.#send; this.disconnect = this.#disconnect; this.channel = new Control(); Object.defineProperty(this, "_channel", { get() { return this.channel; }, set(value) { this.channel = value; }, }); if (options[kFromNode]) this.#closesNeeded += 1; } if (hasSocketsToEagerlyLoad) { for (let item of this.stdio) { item?.ref?.(); } } } catch (ex) { if ( ex != null && typeof ex === "object" && Object.hasOwn(ex, "code") && // node sends these errors on the next tick rather than throwing (ex.code === "EACCES" || ex.code === "EAGAIN" || ex.code === "EMFILE" || ex.code === "ENFILE" || ex.code === "ENOENT") ) { this.#handle = null; ex.syscall = "spawn " + this.spawnfile; ex.spawnargs = Array.prototype.slice.$call(this.spawnargs, 1); process.nextTick(() => { this.emit("error", ex); this.emit("close", (ex as SystemError).errno ?? -1); }); if (ex.code === "EMFILE" || ex.code === "ENFILE") { // emfile/enfile error; in this case node does not initialize stdio streams. this.#stdioOptions[0] = "undefined"; this.#stdioOptions[1] = "undefined"; this.#stdioOptions[2] = "undefined"; } } else { throw ex; } } } #emitIpcMessage(message, _, handle) { this.emit("message", message, handle); } #send(message, handle, options, callback) { if (typeof handle === "function") { callback = handle; handle = undefined; options = undefined; } else if (typeof options === "function") { callback = options; options = undefined; } else if (options !== undefined) { if (typeof options !== "object" || options === null) { throw $ERR_INVALID_ARG_TYPE("options", "object", options); } } if (!this.#handle) { if (callback) { process.nextTick(callback, new TypeError("Process was closed while trying to send message")); } else { this.emit("error", new TypeError("Process was closed while trying to send message")); } return false; } // We still need this send function because return this.#handle.send(message, handle, options, err => { // node does process.nextTick() to emit or call the callback // we don't need to because the send callback is called on nextTick by ipc.zig if (callback) { callback(err); } else if (err) { this.emit("error", err); } }); } #onDisconnect(firstTime: boolean) { if (!firstTime) { // strange return; } $assert(!this.connected); process.nextTick(() => this.emit("disconnect")); process.nextTick(() => this.#maybeClose()); } #disconnect() { if (!this.connected) { this.emit("error", $ERR_IPC_DISCONNECTED()); return; } this.#handle.disconnect(); this.channel = null; } kill(sig?) { const signal = sig === 0 ? sig : convertToValidSignal(sig === undefined ? "SIGTERM" : sig); const handle = this.#handle; if (handle) { if (handle.killed) { this.killed = true; return true; } try { handle.kill(signal); this.killed = true; return true; } catch (e) { this.emit("error", e); } } return false; } #maybeClose() { $debug("Attempting to maybe close..."); this.#closesGot++; if (this.#closesGot === this.#closesNeeded) { this.emit("close", this.exitCode, this.signalCode); } } ref() { if (this.#handle) this.#handle.ref(); } unref() { if (this.#handle) this.#handle.unref(); } // Static initializer to make stdio properties enumerable on the prototype // This fixes libraries like tinyspawn that use Object.assign(promise, childProcess) static { Object.defineProperties(this.prototype, { stdin: { get: function () { const value = (this.#stdin ??= this.#getBunSpawnIo(0, this.#encoding, false)); // Define as own enumerable property on first access Object.defineProperty(this, "stdin", { value: value, enumerable: true, configurable: true, writable: true, }); return value; }, enumerable: true, configurable: true, }, stdout: { get: function () { const value = (this.#stdout ??= this.#getBunSpawnIo(1, this.#encoding, false)); // Define as own enumerable property on first access Object.defineProperty(this, "stdout", { value: value, enumerable: true, configurable: true, writable: true, }); return value; }, enumerable: true, configurable: true, }, stderr: { get: function () { const value = (this.#stderr ??= this.#getBunSpawnIo(2, this.#encoding, false)); // Define as own enumerable property on first access Object.defineProperty(this, "stderr", { value: value, enumerable: true, configurable: true, writable: true, }); return value; }, enumerable: true, configurable: true, }, stdio: { get: function () { const value = (this.#stdioObject ??= this.#createStdioObject()); // Define as own enumerable property on first access Object.defineProperty(this, "stdio", { value: value, enumerable: true, configurable: true, writable: true, }); return value; }, enumerable: true, configurable: true, }, }); } } //------------------------------------------------------------------------------ // Section 4. ChildProcess helpers //------------------------------------------------------------------------------ const nodeToBunLookup = { ignore: null, pipe: "pipe", overlapped: "pipe", // TODO: this may need to work differently for Windows inherit: "inherit", ipc: "ipc", }; function nodeToBun(item: string, index: number): string | number | null | NodeJS.TypedArray | ArrayBufferView { // If not defined, use the default. // For stdin/stdout/stderr, it's pipe. For others, it's ignore. if (item == null) { return index > 2 ? "ignore" : "pipe"; } // If inherit and we are referencing stdin/stdout/stderr index, // we can get the fd from the ReadStream for the corresponding stdio if (typeof item === "number") { return item; } if (isNodeStreamReadable(item)) { if (Object.hasOwn(item, "fd") && typeof item.fd === "number") return item.fd; if (item._handle && typeof item._handle.fd === "number") return item._handle.fd; throw new Error(`TODO: stream.Readable stdio @ ${index}`); } if (isNodeStreamWritable(item)) { if (Object.hasOwn(item, "fd") && typeof item.fd === "number") return item.fd; if (item._handle && typeof item._handle.fd === "number") return item._handle.fd; throw new Error(`TODO: stream.Writable stdio @ ${index}`); } const result = nodeToBunLookup[item]; if (result === undefined) { throw new Error(`Invalid stdio option[${index}] "${item}"`); } return result; } /** * Safer version of `item instance of node:stream.Readable`. * * @param item {object} * @returns {boolean} */ function isNodeStreamReadable(item) { if (typeof item !== "object") return false; if (!item) return false; if (typeof item.on !== "function") return false; if (typeof item.pipe !== "function") return false; return true; } /** * Safer version of `item instance of node:stream.Writable`. * * @param item {objects} * @returns {boolean} */ function isNodeStreamWritable(item) { if (typeof item !== "object") return false; if (!item) return false; if (typeof item.on !== "function") return false; if (typeof item.write !== "function") return false; return true; } function fdToStdioName(fd: number) { switch (fd) { case 0: return "stdin"; case 1: return "stdout"; case 2: return "stderr"; default: return null; } } function getBunStdioFromOptions(stdio) { const normalizedStdio = normalizeStdio(stdio); if (normalizedStdio.filter(v => v === "ipc").length > 1) throw $ERR_IPC_ONE_PIPE(); // Node options: // pipe: just a pipe // ipc = can only be one in array // overlapped -- same as pipe on Unix based systems // inherit -- 'inherit': equivalent to ['inherit', 'inherit', 'inherit'] or [0, 1, 2] // ignore -- > /dev/null, more or less same as null option for Bun.spawn stdio // TODO: Stream -- use this stream // number -- used as FD // null, undefined: Use default value. Not same as ignore, which is Bun.spawn null. // null/undefined: For stdio fds 0, 1, and 2 (in other words, stdin, stdout, and stderr) a pipe is created. For fd 3 and up, the default is 'ignore' // Important Bun options // pipe // fd // null - no stdin/stdout/stderr // Translations: node -> bun // pipe -> pipe // overlapped -> pipe // ignore -> null // inherit -> inherit (stdin/stdout/stderr) // Stream -> throw err for now const bunStdio = normalizedStdio.map(nodeToBun); return bunStdio; } function normalizeStdio(stdio): string[] { if (typeof stdio === "string") { switch (stdio) { case "ignore": return ["ignore", "ignore", "ignore"]; case "pipe": return ["pipe", "pipe", "pipe"]; case "inherit": return ["inherit", "inherit", "inherit"]; default: throw ERR_INVALID_OPT_VALUE("stdio", stdio); } } else if ($isJSArray(stdio)) { // Validate if each is a valid stdio type // TODO: Support wrapped types here let processedStdio; if (stdio.length === 0) processedStdio = ["pipe", "pipe", "pipe"]; else if (stdio.length === 1) processedStdio = [stdio[0], "pipe", "pipe"]; else if (stdio.length === 2) processedStdio = [stdio[0], stdio[1], "pipe"]; else if (stdio.length >= 3) processedStdio = stdio; return processedStdio; } else { throw ERR_INVALID_OPT_VALUE("stdio", stdio); } } function abortChildProcess(child, killSignal, reason) { if (!child) return; try { if (child.kill(killSignal)) { child.emit("error", $makeAbortError(undefined, { cause: reason })); } } catch (err) { child.emit("error", err); } } class Control extends EventEmitter { constructor() { super(); } } //------------------------------------------------------------------------------ // Section 5. Validators //------------------------------------------------------------------------------ function validateMaxBuffer(maxBuffer) { if (maxBuffer != null && !(typeof maxBuffer === "number" && maxBuffer >= 0)) { throw $ERR_OUT_OF_RANGE("options.maxBuffer", "a positive number", maxBuffer); } } function validateArgumentNullCheck(arg, propName) { if (typeof arg === "string" && StringPrototypeIncludes.$call(arg, "\u0000")) { throw $ERR_INVALID_ARG_VALUE(propName, arg, "must be a string without null bytes"); } } function validateArgumentsNullCheck(args, propName) { for (let i = 0; i < args.length; ++i) { validateArgumentNullCheck(args[i], `${propName}[${i}]`); } } function validateTimeout(timeout) { if (timeout != null && !(NumberIsInteger(timeout) && timeout >= 0)) { throw $ERR_OUT_OF_RANGE("timeout", "an unsigned integer", timeout); } } function isInt32(value) { return value === (value | 0); } function nullCheck(path, propName, throwError = true) { const pathIsString = typeof path === "string"; const pathIsUint8Array = isUint8Array(path); // We can only perform meaningful checks on strings and Uint8Arrays. if ( (!pathIsString && !pathIsUint8Array) || (pathIsString && !StringPrototypeIncludes.$call(path, "\u0000")) || (pathIsUint8Array && !Uint8ArrayPrototypeIncludes.$call(path, 0)) ) { return; } const err = $ERR_INVALID_ARG_VALUE(propName, path, "must be a string or Uint8Array without null bytes"); if (throwError) { throw err; } return err; } function validatePath(path, propName = "path") { if (typeof path !== "string" && !isUint8Array(path)) { throw $ERR_INVALID_ARG_TYPE(propName, ["string", "Buffer", "URL"], path); } const err = nullCheck(path, propName, false); if (err !== undefined) { throw err; } } function getValidatedPath(fileURLOrPath, propName = "path") { const path = toPathIfFileURL(fileURLOrPath); validatePath(path, propName); return path; } function isUint8Array(value) { return typeof value === "object" && value !== null && value instanceof Uint8Array; } //------------------------------------------------------------------------------ // Section 6. Random utilities //------------------------------------------------------------------------------ function isURLInstance(fileURLOrPath) { return fileURLOrPath != null && fileURLOrPath.href && fileURLOrPath.origin; } function toPathIfFileURL(fileURLOrPath) { if (!isURLInstance(fileURLOrPath)) return fileURLOrPath; return Bun.fileURLToPath(fileURLOrPath); } //------------------------------------------------------------------------------ // Section 7. Node errors / error polyfills //------------------------------------------------------------------------------ var Error = globalThis.Error; var TypeError = globalThis.TypeError; function genericNodeError(message, errorProperties) { // eslint-disable-next-line no-restricted-syntax const err = new Error(message); ObjectAssign(err, errorProperties); return err; } // const messages = new Map(); // Utility function for registering the error codes. Only used here. Exported // *only* to allow for testing. // function E(sym, val, def) { // messages.set(sym, val); // def = makeNodeErrorWithCode(def, sym); // errorCodes[sym] = def; // } // function makeNodeErrorWithCode(Base, key) { // return function NodeError(...args) { // // const limit = Error.stackTraceLimit; // // if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0; // const error = new Base(); // // Reset the limit and setting the name property. // // if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = limit; // const message = getMessage(key, args); // error.message = message; // // captureLargerStackTrace(error); // error.code = key; // return error; // }; // } // function getMessage(key, args) { // const msgFn = messages.get(key); // if (args.length !== msgFn.length) // throw new Error( // `Invalid number of args for error message ${key}. Got ${args.length}, expected ${msgFn.length}.` // ); // return msgFn(...args); // } // E( // "ERR_INVALID_ARG_TYPE", // (name, expected, actual) => { // assert(typeof name === "string", "'name' must be a string"); // if (!$isJSArray(expected)) { // expected = [expected]; // } // let msg = "The "; // if (StringPrototypeEndsWith(name, " argument")) { // // For cases like 'first argument' // msg += `${name} `; // } else { // const type = StringPrototypeIncludes(name, ".") ? "property" : "argument"; // msg += `"${name}" ${type} `; // } // msg += "must be "; // const types = []; // const instances = []; // const other = []; // for (const value of expected) { // assert( // typeof value === "string", // "All expected entries have to be of type string" // ); // if (ArrayPrototypeIncludes.$call(kTypes, value)) { // ArrayPrototypePush(types, StringPrototypeToLowerCase(value)); // } else if (RegExpPrototypeExec(classRegExp, value) !== null) { // ArrayPrototypePush(instances, value); // } else { // assert( // value !== "object", // 'The value "object" should be written as "Object"' // ); // ArrayPrototypePush(other, value); // } // } // // Special handle `object` in case other instances are allowed to outline // // the differences between each other. // if (instances.length > 0) { // const pos = ArrayPrototypeIndexOf(types, "object"); // if (pos !== -1) { // ArrayPrototypeSplice.$call(types, pos, 1); // $arrayPush(instances, "Object"); // } // } // if (types.length > 0) { // if (types.length > 2) { // const last = ArrayPrototypePop(types); // msg += `one of type ${ArrayPrototypeJoin(types, ", ")}, or ${last}`; // } else if (types.length === 2) { // msg += `one of type ${types[0]} or ${types[1]}`; // } else { // msg += `of type ${types[0]}`; // } // if (instances.length > 0 || other.length > 0) msg += " or "; // } // if (instances.length > 0) { // if (instances.length > 2) { // const last = ArrayPrototypePop(instances); // msg += `an instance of ${ArrayPrototypeJoin( // instances, // ", " // )}, or ${last}`; // } else { // msg += `an instance of ${instances[0]}`; // if (instances.length === 2) { // msg += ` or ${instances[1]}`; // } // } // if (other.length > 0) msg += " or "; // } // if (other.length > 0) { // if (other.length > 2) { // const last = ArrayPrototypePop(other); // msg += `one of ${ArrayPrototypeJoin.$call(other, ", ")}, or ${last}`; // } else if (other.length === 2) { // msg += `one of ${other[0]} or ${other[1]}`; // } else { // if (StringPrototypeToLowerCase(other[0]) !== other[0]) msg += "an "; // msg += `${other[0]}`; // } // } // msg += `. Received ${determineSpecificType(actual)}`; // return msg; // }, // TypeError // ); function ERR_UNKNOWN_SIGNAL(name) { const err = new TypeError(`Unknown signal: ${name}`); err.code = "ERR_UNKNOWN_SIGNAL"; return err; } function ERR_INVALID_OPT_VALUE(name, value) { const err = new TypeError(`The value "${value}" is invalid for option "${name}"`); err.code = "ERR_INVALID_OPT_VALUE"; return err; } class SystemError extends Error { path; syscall; errno; code; constructor(message, path, syscall, errno, code) { super(message); this.path = path; this.syscall = syscall; this.errno = errno; this.code = code; } get name() { return "SystemError"; } } export default { ChildProcess, spawn, execFile, exec, fork, spawnSync, execFileSync, execSync, };