![e26244ce6f34ea488133bcc5511496a7.png](https://i-blog.csdnimg.cn/blog_migrate/fb1990b989ad74f73ce72fe0eda31d03.png)
1 前言
本文面向有JavaScript开发经验且希望入门CLI工具制作的开发人员。文中没有太多理论知识,通过从0到1开发一个简单的CLI工具,分享了CLI工具的开发、发布流程。看完本文,你可以学会如何"私人定制"一个CLI工具来提升开发效率。
总之,简单,实用,有趣。
2 什么是CLI工具
维基百科是这样定义CLI的
CLI(Command-line Interface),命令行界面,指通过输入字符组成的命令行进行操作的用户界面。
常见的CLI工具举例
- 系统命令:cd、mkdir、ifconfig
- 脚手架工具:create-react-app、vue-cli、yeoman
- 预处理器:less、sass、babel
- 测试工具:mocha、karma、wrk
- 构建工具:webpack、gulp、grunt
3 为什么要自己开发CLI工具
举例,如果想查看本机ip地址,通常我会使用ifconfig
命令,如图
![c6442f9ccbd2d9b6bb0e2980462b2a37.png](https://i-blog.csdnimg.cn/blog_migrate/8a445cc50e2492cbea6c72e5226bc24e.jpeg)
执行命令后系统输出了一堆信息,从这堆信息中找到ip地址大概需要花5秒时间,这不是我想要的。我希望的结果是这样的
![0a6f76bf91a36f06b7fe81aa626b4626.png](https://i-blog.csdnimg.cn/blog_migrate/6a206ef2e4eb8f33a8fcba2b823f27f9.png)
输入ip
命令,直接得到本机ip地址,1秒搞定,简单快捷。
理想很丰满,现实很骨感。真实的情况是:当我在终端敲下ip
命令时,系统给我的反馈是"command not found:ip"。
![4d80db53824d20159f4050183fb53bcc.png](https://i-blog.csdnimg.cn/blog_migrate/d087182c5c0496aecd8ec47853cc022c.png)
很显然,系统并没有为我提供ip
这个命令,所以我们需要自己来实现它。
4 开始开发CLI工具
4.1 第一步:确保安装了Node.js
![bf8f4aebbe833bca28e58f48b8a53f86.png](https://i-blog.csdnimg.cn/blog_migrate/712ff1352bb8c6b339b69a8fee946e57.png)
4.2 第二步:编写脚本、测试运行
创建文件夹ip-cli
,并新建index.js
mkdir ip-cli && cd ip-cli
touch index.js
编辑index.js
,内容如下
const os = require("os")
const ip = os.networkInterfaces().en0[1].address
const options = process.argv.slice(2)
if (options[0] === '-v') {
console.log('v1.0.0')
} else {
console.log(`your ip is: ${ip}`)
}
执行node index.js
,结果如下
![1c9a719246a3d0562a3d7e622b8168b1.png](https://i-blog.csdnimg.cn/blog_migrate/a3e8c8da931d550e7fc793dbba4e3dee.png)
4.3 声明执行环境
执行which env
![9ce25bf020b015645e74f5fc07e9ca0a.png](https://i-blog.csdnimg.cn/blog_migrate/d16b2b4b361972cf0f0d7843735df94d.png)
将返回的结果设置到index.js
脚本的第一行,并指定脚本的解释器为node
。
然后,代码就变成了下面这样
#!/usr/bin/env node
const os = require("os")
const ip = os.networkInterfaces().en0[1].address
const options = process.argv.slice(2)
if (options[0] === '-v') {
console.log('v1.0.0')
} else {
console.log(`your ip is: ${ip}`)
}
代码中的#!
被称为Shebang
,是用来告诉编译器用什么命令执行文件的。Shebang
的一些具体用法罗列如下
1、如果脚本文件中没有#!
这一行,那么执行时会默认采用当前Shell去解释这个脚本(即:$SHELL
环境变量)。
2、如果#!
之后的解释程序是一个可执行文件,那么执行这个脚本时,它就会把文件名及其参数一起作为参数传给那个解释程序去执行。
3、如果#!
指定的解释程序没有可执行权限,则会报错“bad interpreter: Permission denied”。如果#!
指定的解释程序不是一个可执行文件,那么指定的解释程序会被忽略,转而交给当前的SHELL去执行这个脚本。
4、如果#!
指定的解释程序不存在,那么会报错“bad interpreter: No such file or directory”。注意:#!
之后的解释程序,需要写其绝对路径(如:#!/bin/bash
),它是不会自动到$PATH
中寻找解释器的。
5、当然,如果你使用类似于”bash test.sh”这样的命令来执行脚本,那么#!
这一行将会被忽略掉,解释器当然是用命令行中显式指定的bash。
6、脚本文件必须拥有可执行权限。
env
可以在系统的PATH
目录中查找脚本解释器安装目录。
添加#!/usr/bin/env node
是告诉系统,这个脚本使用Node.js
来执行。这样我们就可以简化命令,执行index.js
直接得到ip地址,不需要显式的调用node index.js
。
4.4 分配执行权限
在我们直接执行index.js
后,结果如下
![4dc8ad49797b539083f21d2da59f30c5.png](https://i-blog.csdnimg.cn/blog_migrate/452d764067c4902b3427e024ba0e9025.png)
我们并没有得到ip地址,查看文件发现index.js
没有可执行权限。
![d1958c02ee90ab0c96fc7c5f980d1a83.png](https://i-blog.csdnimg.cn/blog_migrate/8488c23684f7477a13823c2b3a42010f.png)
执行chmod +x index.js
给脚本分配可执行权限,然后index.js
就可以执行了。
![12de7f314d62ffd6328b0e60dbf001ca.png](https://i-blog.csdnimg.cn/blog_migrate/6c738a1e08d3399300551674691a830b.jpeg)
4.5 设置环境变量
执行$PATH
查看环境变量
![c2a723d1a8cc35eacbcaf1efe179c904.png](https://i-blog.csdnimg.cn/blog_migrate/cc74b339a40926f9ceb28a6b0d9e40d9.png)
可以看到我电脑的PATH
变量为
/usr/local/mongodb/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:“/usr/libexec/java_home”::/platform-tools:/tools:“/usr/libexec/java_home”:“/usr/libexec/java_home”/bin:/usr/local/mongodb/bin:/Users/qianxuemin/bin/
这里usr
指Unix System Resource
,而不是User
, 通常/usr/bin
下面的都是系统预装的可执行程序,会随着系统升级而改变, /usr/local/bin
目录是给用户放置自己的可执行程序的地方,推荐放在这里,不会被系统升级而覆盖同名文件。如果两个目录下有相同的可执行程序,谁优先执行受到PATH
环境变量的影响,比如我的电脑PATH
变量中,/usr/local/bin
优先于/usr/bin
。
接下来执行ln index.js /usr/local/bin/ip
,创建index.js
的一个硬链接文件/usr/local/bin/ip
,如图
![f399b29031aebf83ab949666d849ab07.png](https://i-blog.csdnimg.cn/blog_migrate/a559070f168dc0b16cb47fd1ccec02f4.jpeg)
然后就可以直接使用自定义的命令ip
了 。
![921073b55f15c6170fd7722162273a43.png](https://i-blog.csdnimg.cn/blog_migrate/32f5159325afc727ecab1ba79b6024d0.png)
如果只是自己使用,那么一个简单的CLI工具就开发完毕了,但作为一名喜欢分享的童鞋,我希望可以把这个CLI工具分享给他人。
这时候可以选择使用npm管理项目,与他人共享开发成果。为了演示在npm项目中开发CLI工具的流程,我们暂时先删除刚才的命令ip
![4e125b312f2527b2087671fcfeb46723.png](https://i-blog.csdnimg.cn/blog_migrate/c83d8a1cb048d749298f9c4a493f3abc.jpeg)
5 使用npm 管理项目
5.1 初始化npm项目
执行npm init -y
将项目初始化为npm项目
![6719c56c0d236568fe1786e289776b8f.png](https://i-blog.csdnimg.cn/blog_migrate/a55a1ef690862c1a1f80910b15dd4166.jpeg)
目录结构如下
.
├── index.js
└── package.json
5.2 设置bin字段
在package.json
中设置bin
字段,定义一个命令ip
并指定该命令执行./index.js
文件
{
"name": "ip-cli",
"version": "1.0.0",
"description": "",
"main": "index.js",
"bin": {
"ip": "./index.js"
},
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
5.3 测试CLI工具
执行npm link
,在全局的 node_modules
下生成一个符号链接
![71bf7e5de889c5394394090c63b100f0.png](https://i-blog.csdnimg.cn/blog_migrate/a7e280fe2d60baeff22d99c3091e5379.jpeg)
然后就可以在全局使用 package.json
中 bin
字段定义的命令ip
了,执行ip
命令结果如下
![f181d3302af33b05c0289cd759d3b47b.png](https://i-blog.csdnimg.cn/blog_migrate/e6b36af96dcd85b8fbbb98435b5e402e.png)
输出了本机IP地址,测试成功。
5.4 发布CLI工具
执行npm login
登录账号,登录成功后执行npm whoami
可以看到npm用户名
![1c8a4c3e4573a0dce2519034e449be3f.png](https://i-blog.csdnimg.cn/blog_migrate/8ebcbbc5bfc762d984d01f594bec6a4a.png)
确认无误后执行 npm publish
发布npm包。
发布包的时候需要确保包的名字没有被占用,如下是包名冲突的情况
![2f0f145c27f6b5de2ca68101915a2a76.png](https://i-blog.csdnimg.cn/blog_migrate/0063707fe602c3796a92dcfa0eda6270.jpeg)
遇到这种情况可以选择换个名字或者加scope
,如下我给包名增加scope
,将包名由ip-cli
改为@qianxuemin/ip-cli
{
"name": "@qianxuemin/ip-cli",
"version": "1.0.2",
"description": "",
"main": "index.js",
"bin": {
"ip": "./index.js"
},
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
加了scope
后,发布包的时候需要设置访问级别,我们设置为公开
![3e7c08897b49238a30b4c290479e3b6f.png](https://i-blog.csdnimg.cn/blog_migrate/153d333ed4950fd495ee654d94576bc2.png)
发布成功后,其他开发人员就可以通过npm install @qianxuemin/ip-cli -g
安装并使用了。
![ce7825f40768cb9aaf6209ff4d008f41.png](https://i-blog.csdnimg.cn/blog_migrate/e6c02106d66abc38089d36d76446db3c.png)
至此,一个简单的CLI工具开发、发布完成。
6 注意事项
上面演示了CLI工具的整体开发、发布流程,没有对细节做太多说明。如下总结了在日常开发CLI工具中需要注意的事项与大家分享:
1、工具应当包含完整的README.md
2、工具应当包含help
和version
信息
3、工具应当包含运行指示,比如加载中
4、发布之前要到 https://www.npmjs.com 查找一下自己的包名有没有被占用,如果被占用需要先修改包名或加scope
5、发布包时,可以通过在项目中添加.npmignore
或.gitignore
文件限制发布的文件内容,.npmignore
优先级高于.gitignore
。或者在 package.json
中 files
字段设置发布哪些文件或目录,它的优先级高于 .npmignore
和 .gitignore
。
7 开发CLI工具常用的库
相比原生Node.js,使用一些开源模块能够简化CLI工具的开发,提高开发效率。如下是开发CLI工具常用的模块:
- commander: 注册、解析命令行参数
- Inquirer: 让命令行与用户进行交互
- chalk: 给命令行字符加颜色
- shelljs: 跨平台调用shell命令的node封装
- Ora: 命令行提示图标
- progress: 命令行进度条
- blessed-contrib: 命令行可视化组件
- download-git-repo:拉取git仓库源代码
由于文章篇幅有限,这里就不一一进行介绍了,使用时可以直接到 https://www.npmjs.com/搜索。
8 总结
本文通过一个简单的例子分享了CLI工具的开发、发布流程,并总结了一些开发注意事项和常用的库。旨在帮助JavaScript开发人员入门CLI工具开发。希望大家都能在掌握CLI工具开发流程后,发挥想象力,开发出一些实用、好玩的东西,让我们的开发变得高效、有趣。
9 参考资料
- npm官网:https://www.npmjs.com.cn/
- Shebang:https://zh.wikipedia.org/wiki/Shebang
- Shebang:https://blog.csdn.net/u012294618/article/details/78427864
- awesome-nodejs:https://github.com/sindresorhus/awesome-nodejs#command-line-apps