2023-01-13 02:51:32 -05:00
|
|
|
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
2022-04-20 18:20:33 -04:00
|
|
|
|
2023-02-07 14:22:46 -05:00
|
|
|
const core = globalThis.Deno.core;
|
|
|
|
const ops = core.ops;
|
|
|
|
const primordials = globalThis.__bootstrap.primordials;
|
refactor(core): include_js_files! 'dir' option doesn't change specifiers (#18019)
This commit changes "include_js_files!" macro from "deno_core"
in a way that "dir" option doesn't cause specifiers to be rewritten
to include it.
Example:
```
include_js_files! {
dir "js",
"hello.js",
}
```
The above definition required embedders to use:
`import ... from "internal:<ext_name>/js/hello.js"`.
But with this change, the "js" directory in which the files are stored
is an implementation detail, which for embedders results in:
`import ... from "internal:<ext_name>/hello.js"`.
The directory the files are stored in, is an implementation detail and
in some cases might result in a significant size difference for the
snapshot. As an example, in "deno_node" extension, we store the
source code in "polyfills" directory; which resulted in each specifier
to look like "internal:deno_node/polyfills/<module_name>", but with
this change it's "internal:deno_node/<module_name>".
Given that "deno_node" has over 100 files, many of them having
several import specifiers to the same extension, this change removes
10 characters from each import specifier.
2023-03-04 21:31:38 -05:00
|
|
|
import { pathFromURL } from "internal:runtime/06_util.js";
|
2023-02-07 16:09:50 -05:00
|
|
|
import { add, remove } from "internal:deno_web/03_abort_signal.js";
|
2023-02-07 14:22:46 -05:00
|
|
|
const {
|
|
|
|
ArrayPrototypeMap,
|
|
|
|
ObjectEntries,
|
|
|
|
ObjectPrototypeIsPrototypeOf,
|
|
|
|
String,
|
|
|
|
TypeError,
|
|
|
|
PromisePrototypeThen,
|
|
|
|
SafePromiseAll,
|
|
|
|
SymbolFor,
|
|
|
|
Symbol,
|
|
|
|
} = primordials;
|
|
|
|
import {
|
|
|
|
readableStreamCollectIntoUint8Array,
|
|
|
|
readableStreamForRidUnrefable,
|
|
|
|
readableStreamForRidUnrefableRef,
|
|
|
|
readableStreamForRidUnrefableUnref,
|
|
|
|
ReadableStreamPrototype,
|
|
|
|
writableStreamForRid,
|
2023-02-07 16:09:50 -05:00
|
|
|
} from "internal:deno_web/06_streams.js";
|
2023-02-07 14:22:46 -05:00
|
|
|
|
|
|
|
const illegalConstructorKey = Symbol("illegalConstructorKey");
|
|
|
|
|
|
|
|
const promiseIdSymbol = SymbolFor("Deno.core.internalPromiseId");
|
|
|
|
|
|
|
|
function spawnChildInner(opFn, command, apiName, {
|
|
|
|
args = [],
|
|
|
|
cwd = undefined,
|
|
|
|
clearEnv = false,
|
|
|
|
env = {},
|
|
|
|
uid = undefined,
|
|
|
|
gid = undefined,
|
|
|
|
stdin = "null",
|
|
|
|
stdout = "piped",
|
|
|
|
stderr = "piped",
|
|
|
|
signal = undefined,
|
|
|
|
windowsRawArguments = false,
|
|
|
|
} = {}) {
|
|
|
|
const child = opFn({
|
|
|
|
cmd: pathFromURL(command),
|
|
|
|
args: ArrayPrototypeMap(args, String),
|
|
|
|
cwd: pathFromURL(cwd),
|
|
|
|
clearEnv,
|
|
|
|
env: ObjectEntries(env),
|
|
|
|
uid,
|
|
|
|
gid,
|
|
|
|
stdin,
|
|
|
|
stdout,
|
|
|
|
stderr,
|
|
|
|
windowsRawArguments,
|
|
|
|
}, apiName);
|
2023-02-13 13:25:00 -05:00
|
|
|
return new ChildProcess(illegalConstructorKey, {
|
2023-02-07 14:22:46 -05:00
|
|
|
...child,
|
|
|
|
signal,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-02-13 13:25:00 -05:00
|
|
|
function spawnChild(command, options = {}) {
|
|
|
|
return spawnChildInner(
|
|
|
|
ops.op_spawn_child,
|
|
|
|
command,
|
|
|
|
"Deno.Command().spawn()",
|
|
|
|
options,
|
|
|
|
);
|
2023-02-07 14:22:46 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
function collectOutput(readableStream) {
|
|
|
|
if (
|
|
|
|
!(ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, readableStream))
|
|
|
|
) {
|
|
|
|
return null;
|
2022-09-27 16:36:33 -04:00
|
|
|
}
|
|
|
|
|
2023-02-07 14:22:46 -05:00
|
|
|
return readableStreamCollectIntoUint8Array(readableStream);
|
|
|
|
}
|
2022-04-20 18:20:33 -04:00
|
|
|
|
2023-02-13 13:25:00 -05:00
|
|
|
class ChildProcess {
|
2023-02-07 14:22:46 -05:00
|
|
|
#rid;
|
|
|
|
#waitPromiseId;
|
|
|
|
#unrefed = false;
|
2022-04-20 18:20:33 -04:00
|
|
|
|
2023-02-07 14:22:46 -05:00
|
|
|
#pid;
|
|
|
|
get pid() {
|
|
|
|
return this.#pid;
|
|
|
|
}
|
2022-04-20 18:20:33 -04:00
|
|
|
|
2023-02-07 14:22:46 -05:00
|
|
|
#stdin = null;
|
|
|
|
get stdin() {
|
|
|
|
if (this.#stdin == null) {
|
|
|
|
throw new TypeError("stdin is not piped");
|
2022-04-20 18:20:33 -04:00
|
|
|
}
|
2023-02-07 14:22:46 -05:00
|
|
|
return this.#stdin;
|
|
|
|
}
|
2022-04-20 18:20:33 -04:00
|
|
|
|
2023-02-07 14:22:46 -05:00
|
|
|
#stdoutRid;
|
|
|
|
#stdout = null;
|
|
|
|
get stdout() {
|
|
|
|
if (this.#stdout == null) {
|
|
|
|
throw new TypeError("stdout is not piped");
|
2022-04-20 18:20:33 -04:00
|
|
|
}
|
2023-02-07 14:22:46 -05:00
|
|
|
return this.#stdout;
|
|
|
|
}
|
2022-04-20 18:20:33 -04:00
|
|
|
|
2023-02-07 14:22:46 -05:00
|
|
|
#stderrRid;
|
|
|
|
#stderr = null;
|
|
|
|
get stderr() {
|
|
|
|
if (this.#stderr == null) {
|
|
|
|
throw new TypeError("stderr is not piped");
|
2022-04-20 18:20:33 -04:00
|
|
|
}
|
2023-02-07 14:22:46 -05:00
|
|
|
return this.#stderr;
|
|
|
|
}
|
2022-04-20 18:20:33 -04:00
|
|
|
|
2023-02-07 14:22:46 -05:00
|
|
|
constructor(key = null, {
|
|
|
|
signal,
|
|
|
|
rid,
|
|
|
|
pid,
|
|
|
|
stdinRid,
|
|
|
|
stdoutRid,
|
|
|
|
stderrRid,
|
|
|
|
} = null) {
|
|
|
|
if (key !== illegalConstructorKey) {
|
|
|
|
throw new TypeError("Illegal constructor.");
|
2022-04-20 18:20:33 -04:00
|
|
|
}
|
|
|
|
|
2023-02-07 14:22:46 -05:00
|
|
|
this.#rid = rid;
|
|
|
|
this.#pid = pid;
|
2022-04-20 18:20:33 -04:00
|
|
|
|
2023-02-07 14:22:46 -05:00
|
|
|
if (stdinRid !== null) {
|
|
|
|
this.#stdin = writableStreamForRid(stdinRid);
|
|
|
|
}
|
2022-04-20 18:20:33 -04:00
|
|
|
|
2023-02-07 14:22:46 -05:00
|
|
|
if (stdoutRid !== null) {
|
|
|
|
this.#stdoutRid = stdoutRid;
|
|
|
|
this.#stdout = readableStreamForRidUnrefable(stdoutRid);
|
|
|
|
}
|
2022-04-20 18:20:33 -04:00
|
|
|
|
2023-02-07 14:22:46 -05:00
|
|
|
if (stderrRid !== null) {
|
|
|
|
this.#stderrRid = stderrRid;
|
|
|
|
this.#stderr = readableStreamForRidUnrefable(stderrRid);
|
|
|
|
}
|
2022-04-20 18:20:33 -04:00
|
|
|
|
2023-02-07 14:22:46 -05:00
|
|
|
const onAbort = () => this.kill("SIGTERM");
|
|
|
|
signal?.[add](onAbort);
|
2022-04-20 18:20:33 -04:00
|
|
|
|
2023-02-07 14:22:46 -05:00
|
|
|
const waitPromise = core.opAsync("op_spawn_wait", this.#rid);
|
|
|
|
this.#waitPromiseId = waitPromise[promiseIdSymbol];
|
|
|
|
this.#status = PromisePrototypeThen(waitPromise, (res) => {
|
|
|
|
this.#rid = null;
|
|
|
|
signal?.[remove](onAbort);
|
|
|
|
return res;
|
|
|
|
});
|
|
|
|
}
|
2022-05-11 01:59:39 -04:00
|
|
|
|
2023-02-07 14:22:46 -05:00
|
|
|
#status;
|
|
|
|
get status() {
|
|
|
|
return this.#status;
|
|
|
|
}
|
2022-04-20 18:20:33 -04:00
|
|
|
|
2023-02-07 14:22:46 -05:00
|
|
|
async output() {
|
|
|
|
if (this.#stdout?.locked) {
|
|
|
|
throw new TypeError(
|
|
|
|
"Can't collect output because stdout is locked",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if (this.#stderr?.locked) {
|
|
|
|
throw new TypeError(
|
|
|
|
"Can't collect output because stderr is locked",
|
|
|
|
);
|
2022-04-20 18:20:33 -04:00
|
|
|
}
|
|
|
|
|
2023-02-07 14:22:46 -05:00
|
|
|
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("stdout is not piped");
|
|
|
|
}
|
|
|
|
return stdout;
|
|
|
|
},
|
|
|
|
get stderr() {
|
|
|
|
if (stderr == null) {
|
|
|
|
throw new TypeError("stderr is not piped");
|
|
|
|
}
|
|
|
|
return stderr;
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
2022-04-20 18:20:33 -04:00
|
|
|
|
2023-02-07 14:22:46 -05:00
|
|
|
kill(signo = "SIGTERM") {
|
|
|
|
if (this.#rid === null) {
|
|
|
|
throw new TypeError("Child process has already terminated.");
|
2022-04-20 18:20:33 -04:00
|
|
|
}
|
2023-02-07 14:22:46 -05:00
|
|
|
ops.op_kill(this.#pid, signo, "Deno.Child.kill()");
|
|
|
|
}
|
2022-04-20 18:20:33 -04:00
|
|
|
|
2023-02-07 14:22:46 -05:00
|
|
|
ref() {
|
|
|
|
this.#unrefed = false;
|
|
|
|
core.refOp(this.#waitPromiseId);
|
|
|
|
if (this.#stdout) readableStreamForRidUnrefableRef(this.#stdout);
|
|
|
|
if (this.#stderr) readableStreamForRidUnrefableRef(this.#stderr);
|
|
|
|
}
|
|
|
|
|
|
|
|
unref() {
|
|
|
|
this.#unrefed = true;
|
|
|
|
core.unrefOp(this.#waitPromiseId);
|
|
|
|
if (this.#stdout) readableStreamForRidUnrefableUnref(this.#stdout);
|
|
|
|
if (this.#stderr) readableStreamForRidUnrefableUnref(this.#stderr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-13 13:25:00 -05:00
|
|
|
function spawn(command, options) {
|
|
|
|
if (options?.stdin === "piped") {
|
|
|
|
throw new TypeError(
|
|
|
|
"Piped stdin is not supported for this function, use 'Deno.Command().spawn()' instead",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return spawnChildInner(
|
|
|
|
ops.op_spawn_child,
|
|
|
|
command,
|
|
|
|
"Deno.Command().output()",
|
|
|
|
options,
|
|
|
|
)
|
|
|
|
.output();
|
2023-02-07 14:22:46 -05:00
|
|
|
}
|
2022-07-18 16:24:35 -04:00
|
|
|
|
2023-02-13 13:25:00 -05:00
|
|
|
function spawnSync(command, {
|
|
|
|
args = [],
|
|
|
|
cwd = undefined,
|
|
|
|
clearEnv = false,
|
|
|
|
env = {},
|
|
|
|
uid = undefined,
|
|
|
|
gid = undefined,
|
|
|
|
stdin = "null",
|
|
|
|
stdout = "piped",
|
|
|
|
stderr = "piped",
|
|
|
|
windowsRawArguments = false,
|
|
|
|
} = {}) {
|
|
|
|
if (stdin === "piped") {
|
|
|
|
throw new TypeError(
|
|
|
|
"Piped stdin is not supported for this function, use 'Deno.Command().spawn()' instead",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
const result = ops.op_spawn_sync({
|
|
|
|
cmd: pathFromURL(command),
|
|
|
|
args: ArrayPrototypeMap(args, String),
|
|
|
|
cwd: pathFromURL(cwd),
|
|
|
|
clearEnv,
|
|
|
|
env: ObjectEntries(env),
|
|
|
|
uid,
|
|
|
|
gid,
|
|
|
|
stdin,
|
|
|
|
stdout,
|
|
|
|
stderr,
|
|
|
|
windowsRawArguments,
|
|
|
|
});
|
|
|
|
return {
|
|
|
|
success: result.status.success,
|
|
|
|
code: result.status.code,
|
|
|
|
signal: result.status.signal,
|
|
|
|
get stdout() {
|
|
|
|
if (result.stdout == null) {
|
|
|
|
throw new TypeError("stdout is not piped");
|
|
|
|
}
|
|
|
|
return result.stdout;
|
|
|
|
},
|
|
|
|
get stderr() {
|
|
|
|
if (result.stderr == null) {
|
|
|
|
throw new TypeError("stderr is not piped");
|
|
|
|
}
|
|
|
|
return result.stderr;
|
|
|
|
},
|
2023-02-07 14:22:46 -05:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-02-13 13:25:00 -05:00
|
|
|
class Command {
|
|
|
|
#command;
|
|
|
|
#options;
|
2022-07-18 16:24:35 -04:00
|
|
|
|
2023-02-13 13:25:00 -05:00
|
|
|
constructor(command, options) {
|
|
|
|
this.#command = command;
|
|
|
|
this.#options = options;
|
|
|
|
}
|
2022-04-20 18:20:33 -04:00
|
|
|
|
2023-02-13 13:25:00 -05:00
|
|
|
output() {
|
|
|
|
if (this.#options?.stdin === "piped") {
|
|
|
|
throw new TypeError(
|
|
|
|
"Piped stdin is not supported for this function, use 'Deno.Command.spawn()' instead",
|
|
|
|
);
|
2023-02-07 14:22:46 -05:00
|
|
|
}
|
2023-02-13 13:25:00 -05:00
|
|
|
return spawn(this.#command, this.#options);
|
|
|
|
}
|
2022-04-20 18:20:33 -04:00
|
|
|
|
2023-02-13 13:25:00 -05:00
|
|
|
outputSync() {
|
|
|
|
if (this.#options?.stdin === "piped") {
|
|
|
|
throw new TypeError(
|
|
|
|
"Piped stdin is not supported for this function, use 'Deno.Command.spawn()' instead",
|
|
|
|
);
|
2023-02-07 14:22:46 -05:00
|
|
|
}
|
2023-02-13 13:25:00 -05:00
|
|
|
return spawnSync(this.#command, this.#options);
|
|
|
|
}
|
2022-11-13 14:00:24 -05:00
|
|
|
|
2023-02-13 13:25:00 -05:00
|
|
|
spawn() {
|
|
|
|
const options = {
|
|
|
|
...(this.#options ?? {}),
|
|
|
|
stdout: this.#options?.stdout ?? "inherit",
|
|
|
|
stderr: this.#options?.stderr ?? "inherit",
|
|
|
|
stdin: this.#options?.stdin ?? "inherit",
|
|
|
|
};
|
|
|
|
return spawnChild(this.#command, options);
|
|
|
|
}
|
2023-02-07 14:22:46 -05:00
|
|
|
}
|
|
|
|
|
2023-02-13 13:25:00 -05:00
|
|
|
export { ChildProcess, Command };
|