Skip to content

Commit

Permalink
Updates to runProcess
Browse files Browse the repository at this point in the history
* Drop back to single process instead of processPipe
* Keep output statuses and stderrs, stdouts
  • Loading branch information
paul-thompson-helix committed Sep 3, 2020
1 parent b5b79a5 commit 489ef12
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 143 deletions.
12 changes: 4 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
# Simple High level build and deploy helper utils for dnit
# Utils for Typescript / deno / dnit

Typescript utils for deno https://deno.land/ & dnit https://deno.land/x/dnit

## Importing in projects

Deno imports via HTTP(s)

There are several URL rewriting tools.

https://deno.land/x - Adds packages to their list of third party modules

https://denopkg.com/user/repo@tag/path/to/file - A simple redirect to github raw
Deno imports via HTTP(s). There are several URL rewriting tools to help import:
- https://deno.land/x - Adds packages to their list of third party modules
- https://denopkg.com/user/repo@tag/path/to/file - A simple redirect to github raw

6 changes: 5 additions & 1 deletion docker/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ export async function dockerAwsLogin(
["aws", "ecr", "get-login-password", "--region", awsRegion],
);
const dockerRepoUrl = `${awsAccountId}.dkr.ecr.${awsRegion}.amazonaws.com`;
await runProcess({
const result = await runProcess({
in: "piped",
out: "inherit",
err: "inherit",
inp: dockerLoginPassword, // pass password in on stdin
cmd: [
"docker",
Expand All @@ -31,4 +32,7 @@ export async function dockerAwsLogin(
dockerRepoUrl,
],
});
if(!result.status.success) {
throw new Error(`dockerAwsLogin fail - ${result.stderr} - ${result.stdout}`);
}
}
82 changes: 38 additions & 44 deletions process.test.ts
Original file line number Diff line number Diff line change
@@ -1,69 +1,63 @@
import { assertEquals } from "./test-deps.ts";
import { assertEquals, assertThrowsAsync } from "./test-deps.ts";

import { processPipe, run } from "./process.ts";
import { runProcess, run, runConsole } from "./process.ts";

Deno.test("Process - piping", async () => {
const str = await processPipe({
in: "null",
Deno.test("Process - stdin stdout", async () => {
const res = await runProcess({
in: "piped",
out: "piped",
inp: null,
cmds: [
{
cmd: ["echo", "hello world"],
},
{
cmd: ["cat"],
},
{
cmd: ["cat"],
},
{
cmd: ["cat"],
},
],
err: "inherit",
inp: "hello world",
cmd: ["cat"],
});

assertEquals(str.trim(), "hello world");
});
assertEquals(res.stdout, "hello world");
assertEquals(res.status.success, true);

Deno.test("Process - stdin stdout", async () => {
const str = await processPipe({
const res2 = await runProcess({
in: "piped",
out: "piped",
out: "inherit",
err: "piped",
inp: "hello world",
cmds: [
{
cmd: ["cat"],
},
{
cmd: ["cat"],
},
],
cmd: ["/bin/sh","-c","cat 1>&2"],
});

assertEquals(str, "hello world");
assertEquals(res2.stderr, "hello world");
assertEquals(res2.status.success, true);
});

Deno.test("Process - inherit", async () => {
const str = await processPipe({
const res = await runProcess({
in: "piped",
out: "inherit",
err: "inherit",
inp: "hello world",
cmds: [
{
cmd: ["cat"],
},
{
cmd: ["cat"],
},
],
cmd: ["cat"]
});

// output went to parent process stdout
assertEquals(str, null);
assertEquals(res.status.success, true);
});

Deno.test("Process - runConsole", async () => {
await runConsole(["echo","helloworld"]);
});

Deno.test("Process - runConsole throws on failure", async () => {
await assertThrowsAsync( async()=>{
// the process called "false" that always fails
await runConsole(["false"]);
});
});

Deno.test("Process - run", async () => {
const str = await run(["echo", "hello world"]);
assertEquals(str.trim(), "hello world");
});

Deno.test("Process - run throws on failure", async () => {
await assertThrowsAsync( async()=>{
// the process called "false" that always fails
await run(["false"]);
});
});
150 changes: 60 additions & 90 deletions process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ export interface IoParams {
stdout: IoOption;
stderr: IoOption;
stdin: IoOption;
} /** Options for execution of processes */
}

/** Options for execution of processes */
export type ExecOptions = {
cwd?: string;
env?: {
Expand All @@ -24,12 +25,6 @@ export type ExecParams = {
cmd: string[];
} & ExecOptions;

export class ExecError extends Error {
public constructor(params: ExecParams, public code: number) {
super(`Process ${Deno.inspect(params.cmd)} failed with ${code}`);
}
}

/** mapping from IoOption to type of data required/expected for stdio */
type ProcessIoValOpts = {
"piped": string;
Expand All @@ -39,130 +34,105 @@ type ProcessIoValOpts = {

type ProcessIoVal<T extends IoOption> = ProcessIoValOpts[T];

/** Run a chain of processes - first and last processes can take or receive either string, inherited or null stdin/stdout. */
export async function processPipe<Inp extends IoOption, Outp extends IoOption>(
export type ProcessResult<Outp extends IoOption, StdErr extends IoOption> = {
stdout: ProcessIoVal<Outp>,
stderr: ProcessIoVal<StdErr>,
status: Deno.ProcessStatus,
};

export async function runProcess<Inp extends IoOption, Outp extends IoOption, StdErr extends IoOption>(
params: {
in: Inp; /// Options for first process stdin
out: Outp; /// Options for last process stdout
stderrs?: "null" | "inherit"; /// stderr values either inherit parent process stderr or null (default: null)
inp: ProcessIoVal<Inp>; /// First process stdin string / or null.
cmds: [ExecParams, ...ExecParams[]]; /// Processes' ExecParams (non-empty)
in: Inp;
out: Outp;
err: StdErr;
inp: ProcessIoVal<Inp>;
cmd: string[],
opts?: ExecOptions
},
): Promise<ProcessIoVal<Outp>> {
const first = 0;
const second = 1;
const last = params.cmds.length - 1;
const end = params.cmds.length;

const ioOpts: IoParams[] = params.cmds.map((_, i) => ({
// first process has option for input on stdin (mid-pipe processes all use "piped" stdin)
stdin: i === first ? params.in : "piped",

// stderrs all null or inherit (defaulting to null)
stderr: params.stderrs || "null",

// last process has option for stdout output (mid-pipe processes all use "piped" stdout)
stdout: i === last ? params.out : "piped",
}));

// merge parameters from IoParams and ExecParams
const runOpts: Deno.RunOptions[] = [...params.cmds];
for (let i = first; i < end; ++i) {
runOpts[i] = { ...runOpts[i], ...ioOpts[i] };
}
): Promise<ProcessResult<Outp, StdErr>> {

const ioOpts: IoParams = {
stdin: params.in,
stderr: params.err,
stdout: params.out,
};

const runOpts: Deno.RunOptions = {
cmd: params.cmd,
...params.opts,
...ioOpts
};

/// start the processes:
const processes = runOpts.map((r) => Deno.run(r));
/// start the process:
const process = Deno.run(runOpts);

let result: ProcessIoVal<Outp> = null as ProcessIoVal<Outp>;
let stdout: ProcessIoVal<Outp> = null as ProcessIoVal<Outp>;
let stderr: ProcessIoVal<StdErr> = null as ProcessIoVal<StdErr>;

const ioJobs: Promise<void>[] = [];
if (params.in === "piped") {
/// setup write of first processes stdin - if requested
/// setup write of process stdin - if requested
const inputStr: string = params.inp as string;
ioJobs.push(writeAllClose(inputStr, processes[first].stdin!));
ioJobs.push(writeAllClose(inputStr, process.stdin!));
}
for (let i = second; i < end; ++i) {
/// setup copying all data between piped processes
/// async copies between Deno.Reader and Deno.Writer using Deno.copy
if (params.err === "piped") {
ioJobs.push(
copyAllClose(processes[i - 1].stdout!, processes[i].stdin!)
.then(() => {}),
/// setup read of process stderr - if requested
readAllClose(process.stderr!).then((x) => {
stderr = x as ProcessIoVal<StdErr>;
}),
);
}
if (params.out === "piped") {
ioJobs.push(
/// setup read of last process stdout - if requested
readAllClose(processes[last].stdout!).then((x) => {
result = x as ProcessIoVal<Outp>;
readAllClose(process.stdout!).then((x) => {
stdout = x as ProcessIoVal<Outp>;
}),
);
}

await Promise.all(ioJobs);
const statuses = await Promise.all(processes.map((p) => p.status()));
processes.forEach((p) => p.close());

/// Check processes exit status and throw for first non-success
for (let i = 0; i < statuses.length; ++i) {
const status = statuses[i];
if (status.success !== true) {
throw new ExecError(params.cmds[i], status.code);
}
}

return result;
}
const status = await process.status();
process.close();

/// Short-form run single process - all other options for stdin, stderr, stdout, cwd and envs available.
export async function runProcess<Inp extends IoOption, Outp extends IoOption>(
params: {
in: Inp; /// Options for stdin
out: Outp; /// Options for stdout
stderrs?: "null" | "inherit"; /// stderr values either inherit parent process stderr or null (default: null)
inp: ProcessIoVal<Inp>; /// stdin string / or null.
cmd: string[];
opts?: ExecOptions;
},
): Promise<ProcessIoVal<Outp>> {
return processPipe({
in: params.in,
out: params.out,
stderrs: params.stderrs,
inp: params.inp,
cmds: [
{
...params.opts,
cmd: params.cmd,
},
],
});
return {
stdout,
stderr,
status
};
}

/// Short-form run single process: no stdin or sterr - final output as string.
export async function run(cmd: string[], opts?: ExecOptions): Promise<string> {
return runProcess({
const result = await runProcess({
in: "null",
out: "piped",
stderrs: "null",
err: "inherit",
inp: null,
cmd,
opts,
});
if (result.status.success !== true) {
throw new Error(`${cmd} - ${result.status.code} - ${result.stderr} - ${result.stdout}`);
}
return result.stdout;
}

/// run with stdin, stdout and stderr to parent io
export async function runConsole(
cmd: string[],
opts?: ExecOptions,
): Promise<void> {
await runProcess({
in: "inherit",
const result = await runProcess({
in: "null",
out: "inherit",
stderrs: "inherit",
err: "inherit",
inp: null,
cmd,
opts,
});
return;
if (result.status.success !== true) {
throw new Error(`${cmd} - ${result.status.code}`);
};
}

0 comments on commit 489ef12

Please sign in to comment.