从 1 到完美,用 node 写一个命令行工具

1. package.json 中的 bin 字段

现在,不管是前端项目还是 node 项目,一般都会用 npm 做包管理工具,而 package.json 是其相关的配置信息。

node 项目而言,模块导出入口文件由 package.jsonmain 字段指定,而如果是要安装到命令行的工具,则是由package.jsonbin 字段指定。

1.1 配置单个命令

与包名同名
{
  "name": "pro",
  "bin": "bin/pro.js"
}

这样安装的命令名称就是 pro

自定义命令名称(与包名不同名)
{
  "name": "pro-cli",
  "bin": {
    "pro": "bin/pro.js"
  }
}

这样安装的命令名称也是 pro

1.2 配置多个命令

{
  "name": "pro-cli",
  "bin": {
    "pro": "bin/pro.js",
    "mini": "bin/mini.js"
  }
}

这样安装就有 promini 两个命令。

2. 对应 bin/pro.js 文件的写法

#!/usr/bin/env node

require('../lib/pro');

与普通的 js 文件写法一样,只是前面要加上 #!/usr/bin/env node

这段前缀代码叫 shebang,具体可以参考 Shebang (Unix) - Wikipedia.

3. 安装方式

3.1 全局安装
npm i -g pro-cli

这种安装方式可以在命令行全局使用。

pro dev

pro build

3.2 本地安装

npm i --save-dev pro-cli

这种安装方式需要配合 npm 一起使用,比如:

# package.json
{
  "scripts": {
    "dev": "pro dev",
    "build": "pro build"
  }
}

# 使用
npm run dev
npm run build

4. 选择合适的命令行封装库

一般来说,一个命令都会有如下的一些参数:

  • -v, --version-V, --version: 查看版本号
  • -h, --help: 查看帮助信息

如果完全自己来写的,就会很麻烦,尤其是帮助信息。所以,选择一个好的命令行封装库,能够帮我们省去很多工作。

用的比较多的:

commander.js 为例:

4.1 安装
npm install commander --save
4.2 注册
const commander = require('commander');

注册版本号与描述

commander
  .version('0.0.1')
  .description('A cli application named pro');

注册参数(非子命令参数)

commander
  .option('-p, --peppers', 'Add peppers')
  .option('-P, --pineapple', 'Add pineapple')
  .option('-b, --bbq-sauce', 'Add bbq sauce')
  .option('-c, --cheese [type]', 'Add the specified type of cheese [marble]', 'marble')

注册子命令

commander
  .command('rm <dir>')
  .option('-r, --recursive', 'Remove recursively')
  .action((dir, cmd) => {
    console.log('remove ' + dir + (cmd.recursive ? ' recursively' : ''))
  })

解析

commander.parse(process.argv);
4.3 使用

查看版本号

pro -V
pro --version

# 打印结果
0.0.1

运行 rm 子命令

pro rm dir

查看帮助(commander 会自动生成)

pro -h
pro --help

# 打印结果
Usage: pro [options]

A cli application named pro

Options:
  -h, --help           output usage information
  -V, --version        output the version number
  -p, --peppers        Add peppers
  -P, --pineapple      Add pineapple
  -b, --bbq            Add bbq sauce
  -c, --cheese <type>  Add the specified type of cheese [marble]
  -C, --no-cheese      You do not want any cheese

更多用法查看 commander.js

5. 常用的命令行相关工具库

5.1 minimist: 解析命令行的参数
var argv = require('minimist')(process.argv.slice(2));
console.dir(argv);
$ node example/parse.js -a beep -b boop
{ _: [], a: 'beep', b: 'boop' }
$ node example/parse.js -x 3 -y 4 -n5 -abc --beep=boop foo bar baz
{ _: [ 'foo', 'bar', 'baz' ],
  x: 3,
  y: 4,
  n: 5,
  a: true,
  b: true,
  c: true,
  beep: 'boop' }

更多参考 minimist

5.2 chalk: 让命令行的字符带上颜色

更多参考 chalk

5.3 Inquirer.js: 让命令行与用户进行交互,如输入、选择等

更多参考 Inquirer.js

5.4 shelljs: 跨平台 Unix shell 命令 的 node 封装
var shell = require('shelljs');

if (!shell.which('git')) {
  shell.echo('Sorry, this script requires git');
  shell.exit(1);
}

// Copy files to release dir
shell.rm('-rf', 'out/Release');
shell.cp('-R', 'stuff/', 'out/Release');

// Replace macros in each .js file
shell.cd('lib');
shell.ls('*.js').forEach(function (file) {
  shell.sed('-i', 'BUILD_VERSION', 'v0.1.2', file);
  shell.sed('-i', /^.*REMOVE_THIS_LINE.*$/, '', file);
  shell.sed('-i', /.*REPLACE_LINE_WITH_MACRO.*\n/, shell.cat('macro.js'), file);
});
shell.cd('..');

// Run external tool synchronously
if (shell.exec('git commit -am "Auto-commit"').code !== 0) {
  shell.echo('Error: Git commit failed');
  shell.exit(1);
}

更多参考 shelljs

5.5 blessed-contrib: 命令行图表

更多参考 blessed-contrib

5.6 cash: 跨平台 linux 命令 的 node 封装

shelljs 功能差不多。

const $ = require('cash');
const out = $.ls('.', {l: true});

更多参考 cash

5.7 prompts: 又一个让命令行与用户进行交互的工具

Inquirer.js 功能差不多。

更多参考 prompts

5.8 ora: 命令行加载中图标

更多参考 ora

5.9 progress: 命令行进度条
downloading [===== ] 39/bps 29% 3.7s

更多参考 progress

5.10 更多

更多关于命令行的工具库可以参考 command-line-utilities

6. 比较常用的命令行 APP

命令行相关的应用就很多啦,比如 babelwebpackrollupeslint 等,但这些不仅仅是命令行工具。

下面介绍一些纯命令行应用:

更多纯命令行应用可以参考 command-line-apps

后续

更多博客,查看 https://github.com/senntyou/blogs
转载:作者:深予之 (@senntyou)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容