引言
什么是CLI呢?它是英语(command-line interface 缩写CLI),即命令行界面。命令行界面是是在**图形用户界面(GUI)**得到普及之前使用最为广泛的的用户界面。
我们现在电脑端的操作使用,更多用户习惯于图形界面,因为它易于使用,所见即所得,而命令行界面需要用户熟记一大堆的命令才能进行操作使用;但是在熟记命令的前提下,使用命令行界面往往要较使用图形用户界面的操作速度要快。GUI侧重于易用而CLI则侧重于效率。
本篇文章,我们基于node来实现一个简单的命令行工具:命令行输入歌曲名字,打开默认浏览器查找。
1.初始化项目
- 新建工程目录 mkdir cli-demo && cd cli-demo
- 初始化 npm init
- 打开vscode编辑器 code .
- 根目录新建bin文件夹并新建index.js
- 修改package.json,删除无用main,script等字段
# package.json添加字段
"bin": {
"ktb": "./bin/index.js"
}
说明:ktb即为我们cli工具的名称
- 修改bin/index.js
#!/usr/bin/env node
console.log('hello ktb cli')
说明
- 文件头部**#!/usr/bin/env node**用来向系统指明这个文件的解释器是node
- 测试
1.通过node终端运行程序,node ./bin/index.js
2.好奇的同学肯定会问,现我们使用的Vue-Cli,create-react-app这些都有一个特定的命令,而我们使用上面的方式来执行是否太难看了,在上面的package.json文件中,我们已经添加了bin字段,并且声明了一个关键字:ktb和对应的执行文件。在我们使用第三方的命令工具之前,我们都先要安装到全局,然而我们目前还没有发布到npm,自然无法安装,这时候我们可以使用npm link这个命令,本地安装。
- 终端执行:ktb
// 终端输出
hello ktb cli
2.添加命令
2.1 查看版本信息
在我们使用第三方命令工具的时候,我们都可以通过参数-V或者--version来查看当前的版本号。这里我们会首先遇到几个问题:
- 如何获取 -V这个参数?
- 如何获取package.json的版本号?
在node程序中,我们可以通过使用process.argv获取到命令参数。
#!/usr/bin/env node
console.log(process.argv)
终端输入 ktb -v -h -l,控制台会输出:
[
'/Users/kongtuanbing/.nvm/versions/node/v16.13.0/bin/node',
'/Users/kongtuanbing/.nvm/versions/node/v16.13.0/bin/ktb',
'-v',
'-h',
'-l'
]
不过在此我们并不采用process.argv的方式来获取,比较麻烦,我们使用一个第三方专门处理命令行交互的包:commander
npm install commander
如何获取package.json的版本号呢?
由于我在package.json添加了"type": "module"的字段来声明node采用es6模块化方案,所以不能直接require('package.json')来读取json文件。
我们在此使用fs模块fs.readFileSync('package.json')
完整代码:
// ./bin/index.js
#!/usr/bin/env node
import fs from 'fs';
import {program} from 'commander';
import { concatUrl } from '../utils/index.js';
// 读取package.json
const pak = JSON.parse(fs.readFileSync(concatUrl('./package.json'), 'utf-8'));
// ktb -V
program.version(pak.version).usage('<command> [options]')
program.parse(process.argv)
// ./utils/index.js
import { fileURLToPath } from 'url';
import { dirname, resolve } from 'path';
export { openBrowser } from './open.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
export function getRootPath(){
return resolve(__dirname, '../')
}
export function concatUrl(...args){
let result = getRootPath();
const startUrl = getRootPath();
args.forEach(item => {
result = resolve(result, item)
})
return result;
}
此时我们可以输入:
- ktb -V 或 --version 查看版本号
- ktb -h 或 --help和使用说明
2.2 美化终端输出
我们使用create-react-app 工具时候,我们可以在终端输入:create-react-app -h
我们可以看到,命令行输出的关键提示进行了颜色区分,增加交互式阅读体验。
在此,我们使用一个成熟的node包chalk
npm install chalk
用法:console.log(chalk.blue('Hello world!'));详细用法可参考chalk文档
修改./bin/index.js
#!/usr/bin/env node
import fs from 'fs';
import {program} from 'commander';
import chalk from 'chalk';
import { concatUrl } from '../utils/index.js';
// welcome
console.log(chalk.green('welcome to use ktb-cli'))
// 读取package.json
const pak = JSON.parse(fs.readFileSync(concatUrl('./package.json'), 'utf-8'));
// ktb -V
program.version(pak.version).usage('<command> [options]')
// ktb -h
program.on('--help', () => {
console.log()
console.log(
`Run ${chalk.cyan(
`ktb <command> --help`
)} for detailed usage of given command.`
)
console.log()
})
program.parse(process.argv)
2.3 添加sing命令
这里我们添加一个命令,实现输入歌曲名称,查找歌曲并自动打开浏览器显示结果。
ktb sing 梦的翅膀已经受了伤
先介绍commander的用法:
# npm install commander
import {program} from 'commander';
- program.command(name,arg),第一个参数为命令名称,第二个参数为命令参数。
- 参数有3种表示方式:
- <> 表示必须按
- [] 表示可选参数
- 点号表示可变长参数,如果使用,只能是最后一个参数
// 添加命令--new
program
.command('sing <word>')
.description('generate a new image use the input word') // 命令描述
.option('-a --auto', 'auto play music') // 布尔选项
.option('-t --time <time>', 'music time limit min', 1000) // 默认值选项
.action((word, options) => {
console.log(word)
console.log(options)
})
完整文件参考
#!/usr/bin/env node
import fs from 'fs';
import {program} from 'commander';
import chalk from 'chalk';
import { concatUrl, openBrowser } from '../utils/index.js';
// welcome
console.log(chalk.green('welcome to use ktb-cli'))
// 读取package.json
const pak = JSON.parse(fs.readFileSync(concatUrl('./package.json'), 'utf-8'));
// ktb -V
program.version(pak.version).usage('<command> [options]')
// 添加命令--new
program
.command('sing <word>')
.description('generate a new image use the input word') // 命令描述
.option('-a --auto', 'auto play music') // 布尔选项
.option('-t --time <time>', 'music time limit min', 1000) // 默认值选项
.action((word, options) => {
console.log(word)
console.log(options)
openBrowser(word)
})
// ktb -h
program.on('--help', () => {
console.log()
console.log(
`Run ${chalk.cyan(
`ktb <command> --help`
)} for detailed usage of given command.`
)
console.log()
})
program.parse(process.argv)
// util
import open from 'open';
export function openBrowser(search = '自由飞翔'){
const rootPath = 'https://music.hitushen.cn/';
const openUrl = new URL(rootPath);
openUrl.search = `?name=${encodeURIComponent(search)}&type=netease`;
switch(process.platform) {
case 'darwin':
open(openUrl.href)
break
case 'win32':
open(openUrl.href)
break
default:
open(openUrl.href)
}
}
2.4 升级命令,添加问答操作
在我们使用vue-cli初始化一个工程的时候,我们都会看到有一系列的问答操作,来处理用户的需求,根据用户不同的问答结果反馈其要的结果
这里我们需要用到一个node成熟的交互模块,inquirer,具体使用,我们可以参考官方说明文档
我们在./bin/index.js 添加命令
// 添加命令--create
program
.command('create <word>')
.description('This is project name')
.action(async(action) => {
const action = await createAction();
console.log(action)
})
// createAction
import inquirer from 'inquirer';
export function createAction () {
return inquirer.prompt([
{
type: 'input',
message: 'please input music name',
default: '自由飞翔',
name: 'projectName'
},
{
type: 'list',
message: 'please select a player',
default: 0,
name: 'style',
choices: [
{
value: 0,
name: 'netease'
},
{
value: 1,
name: 'qq'
},
{
value: 2,
name: 'kugou'
}
]
},
{
type: 'confirm',
message: 'Auto player?',
default: false,
name: 'auto'
},
{
type: 'checkbox',
name: 'allSelect',
message: 'please select condition',
default: 0,
choices: [
{
value: 1,
name: 'babel'
},
{
value: 2,
name: 'typescript'
},
{
value: 3,
name: 'postcss'
}
]
}
]).then(answers => answers)
}
结束
上面只是初步带领大家熟悉cli工具的基本实现。希望能够帮助到有需要学习了解的同学。