# 命令行工具开发小记CLI

TIP

英语:command-line interface 缩写:CLI

# 初始化

  npm init -y
1
  • 在当前目录下初始化package.json文件

# 准备文件

#目录结构
└─bin
    └─index.js
└─package.json
1
2
3
4
  • 在当前项目增加bin文件夹在bin中新建index.js文件
// /bin/index.js
#!/usr/bin/env node
console.log('husky are you scared')
1
2
3
  • index.js文件第一行一定要声名使用那个执行器来执行这里写node
  • deno现在也发布了1.0.0版本,说不定之后就用deno了
  • 需要在package.json中加入bin字段




 
 
 






{
  "name": "chrome-plugin-basic-cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "bin": {
    "basic-chrome-plugin": "./bin/index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
1
2
3
4
5
6
7
8
9
10
11
12
  • 在当前项目目录下执行连接到全局
  npm link
1
  • 在当前目录在连接一下,方便调试,如果后期发到npm上了,可以直接npm install <package-name>即可
  • 开发阶段可以这样安装到本项目中
  npm link chrome-plugin-basic-cli # 这里是自己定义的名字,就是package中的bin对象的属性名
1

TIP

注意这里npm link xxx 是package.json中的name字段,同样npm install 也是 当在命令行执行命令的时候才用bin中定义的名字来执行

# 简化命令

  • 可以看到为了语义化我们的命令行工具,名字很长这里我们给他取一个简短的名字
  • bin对象下再新建一个属性,和basic-chrome-plugin指定的文件是同一个
{
  "name": "chrome-plugin-basic-cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "bin": {
    "basic-chrome-plugin": "./bin/index.js",
    "chromeCli": "./bin/index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 命令行参数

# 用process.argv获取命令行参数

  • 获取命令行的参数
process.argv // 可以获取到我们输入的参数
1
  • 举例:chromeCli -v
  • process.argv会返回一个数组
  • 数组第一个元素是node执行器的位置
  • 第二个元素是当前命令的位置
  • 第三个参数才是我们输入的参数,一次往后都是我们输入的参数,输入多个参数用空格隔开
chromeCli list init name ....
1

# 使用commander工具包

  yarn add commander
1

# 完成-V --version 的参数功能

// /bin/index.js

const { program } = require('commander')
const package = require('./package.json')

program.version(package.version)

program.parse(process.argv)
1
2
3
4
5
6
7
8
  • 此时在命令行中 chromeCli -VchromeCli --version 就会打印package.json中的version

# 定义 init 命令

  • 这里我们以初始化项目模板功能进行演示,首先需要介绍几个API
  1. command 命令名称
  • <require> 代表必填参数
  • [option] 代表可选参数
  1. description 命令描述
  2. action 执行该命令的方法
// /bin/index.js
program
  .command('init <template> <project-name>') // 定义init 命令和两个必填参数
  .description('init chrome plugin project') // 描述
  .action(function (template, projectName) { // 获取参数 并根据所输入的参数完成相应逻辑
    console.log('%s -- %s', template, projectName)
  })

program.parse(process.argv) // 解析参数 必须要执行 一定要放在所有命令的最后执行
1
2
3
4
5
6
7
8
9

# 定义 list 命令

const templates = {
  'basic': {
    repoUrl: 'https://github.com/huskyAreYouScared/husky-tools',
    description: '基础的chrome plugin 需要的文件模板'
  }
}

program
  .command('list')
  .action(()=>{
    for (let key in templates) {
      console.log(`${key} ${templates[key].description}`);
    }
  })


program.parse(process.argv)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  • 在命令行输入 chromeCli list 就会打印templates中的模板信息

# 下载github上的模板

  • 在下载之前首先要在GitHub上面新建仓库,这个步骤就不在这里记录了
  • repo的地址放在templates中即可
  • 这里我们要借助download-git-repo的第三方包来下载模板
yarn add download-git-repo
1
  • 需要在之前的templates对象中新增downloadUrl,这个属性是传给download-git-repo的第一个参数
  • downloadUrl的路径应该遵循这样的规范
  https://github.com:账户/repositry#分支
1
const templates = {
  'basic': {
    repoUrl: 'https://github.com/huskyAreYouScared/husky-tools',
    downloadUrl: 'https://github.com:huskyAreYouScared/husky-tools#master',
    description: '基础的chrome plugin 需要的文件模板'
  }
}

program
  .command('init <template> <project-name>')
  .description('init chrome plugin project')
  .action(function (template, projectName) {
    downloadGitRepo(templates[template].downloadUrl, projectName, { clone : true }, (err)=>{
      if (err) {
        console.log(err);
      } else {
        console.log('success');
      }
    })
  })

program.parse(process.argv)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  • 此时执行 chromeCli init basic demo就会将github上面的模板下载下来

# 命令行交互

# 需要借助 inquirer

yarn add inquirer
1
  • inquirer 插件提供了很多的交互方式,这里已最常见的多选,输入,确认为例
const { program } = require('commander')
const inquirer = require('inquirer')

program
  .command('create')
  .action(()=>{
    inquirer
      .prompt([
        {
          type: 'confirm',
          name: 'isDefaultImg',
          message: 'default image is husky image'
        },
        {
          type: 'checkbox',
          name: 'type',
          choices: ['cat', 'dog', 'birde'],
          message: 'default image is husky image'
        },
        {
          type: 'input',
          name: 'name',
          choices: ['cat', 'dog', 'birde'],
          message: 'default image is husky image'
        }
      ])
      .then(answers => {
        console.log(answers);
        
      })
      .catch(error => {
        if (error.isTtyError) {
          // Prompt couldn't be rendered in the current environment
        } else {
          // Something else when wrong
        }
      });
  })
program.parse(process.argv);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
  • prompt 用户执行完操作后返回一个promise对象,将用户的选择结果已对象的方式进行返回(answers)
? default image is husky image Yes
? default image is husky image cat
? default image is husky image twohaha
{ isDefaultImg: true, type: [ 'cat' ], name: 'twohaha' }
1
2
3
4

# 命令行交互优化

# loading效果

  • 这里需要ora插件来帮忙
  • 使用也很简单
const ora = require('ora')

let spinner = ora('正在初始化...')
spinner.start()
setTimeout(()=>{
  // spinner.fail() 下载失败提示
  spinner.succeed() 下载成功提示
},2000)

1
2
3
4
5
6
7
8
9

# 终端字符串样式

yarn add chalk
1
const chalk = require('chalk')
const ora = require('ora')

let loading = chalk.cyan('正在初始化...') // 加载过程中是蓝色
  let spinner = ora(loading)
  spinner.start()
  setTimeout(()=>{
    spinner.succeed(chalk.green('正在初始化...')) // 成功设置字符为绿色
  },2000)
1
2
3
4
5
6
7
8
9
  • 还有很多的样式详情可以去这里传送门

# 日志图标 log-symbols

yarn add log-symbols
1
  • 一共有四种类型
    一共有四种类型,info,success,warning,error
const logSymbols = require('log-symbols');

console.log(logSymbols.success, 'Finished successfully!');
1
2
3
Last Updated: 1/23/2022, 10:16:22 AM