mirror of
https://github.com/denoland/deno.git
synced 2024-11-22 15:06:54 -05:00
fix(cli/js/process): Strengthen socket types based on pipes (#4836)
This commit is contained in:
parent
54742d29dc
commit
b3e189ee4f
9 changed files with 61 additions and 54 deletions
10
cli/js/lib.deno.ns.d.ts
vendored
10
cli/js/lib.deno.ns.d.ts
vendored
|
@ -1741,12 +1741,12 @@ declare namespace Deno {
|
||||||
options?: { recursive: boolean }
|
options?: { recursive: boolean }
|
||||||
): AsyncIterableIterator<FsEvent>;
|
): AsyncIterableIterator<FsEvent>;
|
||||||
|
|
||||||
export class Process {
|
export class Process<T extends RunOptions = RunOptions> {
|
||||||
readonly rid: number;
|
readonly rid: number;
|
||||||
readonly pid: number;
|
readonly pid: number;
|
||||||
readonly stdin?: Writer & Closer;
|
readonly stdin: T["stdin"] extends "piped" ? Writer & Closer : null;
|
||||||
readonly stdout?: Reader & Closer;
|
readonly stdout: T["stdout"] extends "piped" ? Reader & Closer : null;
|
||||||
readonly stderr?: Reader & Closer;
|
readonly stderr: T["stderr"] extends "piped" ? Reader & Closer : null;
|
||||||
/** Resolves to the current status of the process. */
|
/** Resolves to the current status of the process. */
|
||||||
status(): Promise<ProcessStatus>;
|
status(): Promise<ProcessStatus>;
|
||||||
/** Buffer the stdout until EOF and return it as `Uint8Array`.
|
/** Buffer the stdout until EOF and return it as `Uint8Array`.
|
||||||
|
@ -1829,7 +1829,7 @@ declare namespace Deno {
|
||||||
* Details of the spawned process are returned.
|
* Details of the spawned process are returned.
|
||||||
*
|
*
|
||||||
* Requires `allow-run` permission. */
|
* Requires `allow-run` permission. */
|
||||||
export function run(opt: RunOptions): Process;
|
export function run<T extends RunOptions = RunOptions>(opt: T): Process<T>;
|
||||||
|
|
||||||
interface InspectOptions {
|
interface InspectOptions {
|
||||||
depth?: number;
|
depth?: number;
|
||||||
|
|
|
@ -5,17 +5,15 @@ import { Closer, Reader, Writer } from "./io.ts";
|
||||||
import { readAll } from "./buffer.ts";
|
import { readAll } from "./buffer.ts";
|
||||||
import { kill, runStatus as runStatusOp, run as runOp } from "./ops/process.ts";
|
import { kill, runStatus as runStatusOp, run as runOp } from "./ops/process.ts";
|
||||||
|
|
||||||
export type ProcessStdio = "inherit" | "piped" | "null";
|
|
||||||
|
|
||||||
// TODO Maybe extend VSCode's 'CommandOptions'?
|
// TODO Maybe extend VSCode's 'CommandOptions'?
|
||||||
// See https://code.visualstudio.com/docs/editor/tasks-appendix#_schema-for-tasksjson
|
// See https://code.visualstudio.com/docs/editor/tasks-appendix#_schema-for-tasksjson
|
||||||
export interface RunOptions {
|
export interface RunOptions {
|
||||||
cmd: string[];
|
cmd: string[];
|
||||||
cwd?: string;
|
cwd?: string;
|
||||||
env?: { [key: string]: string };
|
env?: { [key: string]: string };
|
||||||
stdout?: ProcessStdio | number;
|
stdout?: "inherit" | "piped" | "null" | number;
|
||||||
stderr?: ProcessStdio | number;
|
stderr?: "inherit" | "piped" | "null" | number;
|
||||||
stdin?: ProcessStdio | number;
|
stdin?: "inherit" | "piped" | "null" | number;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function runStatus(rid: number): Promise<ProcessStatus> {
|
async function runStatus(rid: number): Promise<ProcessStatus> {
|
||||||
|
@ -30,12 +28,12 @@ async function runStatus(rid: number): Promise<ProcessStatus> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Process {
|
export class Process<T extends RunOptions = RunOptions> {
|
||||||
readonly rid: number;
|
readonly rid: number;
|
||||||
readonly pid: number;
|
readonly pid: number;
|
||||||
readonly stdin?: Writer & Closer;
|
readonly stdin!: T["stdin"] extends "piped" ? Writer & Closer : null;
|
||||||
readonly stdout?: Reader & Closer;
|
readonly stdout!: T["stdout"] extends "piped" ? Reader & Closer : null;
|
||||||
readonly stderr?: Reader & Closer;
|
readonly stderr!: T["stderr"] extends "piped" ? Reader & Closer : null;
|
||||||
|
|
||||||
// @internal
|
// @internal
|
||||||
constructor(res: RunResponse) {
|
constructor(res: RunResponse) {
|
||||||
|
@ -43,15 +41,19 @@ export class Process {
|
||||||
this.pid = res.pid;
|
this.pid = res.pid;
|
||||||
|
|
||||||
if (res.stdinRid && res.stdinRid > 0) {
|
if (res.stdinRid && res.stdinRid > 0) {
|
||||||
this.stdin = new File(res.stdinRid);
|
this.stdin = (new File(res.stdinRid) as unknown) as Process<T>["stdin"];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.stdoutRid && res.stdoutRid > 0) {
|
if (res.stdoutRid && res.stdoutRid > 0) {
|
||||||
this.stdout = new File(res.stdoutRid);
|
this.stdout = (new File(res.stdoutRid) as unknown) as Process<
|
||||||
|
T
|
||||||
|
>["stdout"];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.stderrRid && res.stderrRid > 0) {
|
if (res.stderrRid && res.stderrRid > 0) {
|
||||||
this.stderr = new File(res.stderrRid);
|
this.stderr = (new File(res.stderrRid) as unknown) as Process<
|
||||||
|
T
|
||||||
|
>["stderr"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,23 +63,23 @@ export class Process {
|
||||||
|
|
||||||
async output(): Promise<Uint8Array> {
|
async output(): Promise<Uint8Array> {
|
||||||
if (!this.stdout) {
|
if (!this.stdout) {
|
||||||
throw new Error("Process.output: stdout is undefined");
|
throw new TypeError("stdout was not piped");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return await readAll(this.stdout);
|
return await readAll(this.stdout as Reader & Closer);
|
||||||
} finally {
|
} finally {
|
||||||
this.stdout.close();
|
(this.stdout as Reader & Closer).close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async stderrOutput(): Promise<Uint8Array> {
|
async stderrOutput(): Promise<Uint8Array> {
|
||||||
if (!this.stderr) {
|
if (!this.stderr) {
|
||||||
throw new Error("Process.stderrOutput: stderr is undefined");
|
throw new TypeError("stderr was not piped");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return await readAll(this.stderr);
|
return await readAll(this.stderr as Reader & Closer);
|
||||||
} finally {
|
} finally {
|
||||||
this.stderr.close();
|
(this.stderr as Reader & Closer).close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,14 +109,15 @@ interface RunResponse {
|
||||||
stdoutRid: number | null;
|
stdoutRid: number | null;
|
||||||
stderrRid: number | null;
|
stderrRid: number | null;
|
||||||
}
|
}
|
||||||
export function run({
|
|
||||||
|
export function run<T extends RunOptions = RunOptions>({
|
||||||
cmd,
|
cmd,
|
||||||
cwd = undefined,
|
cwd = undefined,
|
||||||
env = {},
|
env = {},
|
||||||
stdout = "inherit",
|
stdout = "inherit",
|
||||||
stderr = "inherit",
|
stderr = "inherit",
|
||||||
stdin = "inherit",
|
stdin = "inherit",
|
||||||
}: RunOptions): Process {
|
}: T): Process<T> {
|
||||||
const res = runOp({
|
const res = runOp({
|
||||||
cmd: cmd.map(String),
|
cmd: cmd.map(String),
|
||||||
cwd,
|
cwd,
|
||||||
|
@ -126,5 +129,5 @@ export function run({
|
||||||
stdoutRid: isRid(stdout) ? stdout : 0,
|
stdoutRid: isRid(stdout) ? stdout : 0,
|
||||||
stderrRid: isRid(stderr) ? stderr : 0,
|
stderrRid: isRid(stderr) ? stderr : 0,
|
||||||
}) as RunResponse;
|
}) as RunResponse;
|
||||||
return new Process(res);
|
return new Process<T>(res);
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ unitTest({ perms: { run: true } }, async function runSuccess(): Promise<void> {
|
||||||
assertEquals(status.success, true);
|
assertEquals(status.success, true);
|
||||||
assertEquals(status.code, 0);
|
assertEquals(status.code, 0);
|
||||||
assertEquals(status.signal, undefined);
|
assertEquals(status.signal, undefined);
|
||||||
p.stdout!.close();
|
p.stdout.close();
|
||||||
p.close();
|
p.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -141,7 +141,7 @@ unitTest({ perms: { run: true } }, async function runStdinPiped(): Promise<
|
||||||
const n = await p.stdin.write(msg);
|
const n = await p.stdin.write(msg);
|
||||||
assertEquals(n, msg.byteLength);
|
assertEquals(n, msg.byteLength);
|
||||||
|
|
||||||
p.stdin!.close();
|
p.stdin.close();
|
||||||
|
|
||||||
const status = await p.status();
|
const status = await p.status();
|
||||||
assertEquals(status.success, true);
|
assertEquals(status.success, true);
|
||||||
|
@ -161,16 +161,16 @@ unitTest({ perms: { run: true } }, async function runStdoutPiped(): Promise<
|
||||||
assert(!p.stderr);
|
assert(!p.stderr);
|
||||||
|
|
||||||
const data = new Uint8Array(10);
|
const data = new Uint8Array(10);
|
||||||
let r = await p.stdout!.read(data);
|
let r = await p.stdout.read(data);
|
||||||
if (r === null) {
|
if (r === null) {
|
||||||
throw new Error("p.stdout.read(...) should not be null");
|
throw new Error("p.stdout.read(...) should not be null");
|
||||||
}
|
}
|
||||||
assertEquals(r, 5);
|
assertEquals(r, 5);
|
||||||
const s = new TextDecoder().decode(data.subarray(0, r));
|
const s = new TextDecoder().decode(data.subarray(0, r));
|
||||||
assertEquals(s, "hello");
|
assertEquals(s, "hello");
|
||||||
r = await p.stdout!.read(data);
|
r = await p.stdout.read(data);
|
||||||
assertEquals(r, null);
|
assertEquals(r, null);
|
||||||
p.stdout!.close();
|
p.stdout.close();
|
||||||
|
|
||||||
const status = await p.status();
|
const status = await p.status();
|
||||||
assertEquals(status.success, true);
|
assertEquals(status.success, true);
|
||||||
|
@ -190,14 +190,14 @@ unitTest({ perms: { run: true } }, async function runStderrPiped(): Promise<
|
||||||
assert(!p.stdout);
|
assert(!p.stdout);
|
||||||
|
|
||||||
const data = new Uint8Array(10);
|
const data = new Uint8Array(10);
|
||||||
let r = await p.stderr!.read(data);
|
let r = await p.stderr.read(data);
|
||||||
if (r === null) {
|
if (r === null) {
|
||||||
throw new Error("p.stderr.read should not return null here");
|
throw new Error("p.stderr.read should not return null here");
|
||||||
}
|
}
|
||||||
assertEquals(r, 5);
|
assertEquals(r, 5);
|
||||||
const s = new TextDecoder().decode(data.subarray(0, r));
|
const s = new TextDecoder().decode(data.subarray(0, r));
|
||||||
assertEquals(s, "hello");
|
assertEquals(s, "hello");
|
||||||
r = await p.stderr!.read(data);
|
r = await p.stderr.read(data);
|
||||||
assertEquals(r, null);
|
assertEquals(r, null);
|
||||||
p.stderr!.close();
|
p.stderr!.close();
|
||||||
|
|
||||||
|
@ -320,9 +320,9 @@ unitTest({ perms: { run: true } }, async function runClose(): Promise<void> {
|
||||||
p.close();
|
p.close();
|
||||||
|
|
||||||
const data = new Uint8Array(10);
|
const data = new Uint8Array(10);
|
||||||
const r = await p.stderr!.read(data);
|
const r = await p.stderr.read(data);
|
||||||
assertEquals(r, null);
|
assertEquals(r, null);
|
||||||
p.stderr!.close();
|
p.stderr.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
unitTest(function signalNumbers(): void {
|
unitTest(function signalNumbers(): void {
|
||||||
|
|
|
@ -7,7 +7,9 @@ import { delay } from "../../async/delay.ts";
|
||||||
|
|
||||||
const { test } = Deno;
|
const { test } = Deno;
|
||||||
|
|
||||||
async function startServer(): Promise<Deno.Process> {
|
async function startServer(): Promise<
|
||||||
|
Deno.Process<Deno.RunOptions & { stdout: "piped" }>
|
||||||
|
> {
|
||||||
const server = Deno.run({
|
const server = Deno.run({
|
||||||
// TODO(lucacasonato): remove unstable once possible
|
// TODO(lucacasonato): remove unstable once possible
|
||||||
cmd: [
|
cmd: [
|
||||||
|
@ -27,7 +29,7 @@ async function startServer(): Promise<Deno.Process> {
|
||||||
const s = await r.readLine();
|
const s = await r.readLine();
|
||||||
assert(s !== null && s.includes("chat server starting"));
|
assert(s !== null && s.includes("chat server starting"));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
server.stdout!.close();
|
server.stdout.close();
|
||||||
server.close();
|
server.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +48,7 @@ test({
|
||||||
assert(html.includes("ws chat example"), "body is ok");
|
assert(html.includes("ws chat example"), "body is ok");
|
||||||
} finally {
|
} finally {
|
||||||
server.close();
|
server.close();
|
||||||
server.stdout!.close();
|
server.stdout.close();
|
||||||
}
|
}
|
||||||
await delay(10);
|
await delay(10);
|
||||||
},
|
},
|
||||||
|
@ -66,7 +68,7 @@ test({
|
||||||
assertEquals((await it.next()).value, "[1]: Hello");
|
assertEquals((await it.next()).value, "[1]: Hello");
|
||||||
} finally {
|
} finally {
|
||||||
server.close();
|
server.close();
|
||||||
server.stdout!.close();
|
server.stdout.close();
|
||||||
ws!.conn.close();
|
ws!.conn.close();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -17,7 +17,7 @@ Deno.test("[examples/catj] print an array", async () => {
|
||||||
|
|
||||||
assertStrictEquals(actual, expected);
|
assertStrictEquals(actual, expected);
|
||||||
} finally {
|
} finally {
|
||||||
process.stdin!.close();
|
process.stdin.close();
|
||||||
process.close();
|
process.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -36,7 +36,7 @@ Deno.test("[examples/catj] print an object", async () => {
|
||||||
|
|
||||||
assertStrictEquals(actual, expected);
|
assertStrictEquals(actual, expected);
|
||||||
} finally {
|
} finally {
|
||||||
process.stdin!.close();
|
process.stdin.close();
|
||||||
process.close();
|
process.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -54,7 +54,7 @@ Deno.test("[examples/catj] print multiple files", async () => {
|
||||||
|
|
||||||
assertStrictEquals(actual, expected);
|
assertStrictEquals(actual, expected);
|
||||||
} finally {
|
} finally {
|
||||||
process.stdin!.close();
|
process.stdin.close();
|
||||||
process.close();
|
process.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -64,8 +64,8 @@ Deno.test("[examples/catj] read from stdin", async () => {
|
||||||
const process = catj("-");
|
const process = catj("-");
|
||||||
const input = `{ "foo": "bar" }`;
|
const input = `{ "foo": "bar" }`;
|
||||||
try {
|
try {
|
||||||
await process.stdin!.write(new TextEncoder().encode(input));
|
await process.stdin.write(new TextEncoder().encode(input));
|
||||||
process.stdin!.close();
|
process.stdin.close();
|
||||||
const output = await process.output();
|
const output = await process.output();
|
||||||
const actual = decoder.decode(output).trim();
|
const actual = decoder.decode(output).trim();
|
||||||
|
|
||||||
|
@ -75,7 +75,9 @@ Deno.test("[examples/catj] read from stdin", async () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function catj(...files: string[]): Deno.Process {
|
function catj(
|
||||||
|
...files: string[]
|
||||||
|
): Deno.Process<Deno.RunOptions & { stdin: "piped"; stdout: "piped" }> {
|
||||||
return Deno.run({
|
return Deno.run({
|
||||||
cmd: [Deno.execPath(), "run", "--allow-read", "catj.ts", ...files],
|
cmd: [Deno.execPath(), "run", "--allow-read", "catj.ts", ...files],
|
||||||
cwd: "examples",
|
cwd: "examples",
|
||||||
|
|
|
@ -13,7 +13,7 @@ Deno.test("[examples/echo_server]", async () => {
|
||||||
|
|
||||||
let conn: Deno.Conn | undefined;
|
let conn: Deno.Conn | undefined;
|
||||||
try {
|
try {
|
||||||
const processReader = new BufReader(process.stdout!);
|
const processReader = new BufReader(process.stdout);
|
||||||
const message = await processReader.readLine();
|
const message = await processReader.readLine();
|
||||||
|
|
||||||
assertNotEquals(message, null);
|
assertNotEquals(message, null);
|
||||||
|
@ -38,7 +38,7 @@ Deno.test("[examples/echo_server]", async () => {
|
||||||
assertStrictEquals(actualResponse, expectedResponse);
|
assertStrictEquals(actualResponse, expectedResponse);
|
||||||
} finally {
|
} finally {
|
||||||
conn?.close();
|
conn?.close();
|
||||||
process.stdout!.close();
|
process.stdout.close();
|
||||||
process.close();
|
process.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { TextProtoReader } from "../textproto/mod.ts";
|
||||||
import { ServerRequest } from "./server.ts";
|
import { ServerRequest } from "./server.ts";
|
||||||
import { serveFile } from "./file_server.ts";
|
import { serveFile } from "./file_server.ts";
|
||||||
const { test } = Deno;
|
const { test } = Deno;
|
||||||
let fileServer: Deno.Process;
|
let fileServer: Deno.Process<Deno.RunOptions & { stdout: "piped" }>;
|
||||||
|
|
||||||
type FileServerCfg = {
|
type FileServerCfg = {
|
||||||
target?: string;
|
target?: string;
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { BufReader, BufWriter } from "../io/bufio.ts";
|
||||||
import { TextProtoReader } from "../textproto/mod.ts";
|
import { TextProtoReader } from "../textproto/mod.ts";
|
||||||
const { connect, run, test } = Deno;
|
const { connect, run, test } = Deno;
|
||||||
|
|
||||||
let server: Deno.Process;
|
let server: Deno.Process<Deno.RunOptions & { stdout: "piped" }>;
|
||||||
async function startServer(): Promise<void> {
|
async function startServer(): Promise<void> {
|
||||||
server = run({
|
server = run({
|
||||||
// TODO(lucacasonato): remove unstable when stabilized
|
// TODO(lucacasonato): remove unstable when stabilized
|
||||||
|
@ -18,7 +18,7 @@ async function startServer(): Promise<void> {
|
||||||
}
|
}
|
||||||
function killServer(): void {
|
function killServer(): void {
|
||||||
server.close();
|
server.close();
|
||||||
server.stdout?.close();
|
server.stdout.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
const input = [
|
const input = [
|
||||||
|
|
|
@ -372,7 +372,7 @@ test({
|
||||||
.catch((_): void => {}); // Ignores the error when closing the process.
|
.catch((_): void => {}); // Ignores the error when closing the process.
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const r = new TextProtoReader(new BufReader(p.stdout!));
|
const r = new TextProtoReader(new BufReader(p.stdout));
|
||||||
const s = await r.readLine();
|
const s = await r.readLine();
|
||||||
assert(s !== null && s.includes("server listening"));
|
assert(s !== null && s.includes("server listening"));
|
||||||
await delay(100);
|
await delay(100);
|
||||||
|
@ -387,7 +387,7 @@ test({
|
||||||
// Stops the sever and allows `p.status()` promise to resolve
|
// Stops the sever and allows `p.status()` promise to resolve
|
||||||
Deno.kill(p.pid, Deno.Signal.SIGKILL);
|
Deno.kill(p.pid, Deno.Signal.SIGKILL);
|
||||||
await statusPromise;
|
await statusPromise;
|
||||||
p.stdout!.close();
|
p.stdout.close();
|
||||||
p.close();
|
p.close();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -417,7 +417,7 @@ test({
|
||||||
.catch((_): void => {}); // Ignores the error when closing the process.
|
.catch((_): void => {}); // Ignores the error when closing the process.
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const r = new TextProtoReader(new BufReader(p.stdout!));
|
const r = new TextProtoReader(new BufReader(p.stdout));
|
||||||
const s = await r.readLine();
|
const s = await r.readLine();
|
||||||
assert(
|
assert(
|
||||||
s !== null && s.includes("server listening"),
|
s !== null && s.includes("server listening"),
|
||||||
|
@ -444,7 +444,7 @@ test({
|
||||||
// Stops the sever and allows `p.status()` promise to resolve
|
// Stops the sever and allows `p.status()` promise to resolve
|
||||||
Deno.kill(p.pid, Deno.Signal.SIGKILL);
|
Deno.kill(p.pid, Deno.Signal.SIGKILL);
|
||||||
await statusPromise;
|
await statusPromise;
|
||||||
p.stdout!.close();
|
p.stdout.close();
|
||||||
p.close();
|
p.close();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue