使用commander和inquire创建我们的专属秘书

1,930 阅读7分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 11 天,点击查看活动详情

前言

最近在做自己的脚手架,其中一定少不了命令行交互,commander(指挥官)作为一款优秀的命令行交互的库,是命令行交互首选的库。但是里面的api众多,只有我们理解透彻,才能用起来得心应手。今天我们就来一起探讨commander

但我觉得写一篇文章应该想想怎么写才能让别人记得住,而不是只是当做字典查阅,你不觉得你翻字典的时候浪费了你很多摸鱼的时间么,有这时间打一局王者他不香么?

commander

安装commander

npm install commander

声明program变量

首先我们要知道program是什么?

program就是一个全局对象,对就是一个对象,一会儿我们会往这个对象上添加属性和方法。你可以把program想成你的秘书

image.png

不知道这个图片是不是你心中的理想秘书的样子,哈哈。你在命令行敲的命令都是对你的秘书下命令,现在需要的就是你要告诉你的秘书,你下什么命令她需要去做什么

const { program } = require('commander');

解析命令行参数

代码如下

program.parse(process.argv)

可能有的朋友不太熟悉process是什么?
processnode的全局模块,作用比较直观。可以通过它来获得node进程相关的信息,比如运行node程序时的命令行参数。或者设置进程相关信息,比如设置环境变量。

process.argv 返回一个数组,数组元素分别如下:

  • 元素1:node
  • 元素2:可执行文件的绝对路径
  • 元素x:其他,比如参数等
// print process.argv
process.argv.forEach(function(val, index, array) {
  console.log('参数' + index + ': ' + val);
});

运行命令 NODE_ENV=dev node argv.js --env production,输出如下。(不包含环境变量)

参数0: /Users/a/.nvm/versions/node/v6.1.0/bin/node
参数1: /Users/a/Documents/git-code/nodejs-learning-guide/examples/2016.11.22-node-process/argv.js
参数2: --env
参数3: production
 

所以我们可以理解program.parse(process.argv)就是你的秘书在聆听你的命令

环境准备

新建一个空文件夹->StudyCommander

npm init -y //创建一个package.json

再新建一个bin文件夹,里面新建一个index.js文件

#! /usr/bin/env node
const { program } = require('commander');
const pkg = require('../package.json'); //获取本地的package.json文件

program.version(pkg.version);//指定秘书的版本
program.parse(process.argv);

package.json文件中用创建bin属性

 "bin": {
    "secretary": "./bin/index.js"
  },

现在在命令行输入npm link命令,把secretary命令注册到全局环境,这时候你只要在命令行输入secretary命令,他都会执行本文件中index.js脚本

npm link

现在咱们在命令行演示一下

 secretary -V
 -> 1.0.0  // 输入内容为当前秘书的版本

选项

什么是选项?

你可以理解为就是在全局program上定义属性,属性值可以是boolean类型或者字符串

常见boolean类型或者带参数类型选项

program.option('-d, --debug', 'whether to enable debug mode?', false);
program.option('-e, --envName <envName>', 'get environment variable name');

选项参数分析

  • 参数一:是选项的名称,一个-是简写,两个--是全称,如果是带参数的话,那么尖括号参数名
  • 参数二:选项描述
  • 参数三:选项默认值

如何获取属性

program.opts()  // 就能获取全部选项

现在我们测试一下

image.png

good和预期一样

命令注册

命令就很简单了,你就可以理解为让你的秘书帮你去做一些事情,我们可以看以下简单的例子

program
  .command('clone [source] [destination]')
  .description('clone a repository into a newly created directory')
  .option('-d,--debug', 'whether to enable debug mode?', false)
  .action((source, destination, options) => {
    console.log('source', source)
    console.log('destination', destination)
    console.log('options', options)
  })

image.png

【注意】 `<>`表示必填,`[]`表示可选

当然我们也可以分开写,我们建议分开写

program
  .command('split')
  .description('分割字符串')
  // 用<>表示是一个必输的字符,[]表示可输
  .argument('<string>', 'str to split')
  .argument('<array>', 'str to split')
  .option('-s, --separator <char>', 'separator character', ',')
  .action((str, array, options) => {
    console.log('str', str)
    console.log('array', array)
    console.log('options', options)
  })

选项和命令的监听

对options的监听

现在我要对--debug进行监听,如果有输入--debug就执行回调函数,这个回调函数要早于命令的回调函数

program.on('option:debug', () => { 
  console.log('开启了debug模式');
})

对command的监听

如果用户输入一个我们没有定义的命令,那么需要给用户提供一个纠错信息,那么就可以使用监听功能。

program.on('command:*', obj => {
  console.error('未知的命令:' + obj[0])
})

帮助信息

此时我们在命令行输入

secretary

image.png

会显示所有的选项和命令

如果要修改首行提示也就是

Usage: secretary [options] [command]

可以使用usage属性

program.usage("<command> [options]");

此时首行提示就会变成

Usage: secretary <command> [options]

inquirer

但是你有没有感觉这个秘书很生硬呀,必须一次输入全部命令,而且也没有跟人对话的那种功能

这个时候就要引出我们的inquire

安装

npm i inquirer@8 //因为我们要使用require引入得9版本以下的

prompt

prompt是什么意思

接下来让我们的chatGPT解读一下

image.png

其实inquirer的主要方法就是prompt,接下来看一下prompt的用法,prompt方法接受question作为参数,返回一个promise

inquirer
  .prompt([
    /* Pass your questions in here */
  ])
  .then((answers) => {
    // Use user feedback for... whatever!!
  })
  .catch((error) => {
    if (error.isTtyError) {
      // Prompt couldn't be rendered in the current environment
    } else {
      // Something else went wrong
    }
  });

question参数

  • questionArray<Object>参数是一个数组,可以接受多个问题

其中 Object 里面可以塞:

  • type:【String】提示的类型,默认 input,包含 inputnumberconfirmlistrawlistexpandcheckboxpasswordeditor
  • name:【String】存储当前问题回答的变量
  • message:【String|Function】提问的问题内容
  • default:【String|Number|Boolean|Array|Function】默认值
  • choices:【Array|Function】列表选项
  • validate:【Function】验证方法,校验输入值是否可行,有效返回 true,否则返回字符串表示错误信息(返回 false 则为默认的错误信息)
  • filter:【Function】对答案进行过滤处理,返回处理后的值
  • transformer:【Function】操作答案的显示效果
  • when:【Function|Boolean】接受答案,根据前面的内容判断是否需要展示该问题
  • pageSize:【Number】在 listrawlistexpandcheckbox 这种多选项中,进行分页拆分
  • prefix:【String】修改默认前缀
  • suffix:【String】修改默认后缀
  • askAnswered:【Boolean】已有答案是否强制提问
  • loop:【Boolean】list 是否能循环滚动选择,默认 true

单选框

program
  .command('service [serviceName]')
  .description('what would you like some services?')
  .action((serviceName) => {
    if (!serviceName) {
      // 如果serviceName为空,要inquire boss
      inquirer.prompt([
        {
          type: 'list', // 很奇怪,为啥不是radio
          name: 'services', //存储当前问题回答的变量
          message: 'boss, 你想要什么服务',
          choices: ["陪客人吃饭", "订机票出差", "通知经理开会"]
        }
      ]).then((answer) => {
        if (answer.services === "通知经理开会") {
          console.log("好的, 我这就通知所有部门经理到会议室开会");
        }
      })
    }
  })

输入框

现在让我们的秘书询问我们需要什么服务

inquirer.prompt([
          {
            type: 'input', // 
            name: 'otherService',
            message: 'boss, 还有什么其他吩咐么?',
          }
        ]).then((answer) => {
          // 此时不管你的老板说啥,你都说好的
          if (answer.otherService) {
            console.log("好的");
          }
        })

通过上面两个例子,我相信聪明的小伙伴你已经学的差不多了

源码

#! /usr/bin/env node
const { program } = require('commander');
const inquirer = require('inquirer');
const pkg = require('../package.json');
program.option('-d,--debug', 'whether to enable debug mode?', false);
program.option('-e, --envName <envName>', 'get environment variable name');

program
  .command('clone [source] [destination]')
  .description('clone a repository into a newly created directory')
  .option('-d,--debug', 'whether to enable debug mode?', false)
  .action((source, destination, options) => {
    console.log('source', source)
    console.log('destination', destination)
    console.log('options', options)
  })

program
  .command('service [serviceName]')
  .description('what would you like some services?')
  .action((serviceName) => {
    if (!serviceName) {
      // 如果serviceName为空,要inquire boss
      inquirer.prompt([
        {
          type: 'list', // 很奇怪,为啥不是radio
          name: 'services', //存储当前问题回答的变量
          message: 'boss, 你想要什么服务',
          choices: ["陪客人吃饭", "订机票出差", "通知经理开会"]
        }
      ]).then((answer) => {
        if (answer.services === "通知经理开会") {
          console.log("好的, 我这就通知所有部门经理到会议室开会");
        }
        inquirer.prompt([
          {
            type: 'input', // 
            name: 'otherService',
            message: 'boss, 还有什么其他吩咐么?',
          }
        ]).then((answer) => {
          // 此时不管你的老板说啥,你都说好的
          if (answer.otherService) {
            console.log("好的");
          }
        })
      })
    }
  })
  
program.on('option:debug', () => { 
  console.log('开启了debug模式');
})

program.on('command:*', obj => {
  console.error('未知的命令:' + obj[0])
})

program.version(pkg.version);
program.parse(process.argv);

参考