UNPKG

slickgrid

Version:

A lightning fast JavaScript grid/spreadsheet

190 lines (162 loc) 5.5 kB
const chalk = require('chalk'); const os = require('os'); const execa = require('execa'); const logTransformer = require('strong-log-transformer'); // bookkeeping for spawned processes const children = new Set(); /** * Execute a command asynchronously, piping stdio by default. * @param {string} command * @param {string[]} execArgs * @param {import("execa").Options} [opts] * @param {Boolean} [cmdDryRun] */ function exec(command, execArgs, execOpts, cmdDryRun) { const options = Object.assign({ stdio: 'pipe' }, execOpts); const spawned = spawnProcess(command, execArgs, options, cmdDryRun); return cmdDryRun ? Promise.resolve() : wrapError(spawned); } /** * Execute and stream a command asynchronously, piping stdio by default. * @param {string} command * @param {string[]} execArgs * @param {import("execa").Options} [opts] * @param {Boolean} [cmdDryRun] */ function execStreaming(command, execArgs, execOpts, cmdDryRun) { const options = Object.assign({}, execOpts); options.stdio = ['ignore', 'pipe']; const spawned = spawnStreaming(command, execArgs, options, '', cmdDryRun); return cmdDryRun ? Promise.resolve() : wrapError(spawned); } /** * Execute a command synchronously. * @param {string} command * @param {string[]} args * @param {import("execa").SyncOptions} [opts] * @param {Boolean} [cmdDryRun] */ function execSync(command, args, opts, cmdDryRun = false) { return cmdDryRun ? logExecDryRunCommand(command, args) : execa.sync(command, args, opts).stdout; } /** * Log the exec command without actually executing the actual command * @param {String} command * @param {string[]} args * @returns {String} output */ function logExecDryRunCommand(command, args) { const argStr = (Array.isArray(args) ? args.join(' ') : args) ?? ''; const cmdList = []; for (const c of [command, argStr]) { cmdList.push(Array.isArray(c) ? c.join(' ') : c); } console.info(chalk.bold.magenta('[dry-run] >'), cmdList.join(' ')); return ''; } /** * @param {string} command * @param {string[]} args * @param {import("execa").Options} execOpts */ function spawnProcess( command, args, execOpts, cmdDryRun = false ) { if (cmdDryRun) { return logExecDryRunCommand(command, args); } const child = execa(command, args, execOpts); const drain = (_code, signal) => { children.delete(child); // don't run repeatedly if this is the error event if (signal === undefined) { child.removeListener('exit', drain); } }; child.once('exit', drain); child.once('error', drain); children.add(child); return child; } /** * Spawn a command asynchronously, streaming stdio with optional prefix. * @param {string} command * @param {string[]} args * @param {import("execa").Options} [opts] * @param {string} [prefix] * @param {Boolean} [cmdDryRun=false] */ // istanbul ignore next function spawnStreaming( command, args, opts, prefix, cmdDryRun = false ) { const options = Object.assign({}, opts); options.stdio = ['ignore', 'pipe']; if (cmdDryRun) { return logExecDryRunCommand(command, args); } const spawned = spawnProcess(command, args, options, cmdDryRun); const stdoutOpts = {}; const stderrOpts = {}; // mergeMultiline causes escaped newlines :P if (prefix) { const color = chalk['magenta']; stdoutOpts.tag = `${color.bold(prefix)}:`; stderrOpts.tag = `${color(prefix)}:`; } // Avoid 'Possible EventEmitter memory leak detected' warning due to piped stdio if (children.size > process.stdout.listenerCount('close')) { process.stdout.setMaxListeners(children.size); process.stderr.setMaxListeners(children.size); } spawned.stdout.pipe(logTransformer(stdoutOpts)).pipe(process.stdout); spawned.stderr.pipe(logTransformer(stderrOpts)).pipe(process.stderr); return wrapError(spawned); } // -- // private functions /** * Return the exitCode when possible or else throw an error * @param {*} result * @returns */ function getExitCode(result) { // https://nodejs.org/docs/latest-v6.x/api/child_process.html#child_process_event_close if (typeof result.code === 'number' || typeof result.exitCode === 'number') { return result.code ?? result.exitCode; } // https://nodejs.org/docs/latest-v6.x/api/errors.html#errors_error_code // istanbul ignore else if (typeof result.code === 'string' || typeof result.exitCode === 'string') { return os.constants.errno[result.code ?? result.exitCode]; } // istanbul ignore next: extremely weird throw new Error(`Received unexpected exit code value ${JSON.stringify(result.code ?? result.exitCode)}`); } /** * Spawn a command asynchronously, _always_ inheriting stdio. * @param {string} command * @param {string[]} args * @param {import("execa").Options} [opts] */ function wrapError(spawned) { return spawned.catch((err) => { // ensure exit code is always a number err.exitCode = getExitCode(err); console.error('SPAWN PROCESS ERROR'); throw err; }); } module.exports = { exec, execStreaming, execSync, spawnProcess, spawnStreaming, }