Skip to content

Commit

Permalink
BREAKING(unstable): Improve Deno.spawn() stdio API (denoland#14919)
Browse files Browse the repository at this point in the history
- "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)
  • Loading branch information
nayeemrmn committed Jul 18, 2022
1 parent 0f6b455 commit 45c4903
Show file tree
Hide file tree
Showing 14 changed files with 177 additions and 150 deletions.
77 changes: 38 additions & 39 deletions cli/dts/lib.deno.unstable.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1136,7 +1136,11 @@ declare namespace Deno {
/**
* 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
* const child = Deno.spawnChild(Deno.execPath(), {
Expand All @@ -1155,25 +1159,21 @@ declare namespace Deno {
* const status = await child.status;
* ```
*/
export function spawnChild<T extends SpawnOptions = SpawnOptions>(
export function spawnChild(
command: string | URL,
options?: T,
): Child<T>;

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>;
options?: SpawnOptions,
): Child;

export class Child {
get stdin(): WritableStream<Uint8Array>;
get stdout(): ReadableStream<Uint8Array>;
get stderr(): ReadableStream<Uint8Array>;
readonly pid: number;
/** Get the status of the child. */
readonly status: Promise<ChildStatus>;

/** 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. */
kill(signo?: Signal): void;
}
Expand All @@ -1183,61 +1183,60 @@ declare namespace Deno {
* collecting all of its output.
* 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
* const { status, stdout, stderr } = await Deno.spawn(Deno.execPath(), {
* const { code, stdout, stderr } = await Deno.spawn(Deno.execPath(), {
* args: [
* "eval",
* "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("world\n" === new TextDecoder().decode(stderr));
* ```
*/
export function spawn<T extends SpawnOptions = SpawnOptions>(
export function spawn(
command: string | URL,
options?: T,
): Promise<SpawnOutput<T>>;
options?: SpawnOptions,
): Promise<SpawnOutput>;

/**
* Synchronously executes a subprocess, waiting for it to finish and
* collecting all of its output.
* 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
* const { status, stdout, stderr } = Deno.spawnSync(Deno.execPath(), {
* const { code, stdout, stderr } = Deno.spawnSync(Deno.execPath(), {
* args: [
* "eval",
* "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("world\n" === new TextDecoder().decode(stderr));
* ```
*/
export function spawnSync<T extends SpawnOptions = SpawnOptions>(
export function spawnSync(
command: string | URL,
options?: T,
): SpawnOutput<T>;

export type ChildStatus =
| {
success: true;
code: 0;
signal: null;
}
| {
success: false;
code: number;
signal: Signal | null;
};
options?: SpawnOptions,
): SpawnOutput;

export interface ChildStatus {
success: boolean;
code: number;
signal: Signal | null;
}

export interface SpawnOutput<T extends SpawnOptions> {
status: ChildStatus;
stdout: T["stdout"] extends "inherit" | "null" ? null : Uint8Array;
stderr: T["stderr"] extends "inherit" | "null" ? null : Uint8Array;
export interface SpawnOutput extends ChildStatus {
get stdout(): Uint8Array;
get stderr(): Uint8Array;
}
}

Expand Down
20 changes: 10 additions & 10 deletions cli/tests/testdata/045_proxy_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ async function handler(req: Request): Promise<Response> {
}

async function testFetch() {
const { status } = await Deno.spawn(Deno.execPath(), {
const { code } = await Deno.spawn(Deno.execPath(), {
args: [
"run",
"--quiet",
Expand All @@ -44,11 +44,11 @@ async function testFetch() {
},
});

assertEquals(status.code, 0);
assertEquals(code, 0);
}

async function testModuleDownload() {
const { status } = await Deno.spawn(Deno.execPath(), {
const { code } = await Deno.spawn(Deno.execPath(), {
args: [
"cache",
"--reload",
Expand All @@ -60,11 +60,11 @@ async function testModuleDownload() {
},
});

assertEquals(status.code, 0);
assertEquals(code, 0);
}

async function testFetchNoProxy() {
const { status } = await Deno.spawn(Deno.execPath(), {
const { code } = await Deno.spawn(Deno.execPath(), {
args: [
"run",
"--quiet",
Expand All @@ -78,11 +78,11 @@ async function testFetchNoProxy() {
},
});

assertEquals(status.code, 0);
assertEquals(code, 0);
}

async function testModuleDownloadNoProxy() {
const { status } = await Deno.spawn(Deno.execPath(), {
const { code } = await Deno.spawn(Deno.execPath(), {
args: [
"cache",
"--reload",
Expand All @@ -95,11 +95,11 @@ async function testModuleDownloadNoProxy() {
},
});

assertEquals(status.code, 0);
assertEquals(code, 0);
}

async function testFetchProgrammaticProxy() {
const { status } = await Deno.spawn(Deno.execPath(), {
const { code } = await Deno.spawn(Deno.execPath(), {
args: [
"run",
"--quiet",
Expand All @@ -109,7 +109,7 @@ async function testFetchProgrammaticProxy() {
"045_programmatic_proxy_client.ts",
],
});
assertEquals(status.code, 0);
assertEquals(code, 0);
}

proxyServer();
Expand Down
4 changes: 2 additions & 2 deletions cli/tests/testdata/089_run_allow_list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ try {
console.log(e);
}

const { status } = await Deno.spawn("curl", {
const { success } = await Deno.spawn("curl", {
args: ["--help"],
stdout: "null",
stderr: "inherit",
});
console.log(status.success);
console.log(success);
6 changes: 3 additions & 3 deletions cli/tests/testdata/lock_write_fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(), {
stdout: "null",
Expand All @@ -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");

Expand All @@ -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");
4 changes: 2 additions & 2 deletions cli/tests/unit/chown_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ async function getUidAndGid(): Promise<{ uid: number; gid: number }> {
args: ["-g"],
});

assertEquals(uidProc.status.code, 0);
assertEquals(gidProc.status.code, 0);
assertEquals(uidProc.code, 0);
assertEquals(gidProc.code, 0);
const uid = parseInt(new TextDecoder("utf-8").decode(uidProc.stdout));
const gid = parseInt(new TextDecoder("utf-8").decode(gidProc.stdout));

Expand Down
Loading

0 comments on commit 45c4903

Please sign in to comment.