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; };