common-bin

common-bin基于commander。commander以设置回调函数的形式运行命令行脚本;common-bin获取待运行文件后,执行该文件。

官方示例:https://github.com/node-modules/common-bin/blob/master/README.md

源码:

run.js创建commander实例,监听Program程序脚本文件下添加的子命令Command。

'use strict';

const commander = require('commander');

module.exports = Program => {
  process.env.NODE_ENV = process.env.NODE_ENV || 'development';
  const program = new Program();

  // commander.version注册版本号,监听version事件;命令行输入-v时由option发起version事件
  // commander.usage添加使用使用说明,--help打印帮助时显示用
  commander
    .version(program.version)
    .usage('[command] [options]');

  // commander.command注册子命令,返回该子命令Commander实例subCommander;子命令不存在时,执行"*"注册的子命令
  // commander.description添加子命令的描述
  // commander.action监听子命令,回调中参数cmd为子命令的名称
  commander
    .command('*')
    .description('See "More commands"')
    .action(cmd => {
      const args = process.argv.slice(3);
      program.onAction(cmd, process.cwd(), args);
    });

  // 监听--help命令行指令,并调用program.help方法打印帮助文档
  commander.on('--help', () => {
    program.help();
  });

  // 命令行执行只有文件名,无子命令时,由commander.outputHelp触发"--help"事件
  if (!process.argv.slice(2).length) {
    commander.outputHelp();
  }

  // 由命令行参数获取子命令名称cmd,并触发cmd事件,调用对应的action绑定事件回调
  commander.parse(process.argv);
};

 

program.js创建自定义程序脚本文件Program的基类。

'use strict';

const co = require('co');
const chalk = require('chalk');

class Program {
  constructor() {
    // 以键值对存储子命令及其对应的待执行脚本
    this._commands = {};

    this.version = require('../package.json').version;
  }

  // 添加子命令及其关联的待执行脚本文件,该脚本文件下必须包含run和help方法
  // run方法为实际启动接口,help方法用于打印帮助文档
  addCommand(cmd, filepath) {
    this._commands[cmd] = filepath;
  }

  // 在commander实例使用action方法监听事件时使用;加载待执行脚本文件,并异步执行其run方法
  onAction(cmd, cwd, args) {
    const filepath = this._commands[cmd];
    if (!filepath) {
      this.help();
      return;
    }
    co(function* () {
      const Command = require(filepath);
      yield new Command().run(cwd, args);
    }).catch(err => {
      console.error('[common-bin] run %s with %j at %s error:', cmd, args, cwd);
      console.error(chalk.red(err.stack));
      process.exit(1);
    });
  }

  // 通过待执行脚本文件的help方法打印帮助文档;默认在commander实例的"--help"事件中调用,即用户输入"--help"
  help() {
    const keys = Object.keys(this._commands);
    if (!keys.length) return;

    console.log('  More commands');
    console.log('');
    for (const cmd of keys) {
      const Command = require(this._commands[cmd]);
      console.log('    %s - %s', cmd, new Command().help());
    }
    console.log('');
  }
}

module.exports = Program;

 

command.js创建待执行脚本文件Command的基类。

'use strict';

const helper = require('./helper');

// 待执行脚本的构造函数,并提供helper属性
// helper属性含有forkNode方法调用模块并执行,npmInstall执行命令,getIronNodeBin方法安装iron-node
class Command {
  constructor() {
    this.helper = helper;
  }

  run(/* cwd, args */) {
    throw new Error('Must impl this method');
  }

  help() {
    throw new Error('Must impl this method');
  }
}

module.exports = Command;

 

扫描二维码关注公众号,回复: 308109 查看本文章

helper.js待执行脚本Command含有的帮助方法。

'use strict';

const debug = require('debug')('common-bin');
const path = require('path');
const cp = require('child_process');
const fs = require('fs');
const assert = require('assert');

const childs = new Set();

// process.kill(process.pid, 'SIGINT' | 'SIGQUIT' | 'SIGTERM')终止进程时可约定发送何种信号
// 监听SIGINT事件;接收到系统信号SIGINT时触发,主要是用户按Ctrl + c时触发
process.once('SIGINT', () => process.exit());
process.once('SIGQUIT', () => process.exit());
// SIGTERM事件由内核要求当前进程停止
process.once('SIGTERM', () => process.exit());
// 主进程终止后,结束运行中的子进程
process.once('exit', () => {
  for (const child of childs) {
    child.kill();
  }
});

// 以子进程执行文件路径为modulePath的脚本文件,返回值用于设定子进程终止时的回调函数
exports.forkNode = (modulePath, args, opt) => {
  opt = opt || {};
  opt.stdio = opt.stdio || 'inherit';
  args = args || [];
  debug('Run fork `%j %j %j`', process.execPath, modulePath, args.join(' '));

  // cp.fork(modulePath,args,opt)以子进程执行模块路径为modulePath的脚本文件
  // args为参数列表,opt为环境变量对象
  const proc = cp.fork(modulePath, args, opt);

  childs.add(proc);
  return function(cb) {
    proc.once('exit', function(code) {
      childs.delete(proc);
      if (code !== 0) {
        const err = new Error(modulePath + ' ' + args + ' exit with code ' + code);
        err.code = code;
        cb(err);
      } else {
        cb();
      }
    });
  };
};

// 以子进程执行npmCli命令,对返回值传入函数后才予以执行
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 callback => {
    // cp.spawn(cmd,args,opts)以子进程执行cmd命令
    const proc = cp.spawn(npmCli, args, options);
    proc.on('error', err => {
      const cb = callback;
      callback = null;
      cb(err);
    });
    proc.on('exit', code => {
      if (!callback) return;

      if (code !== 0) {
        return callback(new Error('npm ' + args.join(' ') + ' fail, exit code: ' + code));
      }
      callback();
    });
  };
};

// iron-node提供chrome调试node代码的功能
exports.getIronNodeBin = function* (npmCli, cwd) {
  const nodeBin = path.join(cwd, 'node_modules/iron-node/bin/run.js');
  let exists;
  try {
    exists = !!fs.statSync(nodeBin);
  } catch (_) {
    // not exists
    exists = false;
  }
  if (exists) {
    return nodeBin;
  }
  const packageName = 'iron-node@3';
  try {
    yield exports.npmInstall(npmCli, packageName, cwd);
  } catch (err) {
    if (err.code !== 'ENOENT') {
      throw err;
    }
    console.warn('[common-bin] `%s` not exists, use npm and try again', npmCli);
    // use npm and try again
    yield exports.npmInstall('npm', packageName, cwd);
  }

  assert(!!fs.statSync(nodeBin), `${nodeBin} not exists, please run "npm i ${packageName}"`);
  return nodeBin;
};

猜你喜欢

转载自schifred.iteye.com/blog/2361664