import { core, internals, primordials } from "ext:core/mod.js";
import {
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,
} from "ext:core/ops";
const {
ArrayIsArray,
ArrayPrototypeMap,
ArrayPrototypePush,
ArrayPrototypeSlice,
TypedArrayPrototypeSubarray,
TypedArrayPrototypeSet,
Uint8Array,
TypeError,
ObjectEntries,
SafeArrayIterator,
String,
SymbolAsyncDispose,
ObjectPrototypeIsPrototypeOf,
PromisePrototypeThen,
SafePromiseAll,
Symbol,
SymbolFor,
} = primordials;
import { FsFile } from "ext:deno_fs/30_fs.js";
import { readAll } from "ext:deno_io/12_io.js";
import { assert, pathFromURL } from "ext:deno_web/00_infra.js";
import { packageData } from "ext:deno_fetch/22_body.js";
import * as abortSignal from "ext:deno_web/03_abort_signal.js";
import {
ReadableStream,
readableStreamCollectIntoUint8Array,
readableStreamForRidUnrefable,
readableStreamForRidUnrefableRef,
readableStreamForRidUnrefableUnref,
ReadableStreamPrototype,
writableStreamForRid,
} from "ext:deno_web/06_streams.js";
const kInputOption = Symbol("kInputOption");
const kTimeoutOption = Symbol("kTimeoutOption");
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, SymbolFor("Deno.internal.FsFile"));
}
if (res.stdoutRid && res.stdoutRid > 0) {
this.stdout = new FsFile(
res.stdoutRid,
SymbolFor("Deno.internal.FsFile"),
);
}
if (res.stderrRid && res.stderrRid > 0) {
this.stderr = new FsFile(
res.stderrRid,
SymbolFor("Deno.internal.FsFile"),
);
}
}
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()");
}
}
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);
}
export const kExtraStdio = Symbol("extraStdio");
export const kIpc = Symbol("ipc");
export const kNeedsNpmProcessState = Symbol("needsNpmProcessState");
export 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;
export function nodeSpawnChild(command, {
args = [],
cwd,
clearEnv = false,
argv0,
env = { __proto__: null },
uid,
gid,
stdin = "null",
stdout = "piped",
stderr = "piped",
windowsRawArguments = 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,
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);
}
},
};
}
export function nodeSpawnSyncChild({
args,
cwd,
clearEnv,
argv0,
env,
uid,
gid,
stdin,
stdout,
stderr,
extraStdio = [],
windowsRawArguments,
needsNpmProcessState,
input,
timeout,
killSignal,
}) {
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,
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);
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,
};
}
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, 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 {
}
};
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 {
}
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,
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",
);
}
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;
return PromisePrototypeThen(
SafePromiseAll([
op_spawn_wait(child.rid),
stdoutRid != null ? readAllRid(stdoutRid) : null,
stderrRid != null ? readAllRid(stderrRid) : null,
]),
({ 0: status, 1: stdout, 2: stderr }) => ({
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();
}
export {
ChildProcess,
Command,
kArgv0,
kill,
kInputOption,
kKillSignalOption,
kTimeoutOption,
Process,
run,
spawn,
spawnAndWait,
spawnAndWaitSync,
};