/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
ext/process/40_process.js
890 строк
21 KB
Nathan Whitaker
fix(fs): use a private FsFile constructor token (#36439)
10 авг 2026, 17:27
Не верифицирован
10 авг 2026, 17:27
eb89bc8
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. (function () { const { core, internals, primordials } = __bootstrap; const { op_kill, op_node_spawn_child, op_run, op_run_status, op_spawn_child, op_spawn_child_ref, op_spawn_child_unref, op_spawn_kill, op_spawn_sync, op_spawn_wait, } = core.ops; const { ArrayIsArray, ArrayPrototypeMap, ArrayPrototypePush, ArrayPrototypeSlice, TypedArrayPrototypeSubarray, TypedArrayPrototypeSet, Uint8Array, TypeError, NumberIsFinite, ObjectEntries, SafeArrayIterator, String, SymbolAsyncDispose, ObjectPrototypeIsPrototypeOf, PromisePrototypeThen, SafePromiseAll, Symbol, } = primordials; const { FsFile, fsFileConstructorKey } = core.loadExtScript( "ext:deno_fs/30_fs.js", ); const { readAll } = core.loadExtScript("ext:deno_io/12_io.js"); const { assert, pathFromURL } = core.loadExtScript("ext:deno_web/00_infra.js"); const { packageData } = core.loadExtScript("ext:deno_fetch/22_body.js"); const abortSignal = core.loadExtScript("ext:deno_web/03_abort_signal.js"); const { ReadableStream, readableStreamCollectIntoUint8Array, readableStreamForRidUnrefable, readableStreamForRidUnrefableRef, readableStreamForRidUnrefableUnref, ReadableStreamPrototype, writableStreamForRid, } = core.loadExtScript("ext:deno_web/06_streams.js"); // The key for private `input` option for `Deno.Command` const kInputOption = Symbol("kInputOption"); // The key for private `timeout` option for `Deno.Command` const kTimeoutOption = Symbol("kTimeoutOption"); // The key for private `killSignal` option for `Deno.Command` const kKillSignalOption = Symbol("kKillSignalOption"); function opKill(pid, signo, apiName) { op_kill(pid, signo, apiName); } function kill(pid, signo = "SIGTERM") { opKill(pid, signo, "Deno.kill()"); } function opRunStatus(rid) { return op_run_status(rid); } function opRun(request) { assert(request.cmd.length > 0); return op_run(request); } async function runStatus(rid) { const res = await opRunStatus(rid); if (res.gotSignal) { const signal = res.exitSignal; return { success: false, code: 128 + signal, signal }; } else if (res.exitCode != 0) { return { success: false, code: res.exitCode }; } else { return { success: true, code: 0 }; } } class Process { constructor(res) { this.rid = res.rid; this.pid = res.pid; if (res.stdinRid && res.stdinRid > 0) { this.stdin = new FsFile(res.stdinRid, fsFileConstructorKey); } if (res.stdoutRid && res.stdoutRid > 0) { this.stdout = new FsFile( res.stdoutRid, fsFileConstructorKey, ); } if (res.stderrRid && res.stderrRid > 0) { this.stderr = new FsFile( res.stderrRid, fsFileConstructorKey, ); } } status() { return runStatus(this.rid); } async output() { if (!this.stdout) { throw new TypeError("Cannot collect output: 'stdout' is not piped"); } try { return await readAll(this.stdout); } finally { this.stdout.close(); } } async stderrOutput() { if (!this.stderr) { throw new TypeError("Cannot collect output: 'stderr' is not piped"); } try { return await readAll(this.stderr); } finally { this.stderr.close(); } } close() { core.close(this.rid); } kill(signo = "SIGTERM") { opKill(this.pid, signo, "Deno.Process.kill()"); } } // Note: This function was soft-removed in Deno 2. Its types have been removed, // but its implementation has been kept to avoid breaking changes. function run({ cmd, cwd = undefined, env = { __proto__: null }, stdout = "inherit", stderr = "inherit", stdin = "inherit", }) { if (cmd[0] != null) { cmd = [ pathFromURL(cmd[0]), ...new SafeArrayIterator(ArrayPrototypeSlice(cmd, 1)), ]; } const res = opRun({ cmd: ArrayPrototypeMap(cmd, String), cwd, env: ObjectEntries(env), stdin, stdout, stderr, }); return new Process(res); } const kExtraStdio = Symbol("extraStdio"); const kIpc = Symbol("ipc"); const kNeedsNpmProcessState = Symbol("needsNpmProcessState"); const kSerialization = Symbol("serialization"); const kArgv0 = Symbol("argv0"); const illegalConstructorKey = Symbol("illegalConstructorKey"); function spawnChildInner(command, apiName, { args = [], cwd = undefined, clearEnv = false, env = { __proto__: null }, uid = undefined, gid = undefined, signal = undefined, stdin = "null", stdout = "piped", stderr = "piped", windowsRawArguments = false, detached = false, [kSerialization]: serialization = "json", [kExtraStdio]: extraStdio = [], [kIpc]: ipc = -1, [kNeedsNpmProcessState]: needsNpmProcessState = false, [kArgv0]: argv0 = undefined, } = { __proto__: null }) { const child = op_spawn_child({ cmd: pathFromURL(command), args: ArrayPrototypeMap(args, String), cwd: pathFromURL(cwd), clearEnv, argv0, env: ObjectEntries(env), uid, gid, stdin, stdout, stderr, windowsRawArguments, ipc, serialization, extraStdio, detached, needsNpmProcessState, }, apiName); return new ChildProcess(illegalConstructorKey, { ...child, signal, }); } function spawnChild(command, options = { __proto__: null }) { return spawnChildInner( command, "Deno.Command().spawn()", options, ); } function collectOutput(readableStream) { if ( !(ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, readableStream)) ) { return null; } return readableStreamCollectIntoUint8Array(readableStream); } const READ_PER_ITER = 64 * 1024; async function readAllRid(rid) { const buffers = []; try { while (true) { const buf = new Uint8Array(READ_PER_ITER); const nread = await core.read(rid, buf); if (nread === 0) break; ArrayPrototypePush(buffers, TypedArrayPrototypeSubarray(buf, 0, nread)); } } finally { core.tryClose(rid); } let totalLen = 0; for (let i = 0; i < buffers.length; i++) totalLen += buffers[i].length; const result = new Uint8Array(totalLen); let offset = 0; for (let i = 0; i < buffers.length; i++) { TypedArrayPrototypeSet(result, buffers[i], offset); offset += buffers[i].length; } return result; } const _ipcPipeRid = Symbol("[[ipcPipeRid]]"); const _extraPipeRids = Symbol("[[_extraPipeRids]]"); const _stdinRid = Symbol("[[stdinRid]]"); const _stdoutRid = Symbol("[[stdoutRid]]"); const _stderrRid = Symbol("[[stderrRid]]"); internals.getIpcPipeRid = (process) => process[_ipcPipeRid]; internals.getExtraPipeRids = (process) => process[_extraPipeRids]; internals.kExtraStdio = kExtraStdio; // Node compat spawn: returns a lightweight object with raw fds for stdio // instead of a full Deno.ChildProcess with web streams. // The caller (child_process.ts) is responsible for providing all fields. function nodeSpawnChild(command, { args = [], cwd, clearEnv = false, argv0, env = { __proto__: null }, uid, gid, stdin = "null", stdout = "piped", stderr = "piped", windowsRawArguments = false, windowsHide = false, ipc = -1, serialization = "json", extraStdio = [], detached = false, needsNpmProcessState = false, }) { const child = op_node_spawn_child({ cmd: pathFromURL(command), args: ArrayPrototypeMap(args, String), cwd: pathFromURL(cwd), clearEnv, argv0, env: ObjectEntries(env), uid, gid, stdin, stdout, stderr, windowsRawArguments, windowsHide, ipc, serialization, extraStdio, detached, needsNpmProcessState, }, "node:child_process"); const waitPromise = op_spawn_wait(child.rid); let waitComplete = false; const status = PromisePrototypeThen(waitPromise, (res) => { waitComplete = true; return res; }); return { __proto__: null, rid: child.rid, pid: child.pid, stdinFd: child.stdinFd, stdoutFd: child.stdoutFd, stderrFd: child.stderrFd, ipcPipeRid: child.ipcPipeRid, extraPipeFds: child.extraPipeFds, status, kill(signal) { op_spawn_kill(child.rid, signal); }, ref() { core.refOpPromise(waitPromise); if (!waitComplete) { op_spawn_child_ref(child.rid); } }, unref() { core.unrefOpPromise(waitPromise); if (!waitComplete) { op_spawn_child_unref(child.rid); } }, }; } // Node compat sync spawn: calls op_spawn_sync and returns pid/killedByTimeout // as normal fields instead of hidden properties on a Deno.CommandOutput. function nodeSpawnSyncChild({ args, cwd, clearEnv, argv0, env, uid, gid, stdin, stdout, stderr, extraStdio = [], windowsRawArguments, windowsHide = false, needsNpmProcessState, input, timeout, killSignal, maxBuffer, }) { const spawnArgs = { cmd: pathFromURL(args[0]), args: ArrayPrototypeMap(ArrayPrototypeSlice(args, 1), String), cwd: pathFromURL(cwd), clearEnv, env: ObjectEntries(env), uid, gid, stdin, stdout, stderr, windowsRawArguments, windowsHide, extraStdio, detached: false, needsNpmProcessState, input, argv0, }; const hasTimeout = timeout != null && timeout > 0; // Only forward a finite, non-negative `maxBuffer` to the op so that // Infinity / missing values map to "no limit" on the Rust side. const hasMaxBuffer = typeof maxBuffer === "number" && NumberIsFinite(maxBuffer) && maxBuffer >= 0; if (hasTimeout) { spawnArgs.timeout = timeout; } if (hasMaxBuffer) { spawnArgs.maxBuffer = maxBuffer; } // killSignal applies to both `timeout` and `maxBuffer` kills, matching // Node's behavior. if (killSignal != null && (hasTimeout || hasMaxBuffer)) { spawnArgs.killSignal = killSignal; } const result = op_spawn_sync(spawnArgs); return { __proto__: null, success: result.status.success, code: result.status.code, signal: result.status.signal, stdout: result.stdout, stderr: result.stderr, pid: result.pid, killedByTimeout: result.killedByTimeout, killedByMaxBuffer: result.killedByMaxBuffer, }; } class ChildProcess { #rid; #waitPromise; #waitComplete = false; [_ipcPipeRid]; [_extraPipeRids]; [_stdinRid]; [_stdoutRid]; [_stderrRid]; #pid; get pid() { return this.#pid; } #stdin = null; get stdin() { if (this.#stdin == null) { throw new TypeError("Cannot get 'stdin': 'stdin' is not piped"); } return this.#stdin; } #stdout = null; get stdout() { if (this.#stdout == null) { throw new TypeError("Cannot get 'stdout': 'stdout' is not piped"); } return this.#stdout; } #stderr = null; get stderr() { if (this.#stderr == null) { throw new TypeError("Cannot get 'stderr': 'stderr' is not piped"); } return this.#stderr; } constructor(key = null, { signal, rid, pid, stdinRid, stdoutRid, stderrRid, ipcPipeRid, // internal extraPipeRids, } = null) { if (key !== illegalConstructorKey) { throw new TypeError("Illegal constructor"); } this.#rid = rid; this.#pid = pid; this[_ipcPipeRid] = ipcPipeRid; this[_extraPipeRids] = extraPipeRids; this[_stdinRid] = stdinRid; this[_stdoutRid] = stdoutRid; this[_stderrRid] = stderrRid; if (stdinRid !== null) { this.#stdin = writableStreamForRid(stdinRid); } if (stdoutRid !== null) { this.#stdout = readableStreamForRidUnrefable( stdoutRid, ReadableStreamWithCollectors, ); } if (stderrRid !== null) { this.#stderr = readableStreamForRidUnrefable( stderrRid, ReadableStreamWithCollectors, ); } const onAbort = () => { try { this.kill("SIGTERM"); } catch { // Ignore the error for https://github.com/denoland/deno/issues/27112 } }; signal?.[abortSignal.add](onAbort); const waitPromise = op_spawn_wait(this.#rid); this.#waitPromise = waitPromise; this.#status = PromisePrototypeThen(waitPromise, (res) => { signal?.[abortSignal.remove](onAbort); this.#waitComplete = true; return res; }); } #status; get status() { return this.#status; } async output() { if (this.#stdout?.locked) { throw new TypeError( "Cannot collect output: 'stdout' is locked", ); } if (this.#stderr?.locked) { throw new TypeError( "Cannot collect output: 'stderr' is locked", ); } const { 0: status, 1: stdout, 2: stderr } = await SafePromiseAll([ this.#status, collectOutput(this.#stdout), collectOutput(this.#stderr), ]); return { success: status.success, code: status.code, signal: status.signal, get stdout() { if (stdout == null) { throw new TypeError("Cannot get 'stdout': 'stdout' is not piped"); } return stdout; }, get stderr() { if (stderr == null) { throw new TypeError("Cannot get 'stderr': 'stderr' is not piped"); } return stderr; }, }; } kill(signo = "SIGTERM") { if (this.#waitComplete) { throw new TypeError("Child process has already terminated"); } op_spawn_kill(this.#rid, signo); } async [SymbolAsyncDispose]() { try { op_spawn_kill(this.#rid, "SIGTERM"); } catch { // ignore errors from killing the process (such as ESRCH or BadResource) } await this.#status; } ref() { core.refOpPromise(this.#waitPromise); if (this.#stdout) readableStreamForRidUnrefableRef(this.#stdout); if (this.#stderr) readableStreamForRidUnrefableRef(this.#stderr); if (!this.#waitComplete) { op_spawn_child_ref(this.#rid); } } unref() { core.unrefOpPromise(this.#waitPromise); if (this.#stdout) readableStreamForRidUnrefableUnref(this.#stdout); if (this.#stderr) readableStreamForRidUnrefableUnref(this.#stderr); if (!this.#waitComplete) { op_spawn_child_unref(this.#rid); } } } class ReadableStreamWithCollectors extends ReadableStream { constructor(underlyingSource = undefined, strategy = undefined) { super(underlyingSource, strategy); } async arrayBuffer() { const buffer = await readableStreamCollectIntoUint8Array(this); return packageData(buffer, "ArrayBuffer", null); } async bytes() { const buffer = await readableStreamCollectIntoUint8Array(this); return packageData(buffer, "bytes", null); } async json() { const buffer = await readableStreamCollectIntoUint8Array(this); return packageData(buffer, "JSON", null); } async text() { const buffer = await readableStreamCollectIntoUint8Array(this); return packageData(buffer, "text", null); } } function spawnInner(command, { args = [], cwd = undefined, clearEnv = false, env = { __proto__: null }, uid = undefined, gid = undefined, signal = undefined, stdin = "null", stdout = "piped", stderr = "piped", windowsRawArguments = false, [kNeedsNpmProcessState]: needsNpmProcessState = false, } = { __proto__: null }) { if (stdin === "piped") { throw new TypeError( "Piped stdin is not supported for this function, use 'Deno.Command().spawn()' instead", ); } // Bypass ChildProcess and ReadableStream machinery. Creating streams // just to immediately collect all data has significant overhead that // causes RSS growth in tight loops. Reading directly from the resource // IDs avoids that overhead. const child = op_spawn_child({ cmd: pathFromURL(command), args: ArrayPrototypeMap(args, String), cwd: pathFromURL(cwd), clearEnv, argv0: undefined, env: ObjectEntries(env), uid, gid, stdin, stdout, stderr, windowsRawArguments, ipc: -1, serialization: "json", extraStdio: [], detached: false, needsNpmProcessState, }, "Deno.Command().output()"); const stdoutRid = child.stdoutRid; const stderrRid = child.stderrRid; let onAbort = null; if (signal !== undefined) { onAbort = () => { try { op_spawn_kill(child.rid, "SIGTERM"); } catch { // Ignore the error for https://github.com/denoland/deno/issues/27112 } }; if (signal.aborted) { onAbort(); } else { signal[abortSignal.add](onAbort); } } return PromisePrototypeThen( SafePromiseAll([ op_spawn_wait(child.rid), stdoutRid != null ? readAllRid(stdoutRid) : null, stderrRid != null ? readAllRid(stderrRid) : null, ]), ({ 0: status, 1: stdout, 2: stderr }) => { if (onAbort !== null) { signal[abortSignal.remove](onAbort); } return { success: status.success, code: status.code, signal: status.signal, get stdout() { if (stdout == null) { throw new TypeError("Cannot get 'stdout': 'stdout' is not piped"); } return stdout; }, get stderr() { if (stderr == null) { throw new TypeError("Cannot get 'stderr': 'stderr' is not piped"); } return stderr; }, }; }, ); } function spawnSyncInner(command, { args = [], cwd = undefined, clearEnv = false, env = { __proto__: null }, uid = undefined, gid = undefined, stdin = "null", stdout = "piped", stderr = "piped", windowsRawArguments = false, [kInputOption]: input, [kNeedsNpmProcessState]: needsNpmProcessState = false, [kTimeoutOption]: timeout, [kKillSignalOption]: killSignal, [kArgv0]: argv0 = undefined, } = { __proto__: null }) { if (stdin === "piped") { throw new TypeError( "Piped stdin is not supported for this function, use 'Deno.Command().spawn()' instead", ); } const spawnArgs = { cmd: pathFromURL(command), args: ArrayPrototypeMap(args, String), cwd: pathFromURL(cwd), clearEnv, env: ObjectEntries(env), uid, gid, stdin, stdout, stderr, windowsRawArguments, extraStdio: [], detached: false, needsNpmProcessState, input, argv0, }; if (timeout != null && timeout > 0) { spawnArgs.timeout = timeout; if (killSignal != null) { spawnArgs.killSignal = killSignal; } } const result = op_spawn_sync(spawnArgs); const output = { success: result.status.success, code: result.status.code, signal: result.status.signal, get stdout() { if (result.stdout == null) { throw new TypeError("Cannot get 'stdout': 'stdout' is not piped"); } return result.stdout; }, get stderr() { if (result.stderr == null) { throw new TypeError("Cannot get 'stderr': 'stderr' is not piped"); } return result.stderr; }, }; return output; } class Command { #command; #options; constructor(command, options) { this.#command = command; this.#options = options; } output() { if (this.#options?.stdin === "piped") { throw new TypeError( "Piped stdin is not supported for this function, use 'Deno.Command.spawn()' instead", ); } return spawnInner(this.#command, this.#options); } outputSync() { if (this.#options?.stdin === "piped") { throw new TypeError( "Piped stdin is not supported for this function, use 'Deno.Command.spawn()' instead", ); } return spawnSyncInner(this.#command, this.#options); } spawn() { const options = { __proto__: null, ...(this.#options ?? {}), stdout: this.#options?.stdout ?? "inherit", stderr: this.#options?.stderr ?? "inherit", stdin: this.#options?.stdin ?? "inherit", }; return spawnChild(this.#command, options); } } function spawn(command, argsOrOptions, maybeOptions) { if (ArrayIsArray(argsOrOptions)) { const options = maybeOptions ?? {}; if (options.args !== undefined) { throw new TypeError( "Passing 'args' in options is not allowed when args are passed as a separate argument", ); } return new Command(command, { ...options, args: argsOrOptions }).spawn(); } return new Command(command, argsOrOptions).spawn(); } function spawnAndWait(command, argsOrOptions, maybeOptions) { if (ArrayIsArray(argsOrOptions)) { const options = maybeOptions ?? {}; if (options.args !== undefined) { throw new TypeError( "Passing 'args' in options is not allowed when args are passed as a separate argument", ); } return new Command(command, { ...options, args: argsOrOptions }).output(); } return new Command(command, argsOrOptions).output(); } function spawnAndWaitSync(command, argsOrOptions, maybeOptions) { if (ArrayIsArray(argsOrOptions)) { const options = maybeOptions ?? {}; if (options.args !== undefined) { throw new TypeError( "Passing 'args' in options is not allowed when args are passed as a separate argument", ); } return new Command(command, { ...options, args: argsOrOptions }) .outputSync(); } return new Command(command, argsOrOptions).outputSync(); } return { ChildProcess, Command, kArgv0, kExtraStdio, kIpc, kInputOption, kKillSignalOption, kNeedsNpmProcessState, kSerialization, kTimeoutOption, kill, nodeSpawnChild, nodeSpawnSyncChild, Process, run, spawn, spawnAndWait, spawnAndWaitSync, }; })();