diff options
Diffstat (limited to 'src/js/node/child_process.js')
-rw-r--r-- | src/js/node/child_process.js | 1748 |
1 files changed, 1748 insertions, 0 deletions
diff --git a/src/js/node/child_process.js b/src/js/node/child_process.js new file mode 100644 index 000000000..691c9e096 --- /dev/null +++ b/src/js/node/child_process.js @@ -0,0 +1,1748 @@ +// Hardcoded module "node:child_process" +const EventEmitter = import.meta.require("node:events"); +const { + Readable: { fromWeb: ReadableFromWeb }, + NativeWritable, +} = import.meta.require("node:stream"); +const { + constants: { signals }, +} = import.meta.require("node:os"); +const { promisify } = import.meta.require("node:util"); + +const { ArrayBuffer, Uint8Array, String, Object, Buffer, Promise } = import.meta.primordials; + +var ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty; +var ObjectCreate = Object.create; +var ObjectAssign = Object.assign; +var ObjectDefineProperty = Object.defineProperty; +var BufferConcat = Buffer.concat; +var BufferIsEncoding = Buffer.isEncoding; + +var kEmptyObject = ObjectCreate(null); + +var ArrayPrototypePush = Array.prototype.push; +var ArrayPrototypeReduce = Array.prototype.reduce; +var ArrayPrototypeFilter = Array.prototype.filter; +var ArrayPrototypeJoin = Array.prototype.join; +var ArrayPrototypeMap = Array.prototype.map; +var ArrayPrototypeIncludes = Array.prototype.includes; +var ArrayPrototypeSlice = Array.prototype.slice; +var ArrayPrototypeUnshift = Array.prototype.unshift; +var ArrayIsArray = Array.isArray; + +// var ArrayBuffer = ArrayBuffer; +var ArrayBufferIsView = ArrayBuffer.isView; + +var NumberIsInteger = Number.isInteger; +var MathAbs = Math.abs; + +var StringPrototypeToUpperCase = String.prototype.toUpperCase; +var StringPrototypeIncludes = String.prototype.includes; +var StringPrototypeSlice = String.prototype.slice; +var Uint8ArrayPrototypeIncludes = Uint8Array.prototype.includes; + +const MAX_BUFFER = 1024 * 1024; + +// General debug vs tracking stdio streams. Useful for stream debugging in particular +const __DEBUG__ = process.env.DEBUG || false; + +// You can use this env var along with `process.env.DEBUG_TRACK_EE` to debug stdio streams +// Just set `DEBUG_TRACK_EE=PARENT_STDOUT-0, PARENT_STDOUT-1`, etc. and `DEBUG_STDIO=1` and you will be able to track particular stdio streams +// TODO: Add ability to track a range of IDs rather than just enumerated ones +const __TRACK_STDIO__ = process.env.DEBUG_STDIO; +const debug = __DEBUG__ ? console.log : () => {}; + +if (__TRACK_STDIO__) { + 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 <number> Sets the user identity of the process (see setuid(2)). +// gid <number> Sets the group identity of the process (see setgid(2)). +// detached <boolean> Prepare child to run independently of its parent process. Specific behavior depends on the platform, see options.detached). + +// TODO: After IPC channels can be opened +// serialization <string> Specify the kind of serialization used for sending messages between processes. Possible values are 'json' and 'advanced'. See Advanced serialization for more details. Default: 'json'. + +// TODO: Add support for ipc option, verify only one IPC channel in array +// stdio <Array> | <string> 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 +//------------------------------------------------------------------------------ + +// TODO: Implement these props when Windows is supported +// * windowsVerbatimArguments?: boolean; +// * windowsHide?: boolean; + +// 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. + +function spawnTimeoutFunction(child, timeoutHolder) { + var timeoutId = timeoutHolder.timeoutId; + if (timeoutId > -1) { + try { + child.kill(killSignal); + } catch (err) { + child.emit("error", err); + } + timeoutHolder.timeoutId = -1; + } +} +/** + * Spawns a new process using the given `file`. + * @param {string} file + * @param {string[]} [args] + * @param {{ + * cwd?: string; + * env?: Record<string, string>; + * argv0?: string; + * stdio?: Array | string; + * detached?: boolean; + * uid?: number; + * gid?: number; + * serialization?: string; + * shell?: boolean | string; + * signal?: AbortSignal; + * timeout?: number; + * killSignal?: string | number; + * }} [options] + * @returns {ChildProcess} + */ +export 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); + child.spawn(options); + + if (options.timeout > 0) { + let timeoutId = setTimeout(() => { + if (timeoutId) { + try { + child.kill(killSignal); + } catch (err) { + child.emit("error", err); + } + timeoutId = null; + } + }); + + child.once("exit", () => { + if (timeoutId) { + clearTimeout(timeoutId); + timeoutId = null; + } + }); + } + + if (options.signal) { + const signal = options.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); + } + } + return child; +} + +/** + * Spawns the specified file as a shell. + * @param {string} file + * @param {string[]} [args] + * @param {{ + * cwd?: string; + * env?: Record<string, string>; + * 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} + */ +export function execFile(file, args, options, callback) { + ({ file, args, options, callback } = normalizeExecFileArgs(file, args, options, callback)); + + options = { + 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, + // gid: options.gid, + shell: options.shell, + signal: options.signal, + // uid: options.uid, + }); + + let encoding; + const _stdout = []; + const _stderr = []; + if (options.encoding !== "buffer" && BufferIsEncoding(options.encoding)) { + encoding = options.encoding; + } else { + encoding = null; + } + let stdoutLen = 0; + let stderrLen = 0; + let killed = false; + let exited = false; + let timeoutId; + let encodedStdoutLen; + let encodedStderrLen; + + let ex = null; + + let cmd = file; + + function exitHandler(code, signal) { + if (exited) return; + exited = true; + + if (timeoutId) { + clearTimeout(timeoutId); + timeoutId = null; + } + + if (!callback) return; + + const readableEncoding = child?.stdout?.readableEncoding; + // 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) { + let message = `Command failed: ${cmd}`; + if (stderr) message += `\n${stderr}`; + ex = genericNodeError(message, { + // code: code < 0 ? getSystemErrorName(code) : code, // TODO: Add getSystemErrorName + code: code, + killed: child.killed || killed, + signal: signal, + }); + } + + ex.cmd = cmd; + callback(ex, stdout, stderr); + } + + function errorHandler(e) { + 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(options.killSignal); + } catch (e) { + ex = e; + exitHandler(); + } + } + + if (options.timeout > 0) { + timeoutId = setTimeout(function delayedKill() { + kill(); + timeoutId = null; + }, options.timeout); + } + + if (child.stdout) { + if (encoding) child.stdout.setEncoding(encoding); + + child.stdout.on( + "data", + maxBuffer === Infinity + ? function onUnlimitedSizeBufferedData(chunk) { + ArrayPrototypePush.call(_stdout, chunk); + } + : encoding + ? function onChildStdoutEncoded(chunk) { + stdoutLen += chunk.length; + + if (stdoutLen * 4 > maxBuffer) { + const encoding = child.stdout.readableEncoding; + const actualLen = Buffer.byteLength(chunk, encoding); + if (encodedStdoutLen === undefined) { + for (let i = 0; i < _stdout.length; i++) { + encodedStdoutLen += Buffer.byteLength(_stdout[i], encoding); + } + } else { + encodedStdoutLen += actualLen; + } + const truncatedLen = maxBuffer - (encodedStdoutLen - actualLen); + ArrayPrototypePush.call(_stdout, StringPrototypeSlice.apply(chunk, 0, truncatedLen)); + + ex = new ERR_CHILD_PROCESS_STDIO_MAXBUFFER("stdout"); + kill(); + } else { + ArrayPrototypePush.call(_stdout, chunk); + } + } + : function onChildStdoutRaw(chunk) { + stdoutLen += chunk.length; + + if (stdoutLen > maxBuffer) { + const truncatedLen = maxBuffer - (stdoutLen - chunk.length); + ArrayPrototypePush.call(_stdout, chunk.slice(0, truncatedLen)); + + ex = new ERR_CHILD_PROCESS_STDIO_MAXBUFFER("stdout"); + kill(); + } else { + ArrayPrototypePush.call(_stdout, chunk); + } + }, + ); + } + + if (child.stderr) { + if (encoding) child.stderr.setEncoding(encoding); + + child.stderr.on( + "data", + maxBuffer === Infinity + ? function onUnlimitedSizeBufferedData(chunk) { + ArrayPrototypePush.call(_stderr, chunk); + } + : encoding + ? function onChildStderrEncoded(chunk) { + stderrLen += chunk.length; + + if (stderrLen * 4 > maxBuffer) { + const encoding = child.stderr.readableEncoding; + const actualLen = Buffer.byteLength(chunk, encoding); + if (encodedStderrLen === undefined) { + for (let i = 0; i < _stderr.length; i++) { + encodedStderrLen += Buffer.byteLength(_stderr[i], encoding); + } + } else { + encodedStderrLen += actualLen; + } + const truncatedLen = maxBuffer - (encodedStderrLen - actualLen); + ArrayPrototypePush.call(_stderr, StringPrototypeSlice.call(chunk, 0, truncatedLen)); + + ex = new ERR_CHILD_PROCESS_STDIO_MAXBUFFER("stderr"); + kill(); + } else { + ArrayPrototypePush.call(_stderr, chunk); + } + } + : function onChildStderrRaw(chunk) { + stderrLen += chunk.length; + + if (stderrLen > maxBuffer) { + const truncatedLen = maxBuffer - (stderrLen - chunk.length); + ArrayPrototypePush.call(_stderr, StringPrototypeSlice.call(chunk, 0, truncatedLen)); + + ex = new ERR_CHILD_PROCESS_STDIO_MAXBUFFER("stderr"); + kill(); + } else { + ArrayPrototypePush.call(_stderr, chunk); + } + }, + ); + } + + 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<string, string>; + * encoding?: string; + * shell?: string; + * signal?: AbortSignal; + * timeout?: number; + * maxBuffer?: number; + * killSignal?: string | number; + * uid?: number; + * gid?: number; + * windowsHide?: boolean; + * }} [options] + * @param {( + * error?: Error, + * stdout?: string | Buffer, + * stderr?: string | Buffer + * ) => any} [callback] + * @returns {ChildProcess} + */ +export function exec(command, options, callback) { + const opts = normalizeExecArgs(command, options, callback); + return execFile(opts.file, opts.options, opts.callback); +} + +const customPromiseExecFunction = orig => { + return (...args) => { + let resolve; + let reject; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + + promise.child = orig(...args, (err, stdout, stderr) => { + if (err !== null) { + err.stdout = stdout; + err.stderr = stderr; + reject(err); + } else { + resolve({ stdout, stderr }); + } + }); + + return promise; + }; +}; + +ObjectDefineProperty(exec, promisify.custom, { + __proto__: null, + enumerable: false, + value: customPromiseExecFunction(exec), +}); + +/** + * 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<string, string>; + * uid?: number; + * gid?: number; + * timeout?: number; + * killSignal?: string | number; + * maxBuffer?: number; + * encoding?: string; + * shell?: boolean | string; + * }} [options] + * @returns {{ + * pid: number; + * output: Array; + * stdout: Buffer | string; + * stderr: Buffer | string; + * status: number | null; + * signal: string | null; + * error: Error; + * }} + */ +export function spawnSync(file, args, options) { + options = { + 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 new ERR_INVALID_ARG_TYPE(`options.stdio[0]`, ["Buffer", "TypedArray", "DataView", "string"], input); + } + } + + const { stdout, stderr, success, exitCode } = Bun.spawnSync({ + cmd: options.args, + env: options.env || undefined, + cwd: options.cwd || undefined, + stdin: bunStdio[0], + stdout: bunStdio[1], + stderr: bunStdio[2], + }); + + const result = { + signal: null, + status: exitCode, + output: [null, stdout, stderr], + }; + + if (stdout && encoding && encoding !== "buffer") { + result.output[1] = result.output[1]?.toString(encoding); + } + + if (stderr && encoding && encoding !== "buffer") { + result.output[2] = result.output[2]?.toString(encoding); + } + + result.stdout = result.output[1]; + result.stderr = result.output[2]; + + if (!success) { + result.error = new SystemError(result.output[2], options.file, "spawnSync", -1, result.status); + result.error.spawnargs = ArrayPrototypeSlice.call(options.args, 1); + } + + return result; +} + +/** + * 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<string, string>; + * uid?: number; + * gid?: number; + * timeout?: number; + * killSignal?: string | number; + * maxBuffer?: number; + * encoding?: string; + * windowsHide?: boolean; + * shell?: boolean | string; + * }} [options] + * @returns {Buffer | string} + */ +export 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<string, string>; + * shell?: string; + * uid?: number; + * gid?: number; + * timeout?: number; + * killSignal?: string | number; + * maxBuffer?: number; + * encoding?: string; + * windowsHide?: boolean; + * }} [options] + * @returns {Buffer | string} + */ +export 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); // TODO: Uncomment when we have process.stderr + + const err = checkExecSyncError(ret, undefined, command); + + if (err) throw err; + + return ret.stdout; +} + +export function fork() { + throw new Error("Not implemented"); +} + +//------------------------------------------------------------------------------ +// 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 new ERR_UNKNOWN_SIGNAL(signal); +} + +function sanitizeKillSignal(killSignal) { + 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); + } +} + +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 (ArrayIsArray(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 = { ...options }; + options.shell = typeof options.shell === "string" ? options.shell : true; + + return { + file: command, + options: options, + callback: callback, + }; +} + +function normalizeSpawnArguments(file, args, options) { + validateString(file, "file"); + validateArgumentNullCheck(file, "file"); + + if (file.length === 0) throw new ERR_INVALID_ARG_VALUE("file", file, "cannot be empty"); + + if (ArrayIsArray(args)) { + args = ArrayPrototypeSlice.call(args); + } else if (args == null) { + args = []; + } else if (typeof args !== "object") { + throw new ERR_INVALID_ARG_TYPE("args", "object", args); + } else { + options = args; + args = []; + } + + validateArgumentsNullCheck(args, "args"); + + if (options === undefined) options = {}; + else validateObject(options, "options"); + + let cwd = options.cwd; + + // Validate the cwd, if present. + if (cwd != null) { + cwd = getValidatedPath(cwd, "options.cwd"); + } + + // TODO: Detached check + // TODO: Gid check + // TODO: Uid check + + // Validate the shell, if present. + if (options.shell != null && typeof options.shell !== "boolean" && typeof options.shell !== "string") { + throw new 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"); + } + + // TODO: Windows checks for Windows specific options + + // Handle shell + if (options.shell) { + validateArgumentNullCheck(options.shell, "options.shell"); + const command = ArrayPrototypeJoin.call([file, ...args], " "); + // TODO: Windows moment + // 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 (RegExpPrototypeExec(/^(?:.*\\)?cmd(?:\.exe)?$/i, 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 = "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 envPairs = env; + + // // 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); + + // TODO: Windows env support here... + + return { ...options, file, args, cwd, envPairs }; +} + +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; +} + +//------------------------------------------------------------------------------ +// Section 3. ChildProcess class +//------------------------------------------------------------------------------ +export class ChildProcess extends EventEmitter { + #handle; + #exited = false; + #closesNeeded = 1; + #closesGot = 0; + + connected = false; + signalCode = null; + exitCode = null; + spawnfile; + spawnargs; + pid; + channel; + + get killed() { + if (this.#handle == null) return false; + } + + // constructor(options) { + // super(options); + // this.#handle[owner_symbol] = this; + // } + + #handleOnExit(exitCode, signalCode, err) { + if (this.#exited) return; + this.exitCode = this.#handle.exitCode; + this.signalCode = exitCode > 0 ? signalCode : null; + + if (this.#stdin) { + this.#stdin.destroy(); + } + + if (this.#handle) { + this.#handle = null; + } + + if (exitCode < 0) { + const err = new SystemError( + `Spawned process exited with error code: ${exitCode}`, + undefined, + "spawn", + "EUNKNOWN", + "ERR_CHILD_PROCESS_UNKNOWN_ERROR", + ); + + if (this.spawnfile) err.path = this.spawnfile; + + err.spawnargs = ArrayPrototypeSlice.call(this.spawnargs, 1); + this.emit("error", err); + } else { + this.emit("exit", this.exitCode, this.signalCode); + } + + // If any of the stdio streams have not been touched, + // then pull all the data through so that it can get the + // eof and emit a 'close' event. + // Do it on nextTick so that the user has one last chance + // to consume the output, if for example they only want to + // start reading the data once the process exits. + process.nextTick(flushStdio, this); + + this.#maybeClose(); + this.#exited = true; + this.#stdioOptions = ["destroyed", "destroyed", "destroyed"]; + } + + #getBunSpawnIo(i, encoding) { + 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 io = this.#stdioOptions[i]; + switch (i) { + case 0: { + switch (io) { + case "pipe": + return new NativeWritable(this.#handle.stdin); + case "inherit": + return process.stdin || null; + case "destroyed": + return new ShimmedStdin(); + default: + return null; + } + } + case 2: + case 1: { + switch (io) { + case "pipe": + return ReadableFromWeb( + this.#handle[fdToStdioName(i)], + __TRACK_STDIO__ + ? { + encoding, + __id: `PARENT_${fdToStdioName(i).toUpperCase()}-${globalThis.__getId()}`, + } + : { encoding }, + ); + case "inherit": + return process[fdToStdioName(i)] || null; + case "destroyed": + return new ShimmedStdioOutStream(); + default: + return null; + } + } + } + } + + #stdin; + #stdout; + #stderr; + #stdioObject; + #encoding; + #stdioOptions; + + #createStdioObject() { + return Object.create(null, { + 0: { + get: () => this.stdin, + }, + 1: { + get: () => this.stdout, + }, + 2: { + get: () => this.stderr, + }, + }); + } + + get stdin() { + return (this.#stdin ??= this.#getBunSpawnIo(0, this.#encoding)); + } + + get stdout() { + return (this.#stdout ??= this.#getBunSpawnIo(1, this.#encoding)); + } + + get stderr() { + return (this.#stderr ??= this.#getBunSpawnIo(2, this.#encoding)); + } + + get stdio() { + return (this.#stdioObject ??= this.#createStdioObject()); + } + + spawn(options) { + validateObject(options, "options"); + + // validateOneOf(options.serialization, "options.serialization", [ + // undefined, + // "json", + // // "advanced", // TODO + // ]); + // const serialization = options.serialization || "json"; + + // if (ipc !== undefined) { + // // Let child process know about opened IPC channel + // if (options.envPairs === undefined) options.envPairs = []; + // else validateArray(options.envPairs, "options.envPairs"); + + // ArrayPrototypePush.call(options.envPairs, `NODE_CHANNEL_FD=${ipcFd}`); + // ArrayPrototypePush.call( + // options.envPairs, + // `NODE_CHANNEL_SERIALIZATION_MODE=${serialization}` + // ); + // } + + validateString(options.file, "options.file"); + // NOTE: This is confusing... So node allows you to pass a file name + // But also allows you to pass a command in the args and it should execute + // To add another layer of confusion, they also give the option to pass an explicit "argv0" + // which overrides the actual command of the spawned process... + var file; + file = this.spawnfile = options.file; + + var spawnargs; + if (options.args == null) { + spawnargs = this.spawnargs = []; + } else { + validateArray(options.args, "options.args"); + spawnargs = this.spawnargs = options.args; + } + + const stdio = options.stdio || ["pipe", "pipe", "pipe"]; + const bunStdio = getBunStdioFromOptions(stdio); + + var env = options.envPairs || undefined; + + this.#encoding = options.encoding || undefined; + this.#stdioOptions = bunStdio; + this.#handle = Bun.spawn({ + cmd: spawnargs, + stdin: bunStdio[0], + stdout: bunStdio[1], + stderr: bunStdio[2], + cwd: options.cwd || undefined, + env: env || process.env, + onExit: (handle, exitCode, signalCode, err) => { + this.#handle = handle; + this.pid = this.#handle.pid; + + process.nextTick( + (exitCode, signalCode, err) => this.#handleOnExit(exitCode, signalCode, err), + exitCode, + signalCode, + err, + ); + }, + lazy: true, + }); + this.pid = this.#handle.pid; + + onSpawnNT(this); + + // const ipc = stdio.ipc; + // const ipcFd = stdio.ipcFd; + // stdio = options.stdio = stdio.stdio; + + // for (i = 0; i < stdio.length; i++) { + // const stream = stdio[i]; + // if (stream.type === "ignore") continue; + + // if (stream.ipc) { + // this._closesNeeded++; + // continue; + // } + + // // The stream is already cloned and piped, thus stop its readable side, + // // otherwise we might attempt to read from the stream when at the same time + // // the child process does. + // if (stream.type === "wrap") { + // stream.handle.reading = false; + // stream.handle.readStop(); + // stream._stdio.pause(); + // stream._stdio.readableFlowing = false; + // stream._stdio._readableState.reading = false; + // stream._stdio[kIsUsedAsStdio] = true; + // continue; + // } + + // if (stream.handle) { + // stream.socket = createSocket( + // this.pid !== 0 ? stream.handle : null, + // i > 0 + // ); + + // // Add .send() method and start listening for IPC data + // if (ipc !== undefined) setupChannel(this, ipc, serialization); + } + + send() { + console.log("ChildProcess.prototype.send() - Sorry! Not implemented yet"); + } + + disconnect() { + console.log("ChildProcess.prototype.disconnect() - Sorry! Not implemented yet"); + } + + kill(sig) { + const signal = sig === 0 ? sig : convertToValidSignal(sig === undefined ? "SIGTERM" : sig); + + if (this.#handle) { + this.#handle.kill(signal); + } + + this.#maybeClose(); + + // TODO: Figure out how to make this conform to the Node spec... + // The problem is that the handle does not report killed until the process exits + // So we can't return whether or not the process was killed because Bun.spawn seems to handle this async instead of sync like Node does + // return this.#handle?.killed ?? true; + return true; + } + + #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(); + } +} + +//------------------------------------------------------------------------------ +// Section 4. ChildProcess helpers +//------------------------------------------------------------------------------ +const nodeToBunLookup = { + ignore: null, + pipe: "pipe", + overlapped: "pipe", // TODO: this may need to work differently for Windows + inherit: "inherit", +}; + +function nodeToBun(item) { + // 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; + } else { + const result = nodeToBunLookup[item]; + if (result === undefined) throw new Error("Invalid stdio option"); + return result; + } +} + +function fdToStdioName(fd) { + switch (fd) { + case 0: + return "stdin"; + case 1: + return "stdout"; + case 2: + return "stderr"; + default: + return null; + } +} + +function getBunStdioFromOptions(stdio) { + const normalizedStdio = normalizeStdio(stdio); + // 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(item => nodeToBun(item)); + return bunStdio; +} + +function normalizeStdio(stdio) { + 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 new ERR_INVALID_OPT_VALUE("stdio", stdio); + } + } else if (ArrayIsArray(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[0], stdio[1], stdio[2]]; + + return processedStdio.map(item => (!item ? "pipe" : item)); + } else { + throw new ERR_INVALID_OPT_VALUE("stdio", stdio); + } +} + +function flushStdio(subprocess) { + const stdio = subprocess.stdio; + if (stdio == null) return; + + for (let i = 0; i < stdio.length; i++) { + const stream = stdio[i]; + // TODO(addaleax): This doesn't necessarily account for all the ways in + // which data can be read from a stream, e.g. being consumed on the + // native layer directly as a StreamBase. + if (!stream || !stream.readable) { + continue; + } + stream.resume(); + } +} + +function onSpawnNT(self) { + self.emit("spawn"); +} + +function abortChildProcess(child, killSignal) { + if (!child) return; + try { + if (child.kill(killSignal)) { + child.emit("error", new AbortError()); + } + } catch (err) { + child.emit("error", err); + } +} + +class ShimmedStdin extends EventEmitter { + constructor() { + super(); + } + write() { + return false; + } + destroy() {} + end() {} + pipe() {} +} + +class ShimmedStdioOutStream extends EventEmitter { + pipe() {} +} + +//------------------------------------------------------------------------------ +// Section 5. Validators +//------------------------------------------------------------------------------ + +function validateMaxBuffer(maxBuffer) { + if (maxBuffer != null && !(typeof maxBuffer === "number" && maxBuffer >= 0)) { + throw new ERR_OUT_OF_RANGE("options.maxBuffer", "a positive number", maxBuffer); + } +} + +function validateArgumentNullCheck(arg, propName) { + if (typeof arg === "string" && StringPrototypeIncludes.call(arg, "\u0000")) { + throw new 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 new ERR_OUT_OF_RANGE("timeout", "an unsigned integer", timeout); + } +} + +function validateBoolean(value, name) { + if (typeof value !== "boolean") throw new ERR_INVALID_ARG_TYPE(name, "boolean", value); +} + +/** + * @callback validateFunction + * @param {*} value + * @param {string} name + * @returns {asserts value is Function} + */ + +/** @type {validateFunction} */ +function validateFunction(value, name) { + if (typeof value !== "function") throw new ERR_INVALID_ARG_TYPE(name, "Function", value); +} + +/** + * @callback validateAbortSignal + * @param {*} signal + * @param {string} name + */ + +/** @type {validateAbortSignal} */ +const validateAbortSignal = (signal, name) => { + if (signal !== undefined && (signal === null || typeof signal !== "object" || !("aborted" in signal))) { + throw new ERR_INVALID_ARG_TYPE(name, "AbortSignal", signal); + } +}; + +/** + * @callback validateOneOf + * @template T + * @param {T} value + * @param {string} name + * @param {T[]} oneOf + */ + +/** @type {validateOneOf} */ +const validateOneOf = (value, name, oneOf) => { + // const validateOneOf = hideStackFrames((value, name, oneOf) => { + if (!ArrayPrototypeIncludes.call(oneOf, value)) { + const allowed = ArrayPrototypeJoin.call( + ArrayPrototypeMap.call(oneOf, v => (typeof v === "string" ? `'${v}'` : String(v))), + ", ", + ); + const reason = "must be one of: " + allowed; + throw new ERR_INVALID_ARG_VALUE(name, value, reason); + } +}; + +/** + * @callback validateObject + * @param {*} value + * @param {string} name + * @param {{ + * allowArray?: boolean, + * allowFunction?: boolean, + * nullable?: boolean + * }} [options] + */ + +/** @type {validateObject} */ +const validateObject = (value, name, options = null) => { + // const validateObject = hideStackFrames((value, name, options = null) => { + const allowArray = options?.allowArray ?? false; + const allowFunction = options?.allowFunction ?? false; + const nullable = options?.nullable ?? false; + if ( + (!nullable && value === null) || + (!allowArray && ArrayIsArray.call(value)) || + (typeof value !== "object" && (!allowFunction || typeof value !== "function")) + ) { + throw new ERR_INVALID_ARG_TYPE(name, "object", value); + } +}; + +/** + * @callback validateArray + * @param {*} value + * @param {string} name + * @param {number} [minLength] + * @returns {asserts value is any[]} + */ + +/** @type {validateArray} */ +const validateArray = (value, name, minLength = 0) => { + // const validateArray = hideStackFrames((value, name, minLength = 0) => { + if (!ArrayIsArray(value)) { + throw new ERR_INVALID_ARG_TYPE(name, "Array", value); + } + if (value.length < minLength) { + const reason = `must be longer than ${minLength}`; + throw new ERR_INVALID_ARG_VALUE(name, value, reason); + } +}; + +/** + * @callback validateString + * @param {*} value + * @param {string} name + * @returns {asserts value is string} + */ + +/** @type {validateString} */ +function validateString(value, name) { + if (typeof value !== "string") throw new ERR_INVALID_ARG_TYPE(name, "string", value); +} + +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 = new 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 new 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; +var RangeError = globalThis.RangeError; + +// Node uses a slightly different abort error than standard DOM. See: https://github.com/nodejs/node/blob/main/lib/internal/errors.js +class AbortError extends Error { + code = "ABORT_ERR"; + name = "AbortError"; + constructor(message = "The operation was aborted", options = undefined) { + if (options !== undefined && typeof options !== "object") { + throw new ERR_INVALID_ARG_TYPE("options", "Object", options); + } + super(message, options); + } +} + +function genericNodeError(message, options) { + const err = new Error(message); + err.code = options.code; + err.killed = options.killed; + err.signal = options.signal; + 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 (!ArrayIsArray(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); +// ArrayPrototypePush.call(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_OUT_OF_RANGE(str, range, input, replaceDefaultBoolean = false) { + // Node implementation: + // assert(range, 'Missing "range" argument'); + // let msg = replaceDefaultBoolean + // ? str + // : `The value of "${str}" is out of range.`; + // let received; + // if (NumberIsInteger(input) && MathAbs(input) > 2 ** 32) { + // received = addNumericalSeparator(String(input)); + // } else if (typeof input === "bigint") { + // received = String(input); + // if (input > 2n ** 32n || input < -(2n ** 32n)) { + // received = addNumericalSeparator(received); + // } + // received += "n"; + // } else { + // received = lazyInternalUtilInspect().inspect(input); + // } + // msg += ` It must be ${range}. Received ${received}`; + // return new RangeError(msg); + return new RangeError(`The value of ${str} is out of range. It must be ${range}. Received ${input}`); +} + +function ERR_CHILD_PROCESS_STDIO_MAXBUFFER(stdio) { + return Error(`${stdio} maxBuffer length exceeded`); +} + +function ERR_UNKNOWN_SIGNAL(name) { + const err = new TypeError(`Unknown signal: ${name}`); + err.code = "ERR_UNKNOWN_SIGNAL"; + return err; +} + +function ERR_INVALID_ARG_TYPE(name, type, value) { + const err = new TypeError(`The "${name}" argument must be of type ${type}. Received ${value}`); + err.code = "ERR_INVALID_ARG_TYPE"; + return err; +} + +function ERR_INVALID_OPT_VALUE(name, value) { + return new TypeError(`The value "${value}" is invalid for option "${name}"`); +} + +function ERR_INVALID_ARG_VALUE(name, value, reason) { + return new Error(`The value "${value}" is invalid for argument '${name}'. Reason: ${reason}`); +} + +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, + + [Symbol.for("CommonJS")]: 0, +}; |