// // Copyright 2021 DXOS.org // // TODO(burdon): Forked from dxos/cli. import chalk from 'chalk'; import { spawn as _spawn, ChildProcessWithoutNullStreams } from 'child_process'; export class SpawnError extends Error { constructor ( readonly code: number | null, readonly spawnMessage?: string ) { super(spawnMessage ? spawnMessage : `Exit: ${code}`); // Restore prototype chain. // https://stackoverflow.com/a/48342359 Object.setPrototypeOf(this, new.target.prototype); } } export const checkInstalled = (err: any, command: string) => { if (err instanceof SpawnError && err.code == 127) { console.log(chalk.red('Install required:'), `"npm i -g ${command}" or "yarn global add ${command}"`); } else { console.error(String(err)); } } interface Options { log?: boolean verbose?: boolean } export class Command { private _stdout = Buffer.alloc(0); private _stderr = Buffer.alloc(0); constructor ( private readonly _command: string, private readonly _cwd?: string ) {} private async _execute ({ log, verbose }: Options = {}): Promise { if (verbose) { console.log(chalk.green(`Running "${this._command}"`)); } // https://nodejs.org/api/child_process.html#child_processspawncommand-args-options const childProcess = _spawn(this._command, { shell: true, stdio: 'pipe', cwd: this._cwd || process.cwd(), env: { ...process.env } }); childProcess.stdout.on('data', chunk => { this._stdout = Buffer.concat([this._stdout, chunk]); if (log) { process.stdout.write(chunk); } }); childProcess.stderr.on('data', chunk => { this._stderr = Buffer.concat([this._stderr, chunk]); if (verbose) { process.stderr.write(chunk); } }); await new Promise((resolve) => { childProcess.on('close', () => { resolve(); }); }); if (verbose) { console.log(chalk.green(`Exit code: ${childProcess.exitCode}; signal: ${childProcess.signalCode}`)); } if (childProcess.exitCode === null) { throw new SpawnError(null, `Command "${this._command}" killed by signal: ${childProcess.signalCode}`); } else if (childProcess.exitCode !== 0) { throw new SpawnError(childProcess.exitCode as number, `Command failed: "${this._command}"`); } return childProcess; } async run (options: Options): Promise { const childProcess = await this._execute(options); return childProcess.exitCode; } async json (): Promise { await this._execute(); return JSON.parse(this._stdout.toString()); } } export const spawn = (command: string, cwd?: string): Command => { return new Command(command, cwd); };