| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189 | 'use strict';const debug = require('debug')('common-bin');const cp = require('child_process');const is = require('is-type-of');const unparse = require('dargs');// only hook once and only when ever start any child.const childs = new Set();let hadHook = false;function gracefull(proc) {  // save child ref  childs.add(proc);  // only hook once  /* istanbul ignore else */  if (!hadHook) {    hadHook = true;    let signal;    [ 'SIGINT', 'SIGQUIT', 'SIGTERM' ].forEach(event => {      process.once(event, () => {        signal = event;        process.exit(0);      });    });    process.once('exit', () => {      // had test at my-helper.test.js, but coffee can't collect coverage info.      for (const child of childs) {        debug('kill child %s with %s', child.pid, signal);        child.kill(signal);      }    });  }}/** * fork child process, wrap with promise and gracefull exit * @function helper#forkNode * @param {String} modulePath - bin path * @param {Array} [args] - arguments * @param {Object} [options] - options * @return {Promise} err or undefined * @see https://nodejs.org/api/child_process.html#child_process_child_process_fork_modulepath_args_options */exports.forkNode = (modulePath, args = [], options = {}) => {  options.stdio = options.stdio || 'inherit';  debug('Run fork `%s %s %s`', process.execPath, modulePath, args.join(' '));  const proc = cp.fork(modulePath, args, options);  gracefull(proc);  const promise = new Promise((resolve, reject) => {    proc.once('exit', code => {      childs.delete(proc);      if (code !== 0) {        const err = new Error(modulePath + ' ' + args + ' exit with code ' + code);        err.code = code;        reject(err);      } else {        resolve();      }    });  });  promise.proc = proc;  return promise;};/** * spawn a new process, wrap with promise and gracefull exit * @function helper#forkNode * @param {String} cmd - command * @param {Array} [args] - arguments * @param {Object} [options] - options * @return {Promise} err or undefined * @see https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options */exports.spawn = (cmd, args = [], options = {}) => {  options.stdio = options.stdio || 'inherit';  debug('Run spawn `%s %s`', cmd, args.join(' '));  return new Promise((resolve, reject) => {    const proc = cp.spawn(cmd, args, options);    gracefull(proc);    proc.once('error', err => {      /* istanbul ignore next */      reject(err);    });    proc.once('exit', code => {      childs.delete(proc);      if (code !== 0) {        return reject(new Error(`spawn ${cmd} ${args.join(' ')} fail, exit code: ${code}`));      }      resolve();    });  });};/** * exec npm install * @function helper#npmInstall * @param {String} npmCli - npm cli, such as `npm` / `cnpm` / `npminstall` * @param {String} name - node module name * @param {String} cwd - target directory * @return {Promise} err or undefined */exports.npmInstall = (npmCli, name, cwd) => {  const options = {    stdio: 'inherit',    env: process.env,    cwd,  };  const args = [ 'i', name ];  console.log('[common-bin] `%s %s` to %s ...', npmCli, args.join(' '), options.cwd);  return exports.spawn(npmCli, args, options);};/** * call fn * @function helper#callFn * @param {Function} fn - support generator / async / normal function return promise * @param {Array} [args] - fn args * @param {Object} [thisArg] - this * @return {Object} result */exports.callFn = function* (fn, args = [], thisArg) {  if (!is.function(fn)) return;  if (is.generatorFunction(fn)) {    return yield fn.apply(thisArg, args);  }  const r = fn.apply(thisArg, args);  if (is.promise(r)) {    return yield r;  }  return r;};/** * unparse argv and change it to array style * @function helper#unparseArgv * @param {Object} argv - yargs style * @param {Object} [options] - options, see more at https://github.com/sindresorhus/dargs * @param {Array} [options.includes] - keys or regex of keys to include * @param {Array} [options.excludes] - keys or regex of keys to exclude * @return {Array} [ '--debug=7000', '--debug-brk' ] */exports.unparseArgv = (argv, options = {}) => {  // revert argv object to array  // yargs will paser `debug-brk` to `debug-brk` and `debugBrk`, so we need to filter  return [ ...new Set(unparse(argv, options)) ];};/** * extract execArgv from argv * @function helper#extractExecArgv * @param {Object} argv - yargs style * @return {Object} { debugPort, debugOptions: {}, execArgvObj: {} } */exports.extractExecArgv = argv => {  const debugOptions = {};  const execArgvObj = {};  let debugPort;  for (const key of Object.keys(argv)) {    const value = argv[key];    // skip undefined set uppon (camel etc.)    if (value === undefined) continue;    // debug / debug-brk / debug-port / inspect / inspect-brk / inspect-port    if ([ 'debug', 'debug-brk', 'debug-port', 'inspect', 'inspect-brk', 'inspect-port' ].includes(key)) {      if (typeof value === 'number') debugPort = value;      debugOptions[key] = argv[key];      execArgvObj[key] = argv[key];    } else if (match(key, [ 'es_staging', 'expose_debug_as', /^harmony.*/ ])) {      execArgvObj[key] = argv[key];    } else if (key.startsWith('node-options--')) {      // support node options, like: commond --node-options--trace-warnings => execArgv.push('--trace-warnings')      execArgvObj[key.replace('node-options--', '')] = argv[key];    }  }  return { debugPort, debugOptions, execArgvObj };};function match(key, arr) {  return arr.some(x => x instanceof RegExp ? x.test(key) : x === key); // eslint-disable-line no-confusing-arrow}
 |