2020-03-31 04:37:58 +00:00
|
|
|
import { ChildProcessWithoutNullStreams, spawn, ChildProcess } from 'child_process';
|
2020-03-29 13:43:52 +00:00
|
|
|
import { BaseError } from 'noicejs';
|
|
|
|
import { Writable } from 'stream';
|
|
|
|
|
2020-03-31 13:29:47 +00:00
|
|
|
import { doesExist, Optional } from './utils';
|
|
|
|
import { ChildProcessError } from './error/ChildProcessError';
|
2020-03-29 13:43:52 +00:00
|
|
|
import { encode } from './Buffer';
|
|
|
|
import { NameValuePair } from './Map';
|
|
|
|
|
2020-03-31 04:37:58 +00:00
|
|
|
export interface ChildProcessOptions {
|
2020-03-29 13:43:52 +00:00
|
|
|
cwd: string;
|
|
|
|
env: Array<NameValuePair<string>>;
|
|
|
|
timeout: number;
|
|
|
|
}
|
|
|
|
|
2020-03-31 04:37:58 +00:00
|
|
|
export interface ChildOptions extends ChildProcessOptions {
|
|
|
|
args: Array<string>;
|
|
|
|
command: string;
|
|
|
|
}
|
|
|
|
|
2020-03-29 13:43:52 +00:00
|
|
|
export interface ChildResult {
|
|
|
|
status: number;
|
|
|
|
stderr: string;
|
|
|
|
stdout: string;
|
|
|
|
}
|
|
|
|
|
2020-03-31 04:37:58 +00:00
|
|
|
export type ChildStreams = ChildProcessWithoutNullStreams;
|
|
|
|
|
|
|
|
export type ChildSpawner = (
|
|
|
|
command: string,
|
|
|
|
args: Array<string>,
|
|
|
|
options: Partial<ChildProcessOptions>
|
|
|
|
) => ChildStreams;
|
2020-03-29 13:43:52 +00:00
|
|
|
|
|
|
|
const CHILD_ENCODING = 'utf-8';
|
|
|
|
const CHILD_EVENT = 'child process emitted error event';
|
|
|
|
const CHILD_STATUS = 'child process exited with error status';
|
|
|
|
const CHILD_OUTPUT = 'child process emitted error output';
|
|
|
|
|
2020-03-31 04:37:58 +00:00
|
|
|
export function waitForChild(child: ChildStreams): Promise<ChildResult> {
|
2020-03-29 13:43:52 +00:00
|
|
|
return new Promise((res, rej) => {
|
|
|
|
const stderr: Array<Buffer> = [];
|
|
|
|
const stdout: Array<Buffer> = [];
|
|
|
|
|
|
|
|
child.stderr.on('data', (chunk) => {
|
|
|
|
stderr.push(chunk);
|
|
|
|
});
|
|
|
|
|
|
|
|
child.stdout.on('data', (chunk) => {
|
|
|
|
stdout.push(chunk);
|
|
|
|
});
|
|
|
|
|
|
|
|
child.on('error', (err: Error | number) => {
|
|
|
|
if (err instanceof Error) {
|
|
|
|
rej(new ChildProcessError(CHILD_EVENT, err));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
child.on('close', (status: number) => {
|
|
|
|
const errors = encode(stderr, CHILD_ENCODING);
|
|
|
|
if (status > 0) {
|
|
|
|
const msg = `${CHILD_STATUS}: ${status}`;
|
|
|
|
rej(new ChildProcessError(msg, new BaseError(errors)));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (errors.length > 0) {
|
|
|
|
rej(new ChildProcessError(CHILD_OUTPUT, new BaseError(errors)));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
res({
|
|
|
|
status,
|
|
|
|
stderr: errors,
|
|
|
|
stdout: encode(stdout, CHILD_ENCODING),
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
export function writeValue(stream: Writable, value: string): Promise<boolean> {
|
|
|
|
return new Promise<boolean>((res, rej) => {
|
|
|
|
stream.write(value, (err: Optional<Error>) => {
|
|
|
|
if (doesExist(err)) {
|
|
|
|
rej(err);
|
|
|
|
} else {
|
|
|
|
stream.end(() => {
|
|
|
|
res(true);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|