nodejs
开发命令行工具,流程绝对简略,但一套残缺的命令行程序开发流程下来,还是须要下点功夫,网上材料大多零散,这篇教程意在整合一下残缺的开发流程。
npm上命令行开发相干包很多,例如minimist
、optimist
、nopt
、commander.js
、yargs
等等,应用办法和成果相似。其中用得比拟多的是TJ大神的commander和yargs,本文以commander
为根底讲述,能够参考这篇教程,yargs教程能够参考阮大神的或者这一篇。
另外,一个残缺的命令行工具开发,还须要理解process
、shelljs
、path
、linebyline
等模块,这些都是node
根底模块或一些简略模块,非常简单,就不多说了,另外如果你不想用回调函数解决异步还须要理解一下Promise
、Generator
函数。这是教程:i5ting
大神的《深入浅出js(Node.js)异步流程管制》和阮大神的异步编程教程以及promise小人书,另外想尝试ES7 stage3阶段的async/await
异步解决方案,可参考这篇教程,async/await
解决方案须要babel
转码,这是教程。自己喜爱async/await
(哪个node
开发者不喜爱呢?)但不喜爱倒腾,况且async/await
自身就是Promise
的语法糖,所以没抉择应用,据江湖音讯,nodejs
将在往年晚些时候(10月份?)反对async/await
,很是期待。
以下是文章开端实例用到的一些依赖。
"dependencies": { "bluebird": "^3.4.1", "co": "^4.6.0", "colors": "^1.1.2", "commander": "^2.9.0", "dox": "^0.9.0", "handlebars": "^4.0.5", "linebyline": "^1.3.0", "mkdirp": "^0.5.1" }
其中bluebird
用于Promise
化,TJ大神的co
用于执行Generator
函数,handlebars
是一种模板,linebyline
用于分行读取文件,colors
用于丑化输入,mkdirp
用于创立目录,另外教程中的示例是一款工具,能够自动化生成数据库和API
接口的markdown
文档,并通过批改git hooks
,使我的项目的每次commit
都会自动更新文档,借助了TJ大神的dox
模块。
<span style="color:rgb(0, 136, 204)">所有举荐教程/教材,仅供参考,自行甄选浏览。</span>
装置Node
各操作系统下装置见Nodejs官网,装置实现之后用node -v
或者which node
等命令测试装置是否胜利。which
在命令行开发中是一个十分有用的命令,应用which
命令确保你的零碎中不存在名字雷同的命令行工具,例如which commandName
,例如which testdev
命令返回空白那么阐明testdev
命令名称还没有被应用。
初始化
- 新建一个
.js
文件,即是你的命令要执行的主程序入口文件,例如testdev.js
。在文件第一行退出#!/usr/bin/env node
指明零碎在运行这个文件的时候应用node
作为解释器,等价于node testdev.js
命令。 - 初始化
package.json
文件,应用npm init
命令依据提示信息创立,也能够是应用npm init -y
应用默认设置创立。创立实现之后须要批改package.json
文件内容退出"bin": {"testdev": "./testdev.js"}
这条信息用于通知npm
你的命令(testdev
)要执行的脚本文件的门路和名字,这里咱们指定testdev
命令的执行文件为当前目录下的testdev.js
文件。 - 为了不便测试在
testdev.js
文件中退出代码console.log('hello world');
,这里只是用于测试环境是否搭建胜利,更加简单的程序逻辑和过程须要依照理论状况进行编写
测试
应用npm link
命令,能够在本地装置刚刚创立的包,而后就能够用testdev
来运行命令了,如果失常的话在控制台会打印出hello world
commander
TJ的commander十分简洁,README.md
曾经把应用办法写的十分清晰。上面是例子中的代码:
const program = require('commander'), co = require('co');const appInfo = require('./../package.json'), asyncFunc = require('./../common/asyncfunc.js');program.allowUnknownOption();program.version(appInfo.version);program .command('init') .description('初始化当前目录doc.json文件') .action(() => co(asyncFunc.initAction));program .command('show') .description('显示配置文件状态') .action(() => co(asyncFunc.showAction));program .command('run') .description('启动程序') .action(() => co(asyncFunc.runAction));program .command('modifyhook') .description('批改我的项目下的hook文件') .action(() => co(asyncFunc.modifyhookAction));program .command('*') .action((env) => { console.error('不存在命令 "%s"', env); });program.on('--help', () => { console.log(' Examples:'); console.log(''); console.log(' $ createDOC --help'); console.log(' $ createDOC -h'); console.log(' $ createDOC show'); console.log('');});program.parse(process.argv);
定义了四个命令和个性化帮忙阐明。
交互式命令行process
commander
只是实现了命令行参数与回复一对一的固定性能,也就是一个命令必然对应一个回复,那如何实现人机交互式的命令行呢,相似npm init
或者eslint --init
这样的与用户交互,交互之后依据用户的不同需要反馈不同的后果呢。这里就须要node
内置的process
模块。
这是我实现的一个init
命令性能代码:
exports.initAction = function* () { try { var docPath = yield exists(process.cwd() + '/doc.json'); if (docPath) { func.initRepl(config.coverInit, arr => { co(newDoc(arr)); }) } else { func.initRepl(config.newInit, arr => { co(newDoc(arr)); }) } } catch (err) { console.warn(err); }
首先查看doc.json
文件是否存在,如果存在执行笼罩交互,如果不存在执行生成交互,try...catch
捕捉谬误。
交互内容配置如下:
newInit: [ { title:'initConfirm', description:'初始化createDOC,生成doc.json.确认?(y/n) ', defaults: 'y' }, { title:'defaultConfirm', description:'是否应用默认配置.(y/n) ', defaults: 'y' }, { title:'showConfig', description:'是否显示doc.json以后配置?(y/n) ', defaults: 'y' } ], coverInit:[ { title:'modifyConfirm', description:'doc.json已存在,初始化将覆盖文件.确认?(y/n) ', defaults: 'y' }, { title:'defaultConfirm', description:'是否应用默认配置.(y/n) ', defaults: 'y' }, { title:'showConfig', description:'是否显示doc.json以后配置?(y/n) ', defaults: 'y' } ],
人机交互局部代码也就是initRepl
函数内容如下:
//初始化命令,人机交互管制exports.initRepl = function (init, func) { var i = 1; var inputArr = []; var len = init.length; process.stdout.write(init[0].description); process.stdin.resume(); process.stdin.setEncoding('utf-8'); process.stdin.on('data', (chunk) => { chunk = chunk.replace(/[\s\n]/, ''); if (chunk !== 'y' && chunk !== 'Y' && chunk !== 'n' && chunk !== 'N') { console.log(config.colors.red('您输出的命令是: ' + chunk)); console.warn(config.colors.red('请输出正确指令:y/n')); process.exit(); } if ( (init[i - 1].title === 'modifyConfirm' || init[i - 1].title === 'initConfirm') && (chunk === 'n' || chunk === 'N') ) { process.exit(); } var inputJson = { title: init[i - 1].title, value: chunk, }; inputArr.push(inputJson); if ((len--) > 1) { process.stdout.write(init[i++].description) } else { process.stdin.pause(); func(inputArr); } });}
人机交互才用向用户发问依据用户不同输出产生不同后果的模式进行,程序读取发问列表并记录用户输出后果,如果用户输出n/N
则终止交互,用户输出非法字符(除y/Y/n/N
以外)提醒输出命令谬误。
文档自动化
文档自动化,其中数据库文档自动化,才用依赖sequelize
的办法手写(依据需要不同自行编写逻辑),API
文档才用TJ的dox也很简略。因为此处代码与命令行性能相关度不大,请读者自行去示例地址查看代码。
示例地址
github地址
npm地址