too
Version:
Combine multiple processes' Stdout/Stderr/SIGINT to keep them all foreground
114 lines (113 loc) • 3.46 kB
JavaScript
import * as fs from 'fs';
import * as yaml from 'js-yaml';
import { createInterface } from 'readline';
import { ParallelExecutor, SequentialExecutor } from './executor.js';
import { VarGenerator } from './var.js';
import { DefaultLogger } from './logger.js';
export class Too {
version = 0;
env = {};
var = {};
include = { env_files: [] };
prep;
main;
post;
logger = new DefaultLogger();
constructor(data) {
this.version = data.version;
this.env = data.env || {};
this.var = Object.fromEntries(Object.entries(data.var || {}).map(([k, v]) => [k, new VarGenerator(v.generate, v.collect)]));
this.include.env_files = data.include?.env_files || [];
this.prep = new SequentialExecutor("PREP", data.prep);
this.main = new ParallelExecutor("MAIN", data.main);
this.post = new SequentialExecutor("POST", data.post);
}
/**
* Parse a file and return a Too instance.
* Expecting a YAML file given as an argument.
* @param fpath
* @returns
*/
static async parse(fpath) {
const data = await fs.promises.readFile(fpath);
const obj = yaml.load(data.toString());
const too = new Too(obj);
for (const k in too.var) {
const v = await too.var[k].value();
// console.log(`${k}=${v}`);
too.env[k] = v;
}
for (const i in too.include.env_files) {
const data = await fs.promises.readFile(too.include.env_files[i]);
for (const line of data.toString().split("\n")) {
const [k, v] = line.split(/=(.*)/s);
too.env[k] = v;
}
}
too.prep.env = { ...too.env };
too.main.env = { ...too.env };
too.post.env = { ...too.env };
return too;
}
static async direct(cmds) {
const data = {
version: 1,
main: {
jobs: cmds.map((cmd) => ({ run: cmd })),
},
};
return new Too(data);
}
static async interactive(io = {
stdin: process.stdin,
stdout: process.stdout,
}) {
return new Promise((resolve) => {
const data = { version: 0, main: { jobs: [], } };
const r = createInterface({
input: io.stdin || process.stdin,
output: io.stdout || process.stdout,
prompt: "> ",
});
r.prompt();
r.on("line", (line) => {
if (line.trim().length === 0) {
resolve(new Too(data));
r.close();
}
else {
data.main.jobs.push({ run: line });
r.prompt();
}
});
});
}
async run() {
process.on("SIGINT", async () => {
await this.main.cleanup("SIGINT");
await this.post.run();
process.exit(2);
});
try {
await this.prep.run();
}
catch (e) {
await this.post.run();
return e.code || 1;
}
try {
await this.main.run();
}
catch (e) {
await this.post.run();
return e.code || 1;
}
try {
await this.post.run();
}
catch (e) {
return e.code || 1;
}
return 0;
}
}