// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. import { File } from "./files.ts"; import { close } from "./ops/resources.ts"; import { Closer, Reader, Writer } from "./io.ts"; import { readAll } from "./buffer.ts"; import { kill, runStatus as runStatusOp, run as runOp } from "./ops/process.ts"; // TODO Maybe extend VSCode's 'CommandOptions'? // See https://code.visualstudio.com/docs/editor/tasks-appendix#_schema-for-tasksjson export interface RunOptions { cmd: string[]; cwd?: string; env?: { [key: string]: string }; stdout?: "inherit" | "piped" | "null" | number; stderr?: "inherit" | "piped" | "null" | number; stdin?: "inherit" | "piped" | "null" | number; } async function runStatus(rid: number): Promise { const res = await runStatusOp(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 }; } } export class Process { readonly rid: number; readonly pid: number; readonly stdin!: T["stdin"] extends "piped" ? Writer & Closer : (Writer & Closer) | null; readonly stdout!: T["stdout"] extends "piped" ? Reader & Closer : (Writer & Closer) | null; readonly stderr!: T["stderr"] extends "piped" ? Reader & Closer : (Writer & Closer) | null; // @internal constructor(res: RunResponse) { this.rid = res.rid; this.pid = res.pid; if (res.stdinRid && res.stdinRid > 0) { this.stdin = (new File(res.stdinRid) as unknown) as Process["stdin"]; } if (res.stdoutRid && res.stdoutRid > 0) { this.stdout = (new File(res.stdoutRid) as unknown) as Process< T >["stdout"]; } if (res.stderrRid && res.stderrRid > 0) { this.stderr = (new File(res.stderrRid) as unknown) as Process< T >["stderr"]; } } status(): Promise { return runStatus(this.rid); } async output(): Promise { if (!this.stdout) { throw new TypeError("stdout was not piped"); } try { return await readAll(this.stdout as Reader & Closer); } finally { (this.stdout as Reader & Closer).close(); } } async stderrOutput(): Promise { if (!this.stderr) { throw new TypeError("stderr was not piped"); } try { return await readAll(this.stderr as Reader & Closer); } finally { (this.stderr as Reader & Closer).close(); } } close(): void { close(this.rid); } kill(signo: number): void { kill(this.pid, signo); } } export type ProcessStatus = | { success: true; code: 0; signal?: undefined; } | { success: false; code: number; signal?: number; }; function isRid(arg: unknown): arg is number { return !isNaN(arg as number); } interface RunResponse { rid: number; pid: number; stdinRid: number | null; stdoutRid: number | null; stderrRid: number | null; } export function run({ cmd, cwd = undefined, env = {}, stdout = "inherit", stderr = "inherit", stdin = "inherit", }: T): Process { const res = runOp({ cmd: cmd.map(String), cwd, env: Object.entries(env), stdin: isRid(stdin) ? "" : stdin, stdout: isRid(stdout) ? "" : stdout, stderr: isRid(stderr) ? "" : stderr, stdinRid: isRid(stdin) ? stdin : 0, stdoutRid: isRid(stdout) ? stdout : 0, stderrRid: isRid(stderr) ? stderr : 0, }) as RunResponse; return new Process(res); }