mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
BREAKING(unstable): Improve Deno.spawn() stdio API (#14919)
- "SpawnOutput" extends "ChildStatus" instead of composing it - "SpawnOutput::stdout", "SpawnOutput::stderr", "Child::stdin", "Child::stdout" and "Child::stderr" are no longer optional, instead made them getters that throw at runtime if that stream wasn't set to "piped". - Remove the complicated "<T extends SpawnOptions = SpawnOptions>" which we currently need to give proper type hints for the availability of these fields. Their typings for these would get increasingly complex if it became dependent on more options (e.g. "SpawnOptions::pty" which if set should make the stdio streams unavailable)
This commit is contained in:
parent
0f6b455c96
commit
45c49034a7
14 changed files with 176 additions and 149 deletions
75
cli/dts/lib.deno.unstable.d.ts
vendored
75
cli/dts/lib.deno.unstable.d.ts
vendored
|
@ -1136,7 +1136,11 @@ declare namespace Deno {
|
||||||
/**
|
/**
|
||||||
* Spawns a child process.
|
* Spawns a child process.
|
||||||
*
|
*
|
||||||
* If stdin is set to "piped", the stdin WritableStream needs to be closed manually.
|
* If any stdio options are not set to `"piped"`, accessing the corresponding
|
||||||
|
* field on the `Child` or its `SpawnOutput` will throw a `TypeError`.
|
||||||
|
*
|
||||||
|
* If stdin is set to `"piped"`, the stdin WritableStream needs to be closed
|
||||||
|
* manually.
|
||||||
*
|
*
|
||||||
* ```ts
|
* ```ts
|
||||||
* const child = Deno.spawnChild(Deno.execPath(), {
|
* const child = Deno.spawnChild(Deno.execPath(), {
|
||||||
|
@ -1155,25 +1159,21 @@ declare namespace Deno {
|
||||||
* const status = await child.status;
|
* const status = await child.status;
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export function spawnChild<T extends SpawnOptions = SpawnOptions>(
|
export function spawnChild(
|
||||||
command: string | URL,
|
command: string | URL,
|
||||||
options?: T,
|
options?: SpawnOptions,
|
||||||
): Child<T>;
|
): Child;
|
||||||
|
|
||||||
export class Child<T extends SpawnOptions> {
|
|
||||||
readonly stdin: T["stdin"] extends "piped" ? WritableStream<Uint8Array>
|
|
||||||
: null;
|
|
||||||
readonly stdout: T["stdout"] extends "inherit" | "null" ? null
|
|
||||||
: ReadableStream<Uint8Array>;
|
|
||||||
readonly stderr: T["stderr"] extends "inherit" | "null" ? null
|
|
||||||
: ReadableStream<Uint8Array>;
|
|
||||||
|
|
||||||
|
export class Child {
|
||||||
|
get stdin(): WritableStream<Uint8Array>;
|
||||||
|
get stdout(): ReadableStream<Uint8Array>;
|
||||||
|
get stderr(): ReadableStream<Uint8Array>;
|
||||||
readonly pid: number;
|
readonly pid: number;
|
||||||
/** Get the status of the child. */
|
/** Get the status of the child. */
|
||||||
readonly status: Promise<ChildStatus>;
|
readonly status: Promise<ChildStatus>;
|
||||||
|
|
||||||
/** Waits for the child to exit completely, returning all its output and status. */
|
/** Waits for the child to exit completely, returning all its output and status. */
|
||||||
output(): Promise<SpawnOutput<T>>;
|
output(): Promise<SpawnOutput>;
|
||||||
/** Kills the process with given Signal. Defaults to SIGTERM. */
|
/** Kills the process with given Signal. Defaults to SIGTERM. */
|
||||||
kill(signo?: Signal): void;
|
kill(signo?: Signal): void;
|
||||||
}
|
}
|
||||||
|
@ -1183,61 +1183,60 @@ declare namespace Deno {
|
||||||
* collecting all of its output.
|
* collecting all of its output.
|
||||||
* Will throw an error if `stdin: "piped"` is passed.
|
* Will throw an error if `stdin: "piped"` is passed.
|
||||||
*
|
*
|
||||||
|
* If options `stdout` or `stderr` are not set to `"piped"`, accessing the
|
||||||
|
* corresponding field on `SpawnOutput` will throw a `TypeError`.
|
||||||
|
*
|
||||||
* ```ts
|
* ```ts
|
||||||
* const { status, stdout, stderr } = await Deno.spawn(Deno.execPath(), {
|
* const { code, stdout, stderr } = await Deno.spawn(Deno.execPath(), {
|
||||||
* args: [
|
* args: [
|
||||||
* "eval",
|
* "eval",
|
||||||
* "console.log('hello'); console.error('world')",
|
* "console.log('hello'); console.error('world')",
|
||||||
* ],
|
* ],
|
||||||
* });
|
* });
|
||||||
* console.assert(status.code === 0);
|
* console.assert(code === 0);
|
||||||
* console.assert("hello\n" === new TextDecoder().decode(stdout));
|
* console.assert("hello\n" === new TextDecoder().decode(stdout));
|
||||||
* console.assert("world\n" === new TextDecoder().decode(stderr));
|
* console.assert("world\n" === new TextDecoder().decode(stderr));
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export function spawn<T extends SpawnOptions = SpawnOptions>(
|
export function spawn(
|
||||||
command: string | URL,
|
command: string | URL,
|
||||||
options?: T,
|
options?: SpawnOptions,
|
||||||
): Promise<SpawnOutput<T>>;
|
): Promise<SpawnOutput>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronously executes a subprocess, waiting for it to finish and
|
* Synchronously executes a subprocess, waiting for it to finish and
|
||||||
* collecting all of its output.
|
* collecting all of its output.
|
||||||
* Will throw an error if `stdin: "piped"` is passed.
|
* Will throw an error if `stdin: "piped"` is passed.
|
||||||
*
|
*
|
||||||
|
* If options `stdout` or `stderr` are not set to `"piped"`, accessing the
|
||||||
|
* corresponding field on `SpawnOutput` will throw a `TypeError`.
|
||||||
|
*
|
||||||
* ```ts
|
* ```ts
|
||||||
* const { status, stdout, stderr } = Deno.spawnSync(Deno.execPath(), {
|
* const { code, stdout, stderr } = Deno.spawnSync(Deno.execPath(), {
|
||||||
* args: [
|
* args: [
|
||||||
* "eval",
|
* "eval",
|
||||||
* "console.log('hello'); console.error('world')",
|
* "console.log('hello'); console.error('world')",
|
||||||
* ],
|
* ],
|
||||||
* });
|
* });
|
||||||
* console.assert(status.code === 0);
|
* console.assert(code === 0);
|
||||||
* console.assert("hello\n" === new TextDecoder().decode(stdout));
|
* console.assert("hello\n" === new TextDecoder().decode(stdout));
|
||||||
* console.assert("world\n" === new TextDecoder().decode(stderr));
|
* console.assert("world\n" === new TextDecoder().decode(stderr));
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export function spawnSync<T extends SpawnOptions = SpawnOptions>(
|
export function spawnSync(
|
||||||
command: string | URL,
|
command: string | URL,
|
||||||
options?: T,
|
options?: SpawnOptions,
|
||||||
): SpawnOutput<T>;
|
): SpawnOutput;
|
||||||
|
|
||||||
export type ChildStatus =
|
export interface ChildStatus {
|
||||||
| {
|
success: boolean;
|
||||||
success: true;
|
code: number;
|
||||||
code: 0;
|
signal: Signal | null;
|
||||||
signal: null;
|
}
|
||||||
}
|
|
||||||
| {
|
|
||||||
success: false;
|
|
||||||
code: number;
|
|
||||||
signal: Signal | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface SpawnOutput<T extends SpawnOptions> {
|
export interface SpawnOutput extends ChildStatus {
|
||||||
status: ChildStatus;
|
get stdout(): Uint8Array;
|
||||||
stdout: T["stdout"] extends "inherit" | "null" ? null : Uint8Array;
|
get stderr(): Uint8Array;
|
||||||
stderr: T["stderr"] extends "inherit" | "null" ? null : Uint8Array;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
20
cli/tests/testdata/045_proxy_test.ts
vendored
20
cli/tests/testdata/045_proxy_test.ts
vendored
|
@ -31,7 +31,7 @@ async function handler(req: Request): Promise<Response> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function testFetch() {
|
async function testFetch() {
|
||||||
const { status } = await Deno.spawn(Deno.execPath(), {
|
const { code } = await Deno.spawn(Deno.execPath(), {
|
||||||
args: [
|
args: [
|
||||||
"run",
|
"run",
|
||||||
"--quiet",
|
"--quiet",
|
||||||
|
@ -44,11 +44,11 @@ async function testFetch() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
assertEquals(status.code, 0);
|
assertEquals(code, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function testModuleDownload() {
|
async function testModuleDownload() {
|
||||||
const { status } = await Deno.spawn(Deno.execPath(), {
|
const { code } = await Deno.spawn(Deno.execPath(), {
|
||||||
args: [
|
args: [
|
||||||
"cache",
|
"cache",
|
||||||
"--reload",
|
"--reload",
|
||||||
|
@ -60,11 +60,11 @@ async function testModuleDownload() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
assertEquals(status.code, 0);
|
assertEquals(code, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function testFetchNoProxy() {
|
async function testFetchNoProxy() {
|
||||||
const { status } = await Deno.spawn(Deno.execPath(), {
|
const { code } = await Deno.spawn(Deno.execPath(), {
|
||||||
args: [
|
args: [
|
||||||
"run",
|
"run",
|
||||||
"--quiet",
|
"--quiet",
|
||||||
|
@ -78,11 +78,11 @@ async function testFetchNoProxy() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
assertEquals(status.code, 0);
|
assertEquals(code, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function testModuleDownloadNoProxy() {
|
async function testModuleDownloadNoProxy() {
|
||||||
const { status } = await Deno.spawn(Deno.execPath(), {
|
const { code } = await Deno.spawn(Deno.execPath(), {
|
||||||
args: [
|
args: [
|
||||||
"cache",
|
"cache",
|
||||||
"--reload",
|
"--reload",
|
||||||
|
@ -95,11 +95,11 @@ async function testModuleDownloadNoProxy() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
assertEquals(status.code, 0);
|
assertEquals(code, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function testFetchProgrammaticProxy() {
|
async function testFetchProgrammaticProxy() {
|
||||||
const { status } = await Deno.spawn(Deno.execPath(), {
|
const { code } = await Deno.spawn(Deno.execPath(), {
|
||||||
args: [
|
args: [
|
||||||
"run",
|
"run",
|
||||||
"--quiet",
|
"--quiet",
|
||||||
|
@ -109,7 +109,7 @@ async function testFetchProgrammaticProxy() {
|
||||||
"045_programmatic_proxy_client.ts",
|
"045_programmatic_proxy_client.ts",
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
assertEquals(status.code, 0);
|
assertEquals(code, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
proxyServer();
|
proxyServer();
|
||||||
|
|
4
cli/tests/testdata/089_run_allow_list.ts
vendored
4
cli/tests/testdata/089_run_allow_list.ts
vendored
|
@ -4,9 +4,9 @@ try {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { status } = await Deno.spawn("curl", {
|
const { success } = await Deno.spawn("curl", {
|
||||||
args: ["--help"],
|
args: ["--help"],
|
||||||
stdout: "null",
|
stdout: "null",
|
||||||
stderr: "inherit",
|
stderr: "inherit",
|
||||||
});
|
});
|
||||||
console.log(status.success);
|
console.log(success);
|
||||||
|
|
6
cli/tests/testdata/lock_write_fetch.ts
vendored
6
cli/tests/testdata/lock_write_fetch.ts
vendored
|
@ -17,7 +17,7 @@ const fetchProc = await Deno.spawn(Deno.execPath(), {
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`fetch code: ${fetchProc.status.code}`);
|
console.log(`fetch code: ${fetchProc.code}`);
|
||||||
|
|
||||||
const fetchCheckProc = await Deno.spawn(Deno.execPath(), {
|
const fetchCheckProc = await Deno.spawn(Deno.execPath(), {
|
||||||
stdout: "null",
|
stdout: "null",
|
||||||
|
@ -30,7 +30,7 @@ const fetchCheckProc = await Deno.spawn(Deno.execPath(), {
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`fetch check code: ${fetchCheckProc.status.code}`);
|
console.log(`fetch check code: ${fetchCheckProc.code}`);
|
||||||
|
|
||||||
Deno.removeSync("./lock_write_fetch.json");
|
Deno.removeSync("./lock_write_fetch.json");
|
||||||
|
|
||||||
|
@ -47,6 +47,6 @@ const runProc = await Deno.spawn(Deno.execPath(), {
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`run code: ${runProc.status.code}`);
|
console.log(`run code: ${runProc.code}`);
|
||||||
|
|
||||||
Deno.removeSync("./lock_write_fetch.json");
|
Deno.removeSync("./lock_write_fetch.json");
|
||||||
|
|
|
@ -12,8 +12,8 @@ async function getUidAndGid(): Promise<{ uid: number; gid: number }> {
|
||||||
args: ["-g"],
|
args: ["-g"],
|
||||||
});
|
});
|
||||||
|
|
||||||
assertEquals(uidProc.status.code, 0);
|
assertEquals(uidProc.code, 0);
|
||||||
assertEquals(gidProc.status.code, 0);
|
assertEquals(gidProc.code, 0);
|
||||||
const uid = parseInt(new TextDecoder("utf-8").decode(uidProc.stdout));
|
const uid = parseInt(new TextDecoder("utf-8").decode(uidProc.stdout));
|
||||||
const gid = parseInt(new TextDecoder("utf-8").decode(gidProc.stdout));
|
const gid = parseInt(new TextDecoder("utf-8").decode(gidProc.stdout));
|
||||||
|
|
||||||
|
|
|
@ -1228,12 +1228,12 @@ Deno.test(
|
||||||
async function client() {
|
async function client() {
|
||||||
const url = `http://${hostname}:${port}/`;
|
const url = `http://${hostname}:${port}/`;
|
||||||
const args = ["-X", "DELETE", url];
|
const args = ["-X", "DELETE", url];
|
||||||
const { status } = await Deno.spawn("curl", {
|
const { success } = await Deno.spawn("curl", {
|
||||||
args,
|
args,
|
||||||
stdout: "null",
|
stdout: "null",
|
||||||
stderr: "null",
|
stderr: "null",
|
||||||
});
|
});
|
||||||
assert(status.success);
|
assert(success);
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all([server(), client()]);
|
await Promise.all([server(), client()]);
|
||||||
|
@ -1350,11 +1350,11 @@ Deno.test({
|
||||||
"--header",
|
"--header",
|
||||||
"Accept-Encoding: gzip, deflate, br",
|
"Accept-Encoding: gzip, deflate, br",
|
||||||
];
|
];
|
||||||
const { status, stdout } = await Deno.spawn("curl", {
|
const { success, stdout } = await Deno.spawn("curl", {
|
||||||
args,
|
args,
|
||||||
stderr: "null",
|
stderr: "null",
|
||||||
});
|
});
|
||||||
assert(status.success);
|
assert(success);
|
||||||
const output = decoder.decode(stdout);
|
const output = decoder.decode(stdout);
|
||||||
assert(output.includes("vary: Accept-Encoding\r\n"));
|
assert(output.includes("vary: Accept-Encoding\r\n"));
|
||||||
assert(output.includes("content-encoding: gzip\r\n"));
|
assert(output.includes("content-encoding: gzip\r\n"));
|
||||||
|
@ -1453,11 +1453,11 @@ Deno.test({
|
||||||
"--header",
|
"--header",
|
||||||
"Accept-Encoding: gzip, deflate, br",
|
"Accept-Encoding: gzip, deflate, br",
|
||||||
];
|
];
|
||||||
const { status, stdout } = await Deno.spawn("curl", {
|
const { success, stdout } = await Deno.spawn("curl", {
|
||||||
args,
|
args,
|
||||||
stderr: "null",
|
stderr: "null",
|
||||||
});
|
});
|
||||||
assert(status.success);
|
assert(success);
|
||||||
const output = decoder.decode(stdout).toLocaleLowerCase();
|
const output = decoder.decode(stdout).toLocaleLowerCase();
|
||||||
assert(output.includes("vary: accept-encoding\r\n"));
|
assert(output.includes("vary: accept-encoding\r\n"));
|
||||||
assert(!output.includes("content-encoding: "));
|
assert(!output.includes("content-encoding: "));
|
||||||
|
@ -1507,11 +1507,11 @@ Deno.test({
|
||||||
"--header",
|
"--header",
|
||||||
"Accept-Encoding: gzip;q=0.8, br;q=1.0, *;q=0.1",
|
"Accept-Encoding: gzip;q=0.8, br;q=1.0, *;q=0.1",
|
||||||
];
|
];
|
||||||
const { status, stdout } = await Deno.spawn("curl", {
|
const { success, stdout } = await Deno.spawn("curl", {
|
||||||
args,
|
args,
|
||||||
stderr: "null",
|
stderr: "null",
|
||||||
});
|
});
|
||||||
assert(status.success);
|
assert(success);
|
||||||
const output = decoder.decode(stdout);
|
const output = decoder.decode(stdout);
|
||||||
assert(output.includes("vary: Accept-Encoding\r\n"));
|
assert(output.includes("vary: Accept-Encoding\r\n"));
|
||||||
assert(output.includes("content-encoding: br\r\n"));
|
assert(output.includes("content-encoding: br\r\n"));
|
||||||
|
@ -1558,11 +1558,11 @@ Deno.test({
|
||||||
"--header",
|
"--header",
|
||||||
"Accept-Encoding: gzip, deflate, br",
|
"Accept-Encoding: gzip, deflate, br",
|
||||||
];
|
];
|
||||||
const { status, stdout } = await Deno.spawn("curl", {
|
const { success, stdout } = await Deno.spawn("curl", {
|
||||||
args,
|
args,
|
||||||
stderr: "null",
|
stderr: "null",
|
||||||
});
|
});
|
||||||
assert(status.success);
|
assert(success);
|
||||||
const output = decoder.decode(stdout);
|
const output = decoder.decode(stdout);
|
||||||
assert(output.includes("vary: Accept-Encoding, Accept\r\n"));
|
assert(output.includes("vary: Accept-Encoding, Accept\r\n"));
|
||||||
assert(output.includes("content-encoding: gzip\r\n"));
|
assert(output.includes("content-encoding: gzip\r\n"));
|
||||||
|
@ -1613,11 +1613,11 @@ Deno.test({
|
||||||
"--header",
|
"--header",
|
||||||
"Accept-Encoding: gzip, deflate, br",
|
"Accept-Encoding: gzip, deflate, br",
|
||||||
];
|
];
|
||||||
const { status, stdout } = await Deno.spawn("curl", {
|
const { success, stdout } = await Deno.spawn("curl", {
|
||||||
args,
|
args,
|
||||||
stderr: "null",
|
stderr: "null",
|
||||||
});
|
});
|
||||||
assert(status.success);
|
assert(success);
|
||||||
const output = decoder.decode(stdout);
|
const output = decoder.decode(stdout);
|
||||||
assert(output.includes("vary: Accept-Encoding\r\n"));
|
assert(output.includes("vary: Accept-Encoding\r\n"));
|
||||||
assert(
|
assert(
|
||||||
|
@ -1670,11 +1670,11 @@ Deno.test({
|
||||||
"--header",
|
"--header",
|
||||||
"Accept-Encoding: gzip, deflate, br",
|
"Accept-Encoding: gzip, deflate, br",
|
||||||
];
|
];
|
||||||
const { status, stdout } = await Deno.spawn("curl", {
|
const { success, stdout } = await Deno.spawn("curl", {
|
||||||
args,
|
args,
|
||||||
stderr: "null",
|
stderr: "null",
|
||||||
});
|
});
|
||||||
assert(status.success);
|
assert(success);
|
||||||
const output = decoder.decode(stdout);
|
const output = decoder.decode(stdout);
|
||||||
assert(output.includes("vary: Accept-Encoding\r\n"));
|
assert(output.includes("vary: Accept-Encoding\r\n"));
|
||||||
assert(
|
assert(
|
||||||
|
@ -1727,11 +1727,11 @@ Deno.test({
|
||||||
"--header",
|
"--header",
|
||||||
"Accept-Encoding: gzip, deflate, br",
|
"Accept-Encoding: gzip, deflate, br",
|
||||||
];
|
];
|
||||||
const { status, stdout } = await Deno.spawn("curl", {
|
const { success, stdout } = await Deno.spawn("curl", {
|
||||||
args,
|
args,
|
||||||
stderr: "null",
|
stderr: "null",
|
||||||
});
|
});
|
||||||
assert(status.success);
|
assert(success);
|
||||||
const output = decoder.decode(stdout);
|
const output = decoder.decode(stdout);
|
||||||
assert(output.includes("vary: Accept-Encoding\r\n"));
|
assert(output.includes("vary: Accept-Encoding\r\n"));
|
||||||
assert(!output.includes("content-encoding: "));
|
assert(!output.includes("content-encoding: "));
|
||||||
|
@ -1781,11 +1781,11 @@ Deno.test({
|
||||||
"--header",
|
"--header",
|
||||||
"Accept-Encoding: gzip, deflate, br",
|
"Accept-Encoding: gzip, deflate, br",
|
||||||
];
|
];
|
||||||
const { status, stdout } = await Deno.spawn("curl", {
|
const { success, stdout } = await Deno.spawn("curl", {
|
||||||
args,
|
args,
|
||||||
stderr: "null",
|
stderr: "null",
|
||||||
});
|
});
|
||||||
assert(status.success);
|
assert(success);
|
||||||
const output = decoder.decode(stdout);
|
const output = decoder.decode(stdout);
|
||||||
assert(output.includes("vary: Accept-Encoding\r\n"));
|
assert(output.includes("vary: Accept-Encoding\r\n"));
|
||||||
assert(!output.includes("content-encoding: "));
|
assert(!output.includes("content-encoding: "));
|
||||||
|
@ -1841,11 +1841,11 @@ Deno.test({
|
||||||
"--header",
|
"--header",
|
||||||
"Accept-Encoding: gzip, deflate, br",
|
"Accept-Encoding: gzip, deflate, br",
|
||||||
];
|
];
|
||||||
const { status, stdout } = await Deno.spawn("curl", {
|
const { success, stdout } = await Deno.spawn("curl", {
|
||||||
args,
|
args,
|
||||||
stderr: "null",
|
stderr: "null",
|
||||||
});
|
});
|
||||||
assert(status.success);
|
assert(success);
|
||||||
const output = decoder.decode(stdout);
|
const output = decoder.decode(stdout);
|
||||||
assert(output.includes("vary: Accept-Encoding\r\n"));
|
assert(output.includes("vary: Accept-Encoding\r\n"));
|
||||||
assert(output.includes("content-encoding: gzip\r\n"));
|
assert(output.includes("content-encoding: gzip\r\n"));
|
||||||
|
@ -1963,11 +1963,11 @@ Deno.test({
|
||||||
"--header",
|
"--header",
|
||||||
"Accept-Encoding: gzip, deflate, br",
|
"Accept-Encoding: gzip, deflate, br",
|
||||||
];
|
];
|
||||||
const { status, stdout } = await Deno.spawn("curl", {
|
const { success, stdout } = await Deno.spawn("curl", {
|
||||||
args,
|
args,
|
||||||
stderr: "null",
|
stderr: "null",
|
||||||
});
|
});
|
||||||
assert(status.success);
|
assert(success);
|
||||||
const output = decoder.decode(stdout);
|
const output = decoder.decode(stdout);
|
||||||
assert(output.includes("vary: Accept-Encoding\r\n"));
|
assert(output.includes("vary: Accept-Encoding\r\n"));
|
||||||
assert(output.includes("content-encoding: gzip\r\n"));
|
assert(output.includes("content-encoding: gzip\r\n"));
|
||||||
|
|
|
@ -74,11 +74,11 @@ Deno.test(
|
||||||
console.log(
|
console.log(
|
||||||
${JSON.stringify(Object.keys(expectedEnv))}.map(k => Deno.env.get(k))
|
${JSON.stringify(Object.keys(expectedEnv))}.map(k => Deno.env.get(k))
|
||||||
)`;
|
)`;
|
||||||
const { status, stdout } = await Deno.spawn(Deno.execPath(), {
|
const { success, stdout } = await Deno.spawn(Deno.execPath(), {
|
||||||
args: ["eval", src],
|
args: ["eval", src],
|
||||||
env: { ...inputEnv, NO_COLOR: "1" },
|
env: { ...inputEnv, NO_COLOR: "1" },
|
||||||
});
|
});
|
||||||
assertEquals(status.success, true);
|
assertEquals(success, true);
|
||||||
const expectedValues = Object.values(expectedEnv);
|
const expectedValues = Object.values(expectedEnv);
|
||||||
const actualValues = JSON.parse(new TextDecoder().decode(stdout));
|
const actualValues = JSON.parse(new TextDecoder().decode(stdout));
|
||||||
assertEquals(actualValues, expectedValues);
|
assertEquals(actualValues, expectedValues);
|
||||||
|
|
|
@ -262,12 +262,12 @@ if (Deno.build.os === "windows") {
|
||||||
Deno.test(
|
Deno.test(
|
||||||
{ permissions: { run: true, write: true, read: true } },
|
{ permissions: { run: true, write: true, read: true } },
|
||||||
async function removeFileSymlink() {
|
async function removeFileSymlink() {
|
||||||
const { status } = await Deno.spawn("cmd", {
|
const { success } = await Deno.spawn("cmd", {
|
||||||
args: ["/c", "mklink", "file_link", "bar"],
|
args: ["/c", "mklink", "file_link", "bar"],
|
||||||
stdout: "null",
|
stdout: "null",
|
||||||
});
|
});
|
||||||
|
|
||||||
assert(status.success);
|
assert(success);
|
||||||
await Deno.remove("file_link");
|
await Deno.remove("file_link");
|
||||||
await assertRejects(async () => {
|
await assertRejects(async () => {
|
||||||
await Deno.lstat("file_link");
|
await Deno.lstat("file_link");
|
||||||
|
@ -278,12 +278,12 @@ if (Deno.build.os === "windows") {
|
||||||
Deno.test(
|
Deno.test(
|
||||||
{ permissions: { run: true, write: true, read: true } },
|
{ permissions: { run: true, write: true, read: true } },
|
||||||
async function removeDirSymlink() {
|
async function removeDirSymlink() {
|
||||||
const { status } = await Deno.spawn("cmd", {
|
const { success } = await Deno.spawn("cmd", {
|
||||||
args: ["/c", "mklink", "/d", "dir_link", "bar"],
|
args: ["/c", "mklink", "/d", "dir_link", "bar"],
|
||||||
stdout: "null",
|
stdout: "null",
|
||||||
});
|
});
|
||||||
|
|
||||||
assert(status.success);
|
assert(success);
|
||||||
await Deno.remove("dir_link");
|
await Deno.remove("dir_link");
|
||||||
await assertRejects(async () => {
|
await assertRejects(async () => {
|
||||||
await Deno.lstat("dir_link");
|
await Deno.lstat("dir_link");
|
||||||
|
|
|
@ -185,14 +185,14 @@ Deno.test(
|
||||||
permissions: { run: true, read: true },
|
permissions: { run: true, read: true },
|
||||||
},
|
},
|
||||||
async function canExitWhileListeningToSignal() {
|
async function canExitWhileListeningToSignal() {
|
||||||
const { status } = await Deno.spawn(Deno.execPath(), {
|
const { code } = await Deno.spawn(Deno.execPath(), {
|
||||||
args: [
|
args: [
|
||||||
"eval",
|
"eval",
|
||||||
"--unstable",
|
"--unstable",
|
||||||
"Deno.addSignalListener('SIGINT', () => {})",
|
"Deno.addSignalListener('SIGINT', () => {})",
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
assertEquals(status.code, 0);
|
assertEquals(code, 0);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -64,9 +64,8 @@ Deno.test(
|
||||||
stderr: "null",
|
stderr: "null",
|
||||||
});
|
});
|
||||||
|
|
||||||
assert(child.stdin !== null);
|
assertThrows(() => child.stdout, TypeError, "stdout is not piped");
|
||||||
assert(child.stdout === null);
|
assertThrows(() => child.stderr, TypeError, "stderr is not piped");
|
||||||
assert(child.stderr === null);
|
|
||||||
|
|
||||||
const msg = new TextEncoder().encode("hello");
|
const msg = new TextEncoder().encode("hello");
|
||||||
const writer = child.stdin.getWriter();
|
const writer = child.stdin.getWriter();
|
||||||
|
@ -92,9 +91,8 @@ Deno.test(
|
||||||
stderr: "null",
|
stderr: "null",
|
||||||
});
|
});
|
||||||
|
|
||||||
assert(child.stdin === null);
|
assertThrows(() => child.stdin, TypeError, "stdin is not piped");
|
||||||
assert(child.stdout !== null);
|
assertThrows(() => child.stderr, TypeError, "stderr is not piped");
|
||||||
assert(child.stderr === null);
|
|
||||||
|
|
||||||
const readable = child.stdout.pipeThrough(new TextDecoderStream());
|
const readable = child.stdout.pipeThrough(new TextDecoderStream());
|
||||||
const reader = readable.getReader();
|
const reader = readable.getReader();
|
||||||
|
@ -122,13 +120,11 @@ Deno.test(
|
||||||
"eval",
|
"eval",
|
||||||
"await Deno.stderr.write(new TextEncoder().encode('hello'))",
|
"await Deno.stderr.write(new TextEncoder().encode('hello'))",
|
||||||
],
|
],
|
||||||
stderr: "piped",
|
|
||||||
stdout: "null",
|
stdout: "null",
|
||||||
});
|
});
|
||||||
|
|
||||||
assert(child.stdin === null);
|
assertThrows(() => child.stdin, TypeError, "stdin is not piped");
|
||||||
assert(child.stdout === null);
|
assertThrows(() => child.stdout, TypeError, "stdout is not piped");
|
||||||
assert(child.stderr !== null);
|
|
||||||
|
|
||||||
const readable = child.stderr.pipeThrough(new TextDecoderStream());
|
const readable = child.stderr.pipeThrough(new TextDecoderStream());
|
||||||
const reader = readable.getReader();
|
const reader = readable.getReader();
|
||||||
|
@ -322,62 +318,62 @@ Deno.test(
|
||||||
Deno.test(
|
Deno.test(
|
||||||
{ permissions: { run: true, read: true } },
|
{ permissions: { run: true, read: true } },
|
||||||
async function spawnSuccess() {
|
async function spawnSuccess() {
|
||||||
const { status } = await Deno.spawn(Deno.execPath(), {
|
const output = await Deno.spawn(Deno.execPath(), {
|
||||||
args: ["eval", "console.log('hello world')"],
|
args: ["eval", "console.log('hello world')"],
|
||||||
});
|
});
|
||||||
|
|
||||||
assertEquals(status.success, true);
|
assertEquals(output.success, true);
|
||||||
assertEquals(status.code, 0);
|
assertEquals(output.code, 0);
|
||||||
assertEquals(status.signal, null);
|
assertEquals(output.signal, null);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
Deno.test(
|
Deno.test(
|
||||||
{ permissions: { run: true, read: true } },
|
{ permissions: { run: true, read: true } },
|
||||||
function spawnSyncSuccess() {
|
function spawnSyncSuccess() {
|
||||||
const { status } = Deno.spawnSync(Deno.execPath(), {
|
const output = Deno.spawnSync(Deno.execPath(), {
|
||||||
args: ["eval", "console.log('hello world')"],
|
args: ["eval", "console.log('hello world')"],
|
||||||
});
|
});
|
||||||
|
|
||||||
assertEquals(status.success, true);
|
assertEquals(output.success, true);
|
||||||
assertEquals(status.code, 0);
|
assertEquals(output.code, 0);
|
||||||
assertEquals(status.signal, null);
|
assertEquals(output.signal, null);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
Deno.test(
|
Deno.test(
|
||||||
{ permissions: { run: true, read: true } },
|
{ permissions: { run: true, read: true } },
|
||||||
async function spawnUrl() {
|
async function spawnUrl() {
|
||||||
const { status, stdout } = await Deno.spawn(
|
const output = await Deno.spawn(
|
||||||
new URL(`file:///${Deno.execPath()}`),
|
new URL(`file:///${Deno.execPath()}`),
|
||||||
{
|
{
|
||||||
args: ["eval", "console.log('hello world')"],
|
args: ["eval", "console.log('hello world')"],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
assertEquals(new TextDecoder().decode(stdout), "hello world\n");
|
assertEquals(new TextDecoder().decode(output.stdout), "hello world\n");
|
||||||
|
|
||||||
assertEquals(status.success, true);
|
assertEquals(output.success, true);
|
||||||
assertEquals(status.code, 0);
|
assertEquals(output.code, 0);
|
||||||
assertEquals(status.signal, null);
|
assertEquals(output.signal, null);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
Deno.test(
|
Deno.test(
|
||||||
{ permissions: { run: true, read: true } },
|
{ permissions: { run: true, read: true } },
|
||||||
function spawnSyncUrl() {
|
function spawnSyncUrl() {
|
||||||
const { status, stdout } = Deno.spawnSync(
|
const output = Deno.spawnSync(
|
||||||
new URL(`file:///${Deno.execPath()}`),
|
new URL(`file:///${Deno.execPath()}`),
|
||||||
{
|
{
|
||||||
args: ["eval", "console.log('hello world')"],
|
args: ["eval", "console.log('hello world')"],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
assertEquals(new TextDecoder().decode(stdout), "hello world\n");
|
assertEquals(new TextDecoder().decode(output.stdout), "hello world\n");
|
||||||
|
|
||||||
assertEquals(status.success, true);
|
assertEquals(output.success, true);
|
||||||
assertEquals(status.code, 0);
|
assertEquals(output.code, 0);
|
||||||
assertEquals(status.signal, null);
|
assertEquals(output.signal, null);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -398,24 +394,24 @@ Deno.test({ permissions: { run: true } }, function spawnSyncNotFound() {
|
||||||
Deno.test(
|
Deno.test(
|
||||||
{ permissions: { run: true, read: true } },
|
{ permissions: { run: true, read: true } },
|
||||||
async function spawnFailedWithCode() {
|
async function spawnFailedWithCode() {
|
||||||
const { status } = await Deno.spawn(Deno.execPath(), {
|
const output = await Deno.spawn(Deno.execPath(), {
|
||||||
args: ["eval", "Deno.exit(41 + 1)"],
|
args: ["eval", "Deno.exit(41 + 1)"],
|
||||||
});
|
});
|
||||||
assertEquals(status.success, false);
|
assertEquals(output.success, false);
|
||||||
assertEquals(status.code, 42);
|
assertEquals(output.code, 42);
|
||||||
assertEquals(status.signal, null);
|
assertEquals(output.signal, null);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
Deno.test(
|
Deno.test(
|
||||||
{ permissions: { run: true, read: true } },
|
{ permissions: { run: true, read: true } },
|
||||||
function spawnSyncFailedWithCode() {
|
function spawnSyncFailedWithCode() {
|
||||||
const { status } = Deno.spawnSync(Deno.execPath(), {
|
const output = Deno.spawnSync(Deno.execPath(), {
|
||||||
args: ["eval", "Deno.exit(41 + 1)"],
|
args: ["eval", "Deno.exit(41 + 1)"],
|
||||||
});
|
});
|
||||||
assertEquals(status.success, false);
|
assertEquals(output.success, false);
|
||||||
assertEquals(status.code, 42);
|
assertEquals(output.code, 42);
|
||||||
assertEquals(status.signal, null);
|
assertEquals(output.signal, null);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -424,16 +420,16 @@ Deno.test(
|
||||||
permissions: { run: true, read: true },
|
permissions: { run: true, read: true },
|
||||||
},
|
},
|
||||||
async function spawnFailedWithSignal() {
|
async function spawnFailedWithSignal() {
|
||||||
const { status } = await Deno.spawn(Deno.execPath(), {
|
const output = await Deno.spawn(Deno.execPath(), {
|
||||||
args: ["eval", "--unstable", "Deno.kill(Deno.pid, 'SIGKILL')"],
|
args: ["eval", "--unstable", "Deno.kill(Deno.pid, 'SIGKILL')"],
|
||||||
});
|
});
|
||||||
assertEquals(status.success, false);
|
assertEquals(output.success, false);
|
||||||
if (Deno.build.os === "windows") {
|
if (Deno.build.os === "windows") {
|
||||||
assertEquals(status.code, 1);
|
assertEquals(output.code, 1);
|
||||||
assertEquals(status.signal, null);
|
assertEquals(output.signal, null);
|
||||||
} else {
|
} else {
|
||||||
assertEquals(status.code, 128 + 9);
|
assertEquals(output.code, 128 + 9);
|
||||||
assertEquals(status.signal, "SIGKILL");
|
assertEquals(output.signal, "SIGKILL");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -443,16 +439,16 @@ Deno.test(
|
||||||
permissions: { run: true, read: true },
|
permissions: { run: true, read: true },
|
||||||
},
|
},
|
||||||
function spawnSyncFailedWithSignal() {
|
function spawnSyncFailedWithSignal() {
|
||||||
const { status } = Deno.spawnSync(Deno.execPath(), {
|
const output = Deno.spawnSync(Deno.execPath(), {
|
||||||
args: ["eval", "--unstable", "Deno.kill(Deno.pid, 'SIGKILL')"],
|
args: ["eval", "--unstable", "Deno.kill(Deno.pid, 'SIGKILL')"],
|
||||||
});
|
});
|
||||||
assertEquals(status.success, false);
|
assertEquals(output.success, false);
|
||||||
if (Deno.build.os === "windows") {
|
if (Deno.build.os === "windows") {
|
||||||
assertEquals(status.code, 1);
|
assertEquals(output.code, 1);
|
||||||
assertEquals(status.signal, null);
|
assertEquals(output.signal, null);
|
||||||
} else {
|
} else {
|
||||||
assertEquals(status.code, 128 + 9);
|
assertEquals(output.code, 128 + 9);
|
||||||
assertEquals(status.signal, "SIGKILL");
|
assertEquals(output.signal, "SIGKILL");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -31,7 +31,7 @@ export function pathToAbsoluteFileUrl(path: string): URL {
|
||||||
const decoder = new TextDecoder();
|
const decoder = new TextDecoder();
|
||||||
|
|
||||||
export async function execCode(code: string): Promise<[number, string]> {
|
export async function execCode(code: string): Promise<[number, string]> {
|
||||||
const { status, stdout } = await Deno.spawn(Deno.execPath(), {
|
const output = await Deno.spawn(Deno.execPath(), {
|
||||||
args: [
|
args: [
|
||||||
"eval",
|
"eval",
|
||||||
"--unstable",
|
"--unstable",
|
||||||
|
@ -39,5 +39,5 @@ export async function execCode(code: string): Promise<[number, string]> {
|
||||||
code,
|
code,
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
return [status.code, decoder.decode(stdout)];
|
return [output.code, decoder.decode(output.stdout)];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1019,12 +1019,12 @@ function createHttpsListener(port: number): Deno.Listener {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function curl(url: string): Promise<string> {
|
async function curl(url: string): Promise<string> {
|
||||||
const { status, stdout } = await Deno.spawn("curl", {
|
const { success, code, stdout } = await Deno.spawn("curl", {
|
||||||
args: ["--insecure", url],
|
args: ["--insecure", url],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!status.success) {
|
if (!success) {
|
||||||
throw new Error(`curl ${url} failed: ${status.code}`);
|
throw new Error(`curl ${url} failed: ${code}`);
|
||||||
}
|
}
|
||||||
return new TextDecoder().decode(stdout);
|
return new TextDecoder().decode(stdout);
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,21 +77,27 @@
|
||||||
return this.#pid;
|
return this.#pid;
|
||||||
}
|
}
|
||||||
|
|
||||||
#stdinRid;
|
|
||||||
#stdin = null;
|
#stdin = null;
|
||||||
get stdin() {
|
get stdin() {
|
||||||
|
if (this.#stdin == null) {
|
||||||
|
throw new TypeError("stdin is not piped");
|
||||||
|
}
|
||||||
return this.#stdin;
|
return this.#stdin;
|
||||||
}
|
}
|
||||||
|
|
||||||
#stdoutRid;
|
|
||||||
#stdout = null;
|
#stdout = null;
|
||||||
get stdout() {
|
get stdout() {
|
||||||
|
if (this.#stdout == null) {
|
||||||
|
throw new TypeError("stdout is not piped");
|
||||||
|
}
|
||||||
return this.#stdout;
|
return this.#stdout;
|
||||||
}
|
}
|
||||||
|
|
||||||
#stderrRid;
|
|
||||||
#stderr = null;
|
#stderr = null;
|
||||||
get stderr() {
|
get stderr() {
|
||||||
|
if (this.#stderr == null) {
|
||||||
|
throw new TypeError("stderr is not piped");
|
||||||
|
}
|
||||||
return this.#stderr;
|
return this.#stderr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,17 +117,14 @@
|
||||||
this.#pid = pid;
|
this.#pid = pid;
|
||||||
|
|
||||||
if (stdinRid !== null) {
|
if (stdinRid !== null) {
|
||||||
this.#stdinRid = stdinRid;
|
|
||||||
this.#stdin = writableStreamForRid(stdinRid);
|
this.#stdin = writableStreamForRid(stdinRid);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdoutRid !== null) {
|
if (stdoutRid !== null) {
|
||||||
this.#stdoutRid = stdoutRid;
|
|
||||||
this.#stdout = readableStreamForRid(stdoutRid);
|
this.#stdout = readableStreamForRid(stdoutRid);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stderrRid !== null) {
|
if (stderrRid !== null) {
|
||||||
this.#stderrRid = stderrRid;
|
|
||||||
this.#stderr = readableStreamForRid(stderrRid);
|
this.#stderr = readableStreamForRid(stderrRid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,9 +162,21 @@
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status,
|
success: status.success,
|
||||||
stdout,
|
code: status.code,
|
||||||
stderr,
|
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;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,7 +213,7 @@
|
||||||
"Piped stdin is not supported for this function, use 'Deno.spawnChild()' instead",
|
"Piped stdin is not supported for this function, use 'Deno.spawnChild()' instead",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return core.opSync("op_spawn_sync", {
|
const result = core.opSync("op_spawn_sync", {
|
||||||
cmd: pathFromURL(command),
|
cmd: pathFromURL(command),
|
||||||
args: ArrayPrototypeMap(args, String),
|
args: ArrayPrototypeMap(args, String),
|
||||||
cwd: pathFromURL(cwd),
|
cwd: pathFromURL(cwd),
|
||||||
|
@ -210,6 +225,23 @@
|
||||||
stdout,
|
stdout,
|
||||||
stderr,
|
stderr,
|
||||||
});
|
});
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
window.__bootstrap.spawn = {
|
window.__bootstrap.spawn = {
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 2e180b039369a06d46adf506142b597c7ea28f35
|
Subproject commit ec11374217b94e8d0ca604e669faabe5ade5d9a4
|
Loading…
Reference in a new issue