too
Version:
Combine multiple processes' Stdout/Stderr/SIGINT to keep them all foreground
87 lines (86 loc) • 2.98 kB
JavaScript
import * as child_process from 'child_process';
import * as path from 'path';
import { lookpath } from 'lookpath';
import { CYAN } from './colors.js';
import { DefaultLogger } from './logger.js';
export class Command {
oneliner;
env;
color;
logger;
stdout = process.stdout;
stderr = process.stderr;
label;
command;
args;
constructor(oneliner, env = {}, color = CYAN, logger = new DefaultLogger(), label) {
this.oneliner = oneliner;
this.env = env;
this.color = color;
this.logger = logger;
// FIXME: 乱暴な分割なので、スペースを含む引数に対応する
const [c, ...a] = this.oneliner.split(" ");
this.command = c;
this.args = a;
this.label = label || this.command;
}
async start() {
const bin = await lookpath(this.command, { include: this.path() });
if (!bin) {
return Promise.reject({ msg: `command not found: ${this.command}`, code: 127 });
}
this.logger.accept(this.label, this.color, this.oneliner);
const stream = child_process.spawn(this.command, this.args, {
detached: false,
env: {
...process.env, ...this.env,
PATH: this.path().join(path.delimiter),
},
shell: true,
stdio: ["pipe", "pipe", "pipe"],
});
stream.stdout.on("data", (chunk) => {
this.logger.output(this.label, this.color, chunk, "");
});
stream.stderr.on("data", (chunk) => {
this.logger.output(this.label, this.color, "", chunk);
});
stream.on("close", (code, signal) => {
if (code !== 0) {
this.logger.output(this.label, this.color, "", `exit code ${code !== null ? code : signal}`);
}
});
return stream;
}
path() {
const additionals = [];
const cwd = process.cwd();
if ((path.join(cwd, "package.json"))) {
additionals.push(path.join(cwd, "node_modules", ".bin"));
}
return [process.env.PATH || "", ...additionals];
}
/**
* Execute the command synchronously.
*/
async exec(env = this.env) {
const bin = await lookpath(this.command, { include: this.path() });
if (!bin) {
return Promise.reject({ msg: `command not found: ${this.command}`, code: 127 });
}
this.logger.accept(this.label, this.color, this.oneliner);
return new Promise((resolve, reject) => {
child_process.exec(this.oneliner, {
env: {
...process.env,
...env,
},
}, (err, stdout, stderr) => {
if (err)
return reject(err);
this.logger.output(this.label, this.color, stdout, stderr);
return resolve(0);
});
});
}
}