@cloud-cli/exec
Version:
Promise wrapper for child_process.spawn
91 lines (74 loc) • 2.59 kB
text/typescript
import { EventEmitter } from 'events';
import { spawn, exec as sh } from 'child_process';
import { StringDecoder } from 'string_decoder';
export interface ExecOutput {
ok: boolean;
code: number;
stdout: string;
stderr: string;
error?: Error;
}
export type ExecOptions = Parameters<typeof spawn>[2];
export type ShOptions = Parameters<typeof sh>[2];
export class Process extends EventEmitter {
code: number = 0;
completed = false;
error: Error;
get hasError() {
return Boolean((this._errorBuffer.length && this.code !== 0) || this.code !== 0 || this.error);
}
constructor(protected childProcess: ReturnType<typeof spawn>) {
super();
childProcess.stdout?.on('data', (data) => (this.stdout = data));
childProcess.stderr?.on('data', (data) => (this.stderr = data));
childProcess.on('exit', (code) => (this.code = code));
childProcess.on('close', () => this.complete());
childProcess.on('error', (error) => (this.error = error));
}
set stdout(value: Buffer) {
this._outputBuffer += this.stdoutDecoder.write(value);
}
set stderr(value: Buffer) {
this._errorBuffer += this.stderrDecoder.write(value);
}
private _outputBuffer = '';
private _errorBuffer = '';
private stdoutDecoder = new StringDecoder();
private stderrDecoder = new StringDecoder();
complete(): void {
this.completed = true;
this._outputBuffer += this.stdoutDecoder.end();
this._errorBuffer += this.stderrDecoder.end();
this.emit('done', this.output);
}
get output(): ExecOutput {
return {
ok: !this.hasError && this.code === 0,
code: this.error ? -1 : this.code,
stdout: this._outputBuffer,
stderr: this._errorBuffer,
error: this.hasError ? new Error(this._errorBuffer) : undefined,
};
}
}
export async function execString(command: string, options?: ShOptions): Promise<ExecOutput> {
/* istanbul ignore next */
process.env.DEBUG && console.log(command, options);
return execInternal(() => sh(command, options));
}
export async function exec(command: string, args: string[] = [], options?: ExecOptions): Promise<ExecOutput> {
/* istanbul ignore next */
process.env.DEBUG && console.log(command, args, options);
return execInternal(() => spawn(command, args, options));
}
function execInternal(runner: any): Promise<any> {
return new Promise((resolve, reject) => {
try {
const childProcess = runner();
const state = new Process(childProcess);
state.on('done', () => resolve(state.output));
} catch (error) {
reject(error);
}
});
}