promisify-child-process
Version:
seriously like the best async child process library
141 lines • 5.24 kB
JavaScript
import child_process from 'child_process';
function joinChunks(chunks, encoding) {
if (!chunks) return undefined;
const buffer = Buffer.concat(chunks);
return encoding && encoding !== 'buffer' ? buffer.toString(encoding) : buffer;
}
export function promisifyChildProcess(child, options) {
var _promise$finally;
const promise = new Promise((resolve, reject) => {
var _child$stdout, _child$stderr;
const encoding = options === null || options === void 0 ? void 0 : options.encoding;
const killSignal = options === null || options === void 0 ? void 0 : options.killSignal;
const captureStdio = encoding != null || (options === null || options === void 0 ? void 0 : options.maxBuffer) != null;
const maxBuffer = (options === null || options === void 0 ? void 0 : options.maxBuffer) ?? 1024 * 1024;
let bufferSize = 0;
let error;
const stdoutChunks = captureStdio && child.stdout ? [] : undefined;
const stderrChunks = captureStdio && child.stderr ? [] : undefined;
const capture = chunks => data => {
if (typeof data === 'string') data = Buffer.from(data);
const remaining = Math.max(0, maxBuffer - bufferSize);
bufferSize += Math.min(remaining, data.length);
if (data.length > remaining) {
error = new Error('maxBuffer exceeded');
child.kill(killSignal ?? 'SIGTERM');
data = data.subarray(0, remaining);
}
chunks.push(data);
};
const captureStdout = stdoutChunks ? capture(stdoutChunks) : undefined;
const captureStderr = stderrChunks ? capture(stderrChunks) : undefined;
if (captureStdout) (_child$stdout = child.stdout) === null || _child$stdout === void 0 || _child$stdout.on('data', captureStdout);
if (captureStderr) (_child$stderr = child.stderr) === null || _child$stderr === void 0 || _child$stderr.on('data', captureStderr);
function onError(err) {
error = err;
done();
}
child.on('error', onError);
function done(code = null, signal = null) {
var _child$stdout2, _child$stderr2;
child.removeListener('error', onError);
child.removeListener('close', done);
if (captureStdout) (_child$stdout2 = child.stdout) === null || _child$stdout2 === void 0 || _child$stdout2.removeListener('data', captureStdout);
if (captureStderr) (_child$stderr2 = child.stderr) === null || _child$stderr2 === void 0 || _child$stderr2.removeListener('data', captureStderr);
const stdout = joinChunks(stdoutChunks, encoding);
const stderr = joinChunks(stderrChunks, encoding);
if (error || code != null && code != 0 || signal != null) {
reject(Object.assign(error || new Error(signal != null ? `Process was killed with ${signal}` : `Process exited with code ${code}`), {
code,
signal,
killed: signal != null,
stdout,
stderr
}));
} else {
resolve({
stderr,
stdout,
code,
signal,
killed: false
});
}
}
child.on('close', done);
});
return Object.create(child, {
then: {
value: promise.then.bind(promise)
},
catch: {
value: promise.catch.bind(promise)
},
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
finally: {
value: (_promise$finally = promise.finally) === null || _promise$finally === void 0 ? void 0 : _promise$finally.bind(promise)
}
});
}
function isArray(t) {
return Array.isArray(t);
}
export function spawn(command, args, options) {
if (!isArray(args)) {
options = args;
args = [];
}
return promisifyChildProcess(child_process.spawn(command, args, options), options);
}
export function fork(module, args, options) {
if (!isArray(args)) {
options = args;
args = [];
}
return promisifyChildProcess(child_process.fork(module, args, options), options);
}
function promisifyExecMethod(method) {
return (...args) => {
var _promise$finally2;
let child;
const promise = new Promise((resolve, reject) => {
child = method(...args, (err, stdout, stderr) => {
if (err) {
reject(Object.assign(err, {
stdout,
stderr
}));
} else {
resolve({
code: 0,
signal: null,
killed: false,
stdout: stdout,
stderr: stderr
});
}
});
});
if (!child) {
throw new Error('unexpected error: child has not been initialized');
}
return Object.create(child, {
then: {
value: promise.then.bind(promise)
},
catch: {
value: promise.catch.bind(promise)
},
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
finally: {
value: (_promise$finally2 = promise.finally) === null || _promise$finally2 === void 0 ? void 0 : _promise$finally2.bind(promise)
}
});
};
}
export const exec = promisifyExecMethod(child_process.exec);
export const execFile = promisifyExecMethod(child_process.execFile);
export function isChildProcessError(error) {
return error instanceof Error && 'code' in error && 'signal' in error;
}
//# sourceMappingURL=index.js.map