从零开发一个node命令行工具

开发 前端
本文列举的只是 cli 开发的冰山一角,想要开发出强大的 cli 工具,除了需要熟悉 node 和常用工具包,更重要的是了解 linux 常用命令和文件系统。

[[267652]]

什么是命令行工具?

命令行工具(Cmmand Line Interface)简称cli,顾名思义就是在命令行终端中使用的工具。我们常用的 git 、npm、vim 等都是 cli 工具,比如我们可以通过 git clone 等命令简单把远程代码复制到本地。

为什么要用cli工具?

和 cli 相对的是图形用户界面(gui),windows 环境中几乎都是 gui 工具,而 linux 环境中则几乎都是 cli 工具,因为两者用户不同,gui 侧重于易用,cli 则侧重于效率。对于熟悉 gui 和集成开发环境(IDE)的程序员,这似乎很难理解。毕竟用鼠标点点拽拽,不是更方便么?

很遗憾,答案是否定的。gui对于某些简单操作,可能更快、更方便。比如移动文件、阅读邮件或写word文档。但如果你依赖 gui 完成全部工作,你将会错过环境的某些能力,比如使常见任务自动化,或是利用各种工具的全部功能。并且,你也无法将工具组合,创建出定制的宏工具。gui 的好处是所见即所得(what you see is what you get)。缺点是所见即全部所得(what you see is all you get)。

作为注重实效的程序员,你不断的想要执行特别的操作(gui 可能不支持的操作)。当你想要快速地组合一些命令,以完成一次查询或某种其他的任务时,cli 要更为合适。比如:查看上周哪些js文件没有改动过: 

  1. # cli:  
  2. find . -name '*.js' -mtime +7 -print  
  3. # gui:  
  4. 1.点击并转到"查找文件",点击"文件名"字段,敲入"*.js",选择"修改日期"选项卡;  
  5. 2.然后选择"介于".点击"开始日期",敲入项目开始的日期。  
  6. 3.点击"结束日期",敲入1周以前的日期(确保手边有日历),点击"开始查找"; 

如何开发一个 cli 工具?

基本上,使用任何成熟的语言都可以开发 cli 工具,作为一个前端小白,还是 JavaScript 比较顺手,因此我们选用 node 作为开发语言。

创建一个项目 

  1. # 1.创建一个目录:  
  2. mkdir kid-cli && cd kid-cli  
  3. # 2.因为最终我们要把cli发布到npm上,所以需要初始化一个程序包:   
  4. npm init  
  5. # 3.创建一个index.js文件  
  6. touch index.js  
  7. # 4.打开编辑器(vscode)  
  8. code  . 

安装 code 命令,运行 VS code 并打开命令面板( ⇧⌘P ),然后输入 shell command 找到: Install 'code' command in PATH 就行了。

打开index.js文件,添加一段测试代码: 

  1. #!/usr/bin/env node  
  2. console.log('hello world!’) 

终端运行 node 程序,需要先输入 node 命令,比如 

  1. node index.js 

可以正确输出 hello world!,代码顶部的 #!/usr/bin/env node是告诉终端,这个文件要使用 node 去执行。

创建一个命令

一般 cli都有一个特定的命令,比如 git,刚才使用的 code 等,我们也需要设置一个命令,就叫 kid 吧!如何让终端识别这个命令呢?很简单,打开 package.json 文件,添加一个字段 bin,并且声明一个命令关键字和对应执行的文件: 

  1. # package.json  
  2. ......  
  3.   "bin": {  
  4.     "kid": "index.js"  
  5.   },  
  6. ...... 

如果想声明多个命令,修改这个字段就好了。

然后我们测试一下,在终端中输入 kid,会提示: 

  1. zsh: command not found: kid 

为什么会这样呢?回想一下,通常我们在使用一个 cli 工具时,都需要先安装它,比如 vue-cli,使用前需要全局安装: 

  1. npm i vue-cli -g 

而我们的 kid-cli 并没有发布到 npm 上,当然也没有安装过了,所以终端现在还不认识这个命令。通常我们想本地测试一个 npm 包,可以使用:npm link 这个命令,本地安装这个包,我们执行一下: 

  1. npm link 

然后再执行 

  1. kid 

命令,看正确输出 hello world! 了。

到此,一个简单的命令行工具就完成了,但是这个工具并没有任何卵用,别着急,我们来一点一点增强它的功能。

查看版本信息

首先是查看 cli 的版本信息,希望通过如下命令来查看版本信息: 

  1. kid -v 

这里有两个问题

  1.  如何获取 -v 这参数?
  2.  如何获取版本信息?

在 node 程序中,通过 process.argv 可获取到命令的参数,以数组返回,修改 index.js,输出这个数组: 

  1. console.log(process.argv) 

然后输入任意命令,比如: 

  1. kid -v -h -lalala 

控制台会输出 

  1. [ '/Users/shaolong/.nvm/versions/node/v8.9.0/bin/node',  
  2.   '/Users/shaolong/.nvm/versions/node/v8.9.0/bin/kid',  
  3.   '-v',  
  4.   '-h',  
  5.   '-lalala' ] 

这个数组的第三个参数就是我们想要的 -v。

第二个问题,版本信息一般是放在package.json 文件的 version 字段中, require 进来就好了,改造后的 index.js 代码如下: 

  1. #!/usr/bin/env node  
  2. const pkg = require('./package.json')  
  3. const command = process.argv[2]  
  4. switch (command) {  
  5.     case '-v':  
  6.     console.log(pkg.version)  
  7.     break  
  8.     default:  
  9.     break  

然后我们再执行kid -v,就可以输出版本号了。

初始化一个项目

接下来我们来实现一个最常见的功能,利用 cli 初始化一个项目。

整个流程大概是这样的:

  1.  cd 到一个你想新建项目的目录;
  2.  执行 kid init 命令,根据提示输入项目名称;
  3.  cli 通过 git 拉取模版项目代码,并拷贝到项目名称所在目录中;

为了实现这个流程,我们需要解决下面几个问题:

执行复杂的命令

上面的例子中,我们通过 process.argv 获取到了命令的参数,但是当一个命令有多个参数,或者像新建项目这种需要用户输入项目名称(我们称作“问答”)的命令时,一个简单的swith case 就显得捉襟见肘了。这里我们引用一个专门处理命令行交互的包:commander。 

  1. npm i commander --save 

然后改造index.js 

  1. #!/usr/bin/env node  
  2. const program = require('commander')  
  3. program.version(require('./package.json').version)  
  4. program.parse(process.argv) 

运行 

  1. kid -h 

会输出 

  1. Usage: kid [options] [command]  
  2. Options:  
  3.   -V, --version  output the version number  
  4.   -h, --help     output usage information 

commander已经为我们创建好了帮助信息,以及两个参数 -V 和 -h,上面代码中的program.version 就是返回版本号,和之前的功能一致,program.parse 是将命令参数传入commander 管道中,一般放在***执行。

添加问答操作

接下来我们添加 kid init 的问答操作,这里有需要引入一个新的包:inquirer, 这个包可以通过简单配置让 cli 支持问答交互。 

  1. npm i inquirer --save 

index.js: 

  1. #!/usr/bin/env node  
  2. const program = require('commander')  
  3. var inquirer = require('inquirer')  
  4. const initAction = () => {  
  5.     inquirer.prompt([{  
  6.         type: 'input',  
  7.         message: '请输入项目名称:',  
  8.         name: 'name'  
  9.     }]).then(answers => {  
  10.         console.log('项目名为:', answers.name)  
  11.         console.log('正在拷贝项目,请稍等')  
  12.     })  
  13.  
  14. program.version(require('./package.json').version) 
  15. program  
  16.     .command('init')  
  17.     .description('创建项目')  
  18.     .action(initAction)  
  19. program.parse(process.argv) 

program.command 可以定义一个命令,description 添加一个描述,在 --help 中展示,action 指定一个回调函数执行命令。inquirer.prompt 可以接收一组问答对象,type字段表示问答类型,name 指定答案的key,可以在 answers 里通过 name 拿到用户的输入,问答的类型有很多种,这里我们使用 input,让用户输入项目名称。

运行 kid init,然后会提示输入项目名称,输入后会打印出来。

运行 shell 脚本

熟悉 git 和 linux 的同学几句话便可以初始化一个项目: 

  1. git clone xxxxx.git --depth=1  
  2. mv xxxxx my-project  
  3. rm -rf ./my-project/.git  
  4. cd my-project  
  5. npm i 

那么如何在 node 中执行 shell 脚本呢?只需要安装 shelljs 这个包就可以轻松搞定。 

  1. npm i shelljs --save 

假定我们想克隆 github 上 vue-admin-template 这个项目的代码,并自动安装依赖,改造index.js,在 initAction 函数中加上执行shell脚本的逻辑: 

  1. #!/usr/bin/env node  
  2. const program = require('commander')  
  3. const inquirer = require('inquirer')  
  4. const shell = require('shelljs')  
  5. const initAction = () => {  
  6.     inquirer.prompt([{  
  7.         type: 'input',  
  8.         message: '请输入项目名称:',  
  9.         name: 'name'  
  10.     }]).then(answers => {  
  11.         console.log('项目名为:', answers.name)  
  12.         console.log('正在拷贝项目,请稍等')        
  13.         const remote = 'https://github.com/PanJiaChen/vue-admin-template.git'  
  14.         const curName = 'vue-admin-template'  
  15.         const tarName = answers.name  
  16.         shell.exec(`  
  17.                 git clone ${remote} --depth=1  
  18.                 mv ${curName} ${tarName}  
  19.                 rm -rf ./${tarName}/.git  
  20.                 cd ${tarName}  
  21.                 npm i  
  22.               `, (error, stdout, stderr) => {  
  23.             if (error) {  
  24.                 console.error(`exec error: ${error}`)  
  25.                 return  
  26.             }  
  27.             console.log(`${stdout}`)  
  28.             console.log(`${stderr}`)  
  29.         });  
  30.     })  
  31.  
  32. program.version(require('./package.json').version)  
  33. program  
  34.     .command('init')  
  35.     .description('创建项目')  
  36.     .action(initAction)  
  37. program.parse(process.argv) 

shell.exec 可以帮助我们执行一段脚本,在回调函数中可以输出脚本执行的结果。

测试一下我们初始化功能: 

  1. cd ..  
  2. kid init  
  3. # 输入一个项目名称 

可以看到,cli已经自动从github上拉取vue-admin-template的代码,放在指定目录,并帮我们自动安装了依赖。

尾声

***别忘了将你的 cli 工具发布到 npm 上,给更多的同学使用。 

  1. npm publish 

怎么样,是不是感觉看似神秘的命令行开发其实也没有什么技术含量,上文列举的只是 cli 开发的冰山一角,想要开发出强大的 cli 工具,除了需要熟悉 node 和常用工具包,更重要的是了解 linux 常用命令和文件系统,希望各位同学可以受到启发,开发出属于自己的 cli 工具。

安利时间

前端的技术点众多,其中不乏抽象且晦涩的知识点,它们用文字无法很直观的表述出来,所以众多开发者对这些知识点的理解都是是而非,如果我们通过图画来展示,就会很容易理解。因此Diagram项目希望开发者能通过这种方式吃透前端技术领域的知识点。 

责任编辑:庞桂玉 来源: segmentfault
相关推荐

2021-02-02 10:15:55

工具命令行Node

2019-05-30 10:40:04

ddgrLinuxDuckDuckGo

2011-06-17 16:49:05

Cocoa苹果

2016-08-10 12:41:00

Linux工具bcShell

2020-12-10 16:16:08

工具代码开发

2020-12-11 06:44:16

命令行工具开发

2020-12-08 08:46:07

GoJava工具

2021-04-01 13:25:46

Node命令工具

2022-02-17 18:21:47

工具HTTPie客户端

2018-05-04 09:15:35

PythonPlumbum命令行

2015-07-15 10:32:44

Node.js命令行程序

2016-09-23 20:16:23

TaskwarriorLinux命令行工具

2018-06-12 15:10:11

Linuxvim命令PacVim

2018-11-21 09:57:44

命令行Linux文件

2014-08-25 16:23:24

2018-07-05 08:30:54

Python命令行工具shell

2022-01-26 18:59:08

Python工具

2021-05-17 10:32:10

命令行工具GitHub代码

2019-04-04 11:00:40

iWant命令行工具开源

2022-01-11 09:05:07

工具Python 命令行
点赞
收藏

51CTO技术栈公众号