4

nodejs交互工具库系列

作用
chalk-pipe使用更简单的样式字符串创建粉笔样式方案
chalk正确处理终端字符串样式
Commander.js完整的 node.js 命令行解决方案
Inquirer.js一组通用的交互式命令行用户界面。
slash系统路径符处理
minimist解析参数选项
dotenv将环境变量从 .env文件加载到process.env中
dotenv-expand扩展计算机上已经存在的环境变量
hash-sum非常快的唯一哈希生成器
deepmerge深度合并两个或多个对象的可枚举属性。
yaml-front-matter解析yaml或json
resolve实现node的 require.resolve()算法,这样就可以异步和同步地使用require.resolve()代表文件
semvernpm的语义版本器
leven测量两字符串之间的差异<br/>最快的JS实现之一
lru cache删除最近最少使用的项的缓存对象
portfinder自动寻找 800065535内可用端口号
ora优雅的终端转轮
envinfo生成故障排除软件问题(如操作系统、二进制版本、浏览器、已安装语言等)时所需的通用详细信息的报告
memfs内存文件系统与Node's fs API相同实现
execa针对人类的流程执行
webpack-merge用于连接数组和合并对象,从而创建一个新对象
webpack-chain使用链式API去生成简化webpack版本配置的修改
strip-ansi从字符串中去掉ANSI转义码
address获取当前机器的IP, MAC和DNS服务器。
default-gateway通过对OS路由接口的exec调用获得机器的默认网关
joiJavaScript最强大的模式描述语言和数据验证器。
fs-extra添加了未包含在原生fs模块中的文件系统方法,并向fs方法添加了promise支持
Acorn一个小而快速的JavaScript解析器,完全用JavaScript编写。
zlib.jsZLIB.js是ZLIB(RFC1950), DEFLATE(RFC1951), GZIP(RFC1952)和PKZIP在JavaScript实现。

nodejs交互工具库 -- chalk-pipe和chalk

nodejs交互工具库 -- commander和Inquirer

nodejs交互工具库 -- slash, minimist和dotenv, dotenv-expand

nodejs交互工具库 -- hash-sum, deepmerge和yaml-front-matter

nodejs交互工具库 -- resolve和semver

nodejs交互工具库 -- leven, lru cache和portfinder

nodejs交互工具库 -- ora和envinfo

nodejs交互工具库 -- memfs和execa

nodejs交互工具库 -- webpack-merge和webpack-chain

nodejs交互工具库 -- strip-ansi, address, default-gateway和joi

nodejs交互工具库 -- fs-extra, Acorn和zlib

Commander.js

完整的 node.js 命令行解决方案

安装

yarn add commander

声明 program 变量

为简化使用,Commander 提供了一个全局对象。

const { program } = require('commander');
program.version('0.0.1');

如果程序较为复杂,用户需要以多种方式来使用 Commander,如单元测试等。创建本地 Command 对象是一种更好的方式:

const { Command } = require('commander');
const program = new Command();
program.version('0.0.1');

选项

Commander 使用.option() 方法来定义选项,同时可以附加选项的简介。每个选项可以定义一个短选项名称(-后面接单个字符)和一个长选项名称(--后面接一个或多个单词),使用逗号、空格或|分隔。

选项可以通过 Commander 对象的同名属性获取,对于多个单词的长选项,使用驼峰法获取,例如--template-engine与属性program.templateEngine关联。选项命名可参考避免选项命名冲突

多个短选项可以合并简写,其中最后一个选项可以附加参数。 例如,-a -b -p 80 也可以写为 -ab -p80 ,甚至进一步简化为 -abp80

--可以标记选项的结束,后续的参数均不会被命令解释,可以正常使用。 如果后续命令也需要设置选项,则可以通过该方式实现,例如:do -- git --version

选项在命令行中的顺序不固定,一个选项可以在其他选项之前或之后指定。

常用选项类型,boolean 型选项和带参数选项

有两种最常用的选项,一类是 boolean 型选项,选项无需配置参数,另一类选项则可以设置参数(使用尖括号声明)。如果在命令行中不指定具体的选项及参数,则会被定义为undefined

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

program
  .option('-d, --debug', 'output extra debugging')
  .option('-s, --small', 'small pizza size')
  .option('-p, --pizza-type <type>', 'flavour of pizza');

program.parse(process.argv);

if (program.debug) console.log(program.opts());
if (program.small) console.log('- small pizza size');
if (program.pizzaType) console.log(`- ${program.pizzaType}`);

通过program.parse(arguments)方法处理参数,没有被使用的选项会存放在program.args数组中。

node commander -d -s -p 123
{ debug: true, small: true, pizzaType: '123' }

  • small pizza size
  • 123

选项的默认值

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

program
  .option('-c, --cheese <type>', 'add the specified type of cheese', 'blue');

program.parse(process.argv);

console.log(`cheese: ${program.cheese}`);
node commander
cheese: blue

可选参数

选项的参数使用方括号声明表示参数是可选参数,即传值不是必须的。

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

program
  .option('-c, --cheese [type]', 'Add cheese with optional type');

program.parse(process.argv);

if (program.cheese === undefined) console.log('no cheese');
else if (program.cheese === true) console.log('add cheese');
else console.log(`add cheese type ${program.cheese}`);
node commander
no cheese

node commander -c 123
add cheese type 123

必填选项

通过.requiredOption方法可以设置选项为必填。必填选项要么设有默认值,要么必须在命令行中输入,对应的属性字段在解析时必定会有赋值。该方法其余参数与.option一致。

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

program
  .requiredOption('-c, --cheese <type>', 'pizza must have cheese');

program.parse(process.argv);
node Commander

error: required option '-c, --cheese <type>' not specified

自定义选项处理

选项的参数可以通过自定义函数来处理,该函数接收两个参数:用户新输入的参数和当前已有的参数(即上一次调用自定义处理函数后的返回值),返回新的选项参数。

自定义函数适用场景包括参数类型转换,参数暂存,或者其他自定义处理的场景。

自定义函数可以设置选项参数的默认值或初始值(例如参数用list暂存时需要设置一个初始空集合)。

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

function myParseInt(value, dummyPrevious) {
  // parseInt takes a string and an optional radix
  return parseInt(value);
}

function increaseVerbosity(dummyValue, previous) {
  return previous + 1;
}

program
  .option('-f, --float <number>', 'float argument', parseFloat)
  .option('-i, --integer <number>', 'integer argument', myParseInt)
  .option('-v, --verbose', 'verbosity that can be increased', increaseVerbosity, 0)
  ;

program.parse(process.argv);

if (program.float !== undefined) console.log(`float: ${program.float}`);
if (program.integer !== undefined) console.log(`integer: ${program.integer}`);
if (program.verbose > 0) console.log(`verbosity: ${program.verbose}`);
node Commander -f 1e2
float: 100

node Commander -i 123
integer: 123

node Commander -vvv
verbosity: 3

变长参数选项

定义选项时,可以通过使用...来设置参数为可变长参数。在命令行中,用户可以输入多个参数,解析后会以数组形式存储在对应属性字段中。在输入下一个选项前(-或--开头),用户输入的指令均会被视作变长参数。与普通参数一样的是,可以通过--标记当前命令的结束。

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

program
  .option('-n, --number <numbers...>', 'specify numbers')
  .option('-l, --letter [letters...]', 'specify letters');

program.parse();

console.log('Options: ', program.opts());
console.log('Remaining arguments: ', program.args);
node Commander -n 1 2 3 --letter a b c
Options: { number: [ '1', '2', '3' ], letter: [ 'a', 'b', 'c' ] }
Remaining arguments: []

node Commander --letter=A -n80 operand
Options: { number: [ '80' ], letter: [ 'A' ] }
Remaining arguments: [ 'operand' ]

命令

通过.command().addCommand()可以配置命令,有两种实现方式:为命令绑定处理函数,或者将命令单独写成一个可执行文件(详述见后文)。子命令支持嵌套。

.command()的第一个参数可以配置命令名称及参数,参数支持必选(尖括号表示)、可选(方括号表示)及变长参数(点号表示,如果使用,只能是最后一个参数)。

使用.addCommand()program增加配置好的子命令。

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


// 通过绑定处理函数实现命令(这里的指令描述为放在`.command`中)
// 返回新生成的命令(即该子命令)以供继续配置
program
  .command('clone <source> [destination]')
  .description('clone a repository into a newly created directory')
  .action((source, destination) => {
    console.log('clone command called');
  });


program.parse(process.argv);
node Commander clone ./123
clone command called

其他用法如下

// 通过独立的的可执行文件实现命令 (注意这里指令描述是作为`.command`的第二个参数)
// 返回最顶层的命令以供继续添加子命令
program
  .command('start <service>', 'start named service')
  .command('stop [service]', 'stop named service, or all if no name supplied');

// 分别装配命令
// 返回最顶层的命令以供继续添加子命令
program
  .addCommand(build.makeBuildCommand());  

使用.command()addCommand()来传递配置的选项。当opts.noHelp设置为true时,该命令不会打印在帮助信息里。当opts.isDefault设置为true时,若没有指定其他子命令,则会默认执行这个命令

参考

基本常用的方法场景就这些了,更完整的用法可以直接查阅文档

commander.js

Inquirer.js

一组通用的交互式命令行用户界面。

目标和理念

努力为Node.js(也许还有“CLI Xanadu”)提供一个容易嵌入和漂亮的命令行界面。

  • 提供错误的反馈
  • 询问交互
  • 解析输入
  • 验证答案
  • 层次提示管理
注意: Inquirer.js 提供用户界面和查询会话流。如果您正在搜索一个完整的命令行程序实用程序,那么请查看commander, vorpal or args.

Installation

yarn add inquirer
var inquirer = require('inquirer');
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 when wrong
    }
  });

方法

inquirer.prompt(questions) -> promise

启动提示界面(查询会话)

inquirer.registerPrompt(name, prompt)

在name下注册提示插件

inquirer.createPromptModule() -> prompt function

创建一个自包含的查询器模块。如果您不希望在覆盖或添加新的提示类型时影响同样依赖于查询器的其他库。

var prompt = inquirer.createPromptModule();

prompt(questions).then(/* ... */);

Question Object

question object是一个包含问题相关值的 hash

  • type: (String) 提示符类型. 默认input - 可能的值 input, number, confirm, list, rawlist, expand, checkbox, password, editor
  • name: (String) 在answers hash中存储答案时要使用的名称。如果名称包含句点,它将在answers散列中定义一个路径。
  • message: (String|Function)打印的问题。如果定义为函数,第一个参数将是当前查询者会话的回答。默认值为 name(后跟冒号)。
  • default: (String|Number|Boolean|Array|Function)不输入任何内容时使用的默认值,或返回默认值的函数。如果定义为函数,第一个参数将是当前查询者会话的回答。
  • choices: (Array|Function) 选择数组或函数返回的选择数组。如果定义为函数,第一个参数将是当前查询者会话的回答。数组值可以是简单的numbersstrings或包含nameobjects(在列表中显示)、value (保存在answers hash中)和short属性(在选择后显示)的对象。选择数组还可以包含一个分隔符。
  • validate: (Function) 接收用户输入并answers hash。如果值有效,应该返回 true,否则返回错误消息(String)。如果返回 false,则提供一个默认的错误消息。
  • filter: (Function) 接收用户输入并answers hash。返回要在程序中使用的过滤后的值。返回的值将被添加到answers hash中。
  • transformer: (Function) 接收用户输入,answers hash和选项标志,并返回转换后的值以显示给用户。转换只影响编辑时显示的内容。它不会修改answers hash。
  • when: (Function, Boolean) 接收当前用户的answers hash,并根据是否应该问这个问题返回真或假。值也可以是一个简单的布尔值。
  • pageSize: (Number) 更改在使用 list、rawList、expandcheckbox时将呈现的行数。
  • prefix: (String) 更改默认的前缀消息。
  • suffix: (String) 更改默认后缀消息。
  • askAnswered: (Boolean) 如果答案已经存在,强制提示问题。
  • loop: (Boolean) 启用列表循环。默认值:真

默认情况下,选择(如果定义为函数)、validate, filter and when可以异步调用函数。要么返回一个promise,要么使用 this.async()来获得一个回调,您将使用最终值调用它。

{
  /* Preferred way: with promise */
  filter() {
    return new Promise(/* etc... */);
  },

  /* Legacy way: with this.async */
  validate: function (input) {
    // Declare function as asynchronous, and save the done callback
    var done = this.async();

    // Do async stuff
    setTimeout(function() {
      if (typeof input !== 'number') {
        // Pass the return value in the done callback
        done('You need to provide a number');
        return;
      }
      // Pass the return value in the done callback
      done(null, true);
    }, 3000);
  }
}

Answers

在每个提示符中包含客户端答案的键/值散列。

  • Key 问题对象的 name属性
  • Value (取决于提示)

    • confirm: (Boolean)
    • input : 用户输入(如果定义了过滤器,则进行筛选) (String)
    • number: 用户输入(如果定义了过滤器,则进行筛选) (Number)
    • rawlist, list : 选择的选择值(如果没有指定值,则为name)(String)

Separator

一个分隔符可以添加到任何选择数组:

// In the question object
choices: [ "Choice A", new inquirer.Separator(), "choice B" ]

// Which'll be displayed this way
[?] What do you want to do?
 > Order a pizza
   Make a reservation
   --------
   Ask opening hours
   Talk to the receptionist

构造函数接受一个兼性字符串值,它将用作分隔符。如果省略,分隔符将是--------

Separator实例的属性类型等于separator。这将允许使用查询器接口的工具检测列表中的分隔符类型。

Prompt types

注意: 允许在方括号([])中写入的选项是可选的。别人是必需的。
List - {type: 'list'}

使用 type, name, message, choices[, default, filter, loop] 属性. (注意,默认值必须是数组中的选择索引或选择值)

var inquirer = require('inquirer');
inquirer
  .prompt([
    {
      type: 'list',
      name: 'theme',
      message: 'What do you want to do?',
      choices: [
        'Order a pizza',
        // 分隔符
        new inquirer.Separator(),
        // 禁止选择
        {
          name: 'Contact support',
          disabled: 'Unavailable at this time',
        },
        'Talk to the receptionist',
      ],
    },
    {
      type: 'list',
      name: 'size',
      message: 'What size do you need?',
      choices: ['Jumbo', 'Large', 'Standard', 'Medium', 'Small', 'Micro'],
      filter: function (val) {
        return val.toLowerCase();
      },
    },
  ])
  .then(answers => {
    console.log(JSON.stringify(answers, null, '  '));
  })

image
image
image

Raw List - {type: 'rawlist'}

使用type, name, message, choices[, default, filter, loop] 属性. (注意,默认值必须是数组中的选择索引)

var inquirer = require('inquirer');
inquirer
  .prompt([
    {
      type: 'rawlist',
      name: 'theme',
      message: 'What do you want to do?',
      choices: [
        'Order a pizza',
        new inquirer.Separator(),
        'Ask opening hours',
      ],
    },
    {
      type: 'rawlist',
      name: 'size',
      message: 'What size do you need',
      choices: ['Jumbo', 'Large', 'Standard', 'Medium', 'Small', 'Micro'],
      filter: function (val) {
        return val.toLowerCase();
      },
    },
  ])
  .then(answers => {
    console.log(JSON.stringify(answers, null, '  '));
  })

image
image
image

Expand - {type: 'expand'}

使用type, name, message, choices[, default] 属性. (注意,默认值必须是数组中的选择索引。如果没有提供默认键,那么帮助将被用作默认选项)

注意,选择对象将接受一个名为key的额外参数,用于展开提示符。该参数必须是单个(小写)字符。h选项是由提示符添加的,不应该由用户定义。

var inquirer = require('inquirer');
inquirer
  .prompt([
    {
      type: 'expand',
      message: 'Conflict on `file.js`: ',
      name: 'overwrite',
      choices: [
        {
          key: 'y',
          name: 'Overwrite',
          value: 'overwrite',
        },
        {
          key: 'a',
          name: 'Overwrite this one and all next',
          value: 'overwrite_all',
        },
      ],
    },
  ])
  .then(answers => {
    console.log(JSON.stringify(answers, null, '  '));
  })

image
image

Checkbox - {type: 'checkbox'}

使用type, name, message, choices[, filter, validate, default, loop] 属性. default 期望为选中选项值的数组。

默认情况下会选中标记为{checked: true}的选项。

属性disabled为true的选项将无法选择。如果disabled是一个字符串,那么这个字符串将被输出到disabled选项旁边,否则它将默认为“Disabled”。禁用的属性也可以是一个同步函数,接收当前的答案作为参数,并返回一个布尔值或字符串。

var inquirer = require('inquirer');
inquirer
  .prompt([
    {
      type: 'checkbox',
      message: 'Select toppings',
      name: 'toppings',
      choices: [
        new inquirer.Separator(' = The Meats = '),
        {
          name: 'Pepperoni',
        },
        {
          name: 'Ham',
        },
        new inquirer.Separator(' = The Cheeses = '),
        {
          name: 'Mozzarella',
          checked: true,
        },
        {
          name: 'Cheddar',
        },
      ],
      validate: function (answer) {
        if (answer.length < 1) {
          return 'You must choose at least one topping.';
        }

        return true;
      },
    },
  ])
  .then(answers => {
    console.log(JSON.stringify(answers, null, '  '));
  })

image
image

Confirm - {type: 'confirm'}

使用type, name, message, [default] 属性. default 期望是一个布尔值

var inquirer = require('inquirer');
inquirer
  .prompt([
    {
      type: 'confirm',
      name: 'toBeDelivered',
      message: 'Is this for delivery?',
      default: false,
    }
  ])
  .then(answers => {
    console.log(JSON.stringify(answers, null, '  '));
  })

image

Input - {type: 'input'}

使用type, name, message[, default, filter, validate, transformer] 属性.

var inquirer = require('inquirer');
var chalkPipe = require('chalk-pipe');

const requireLetterAndNumber = (value) => {
  if (/\w/.test(value) && /\d/.test(value)) {
    return true;
  }

  return 'Password need to have at least a letter and a number';
};

inquirer
  .prompt([
    {
      type: 'input',
      name: 'name',
      message: "What's your first name",
    },
    {
      type: 'input',
      name: 'fav_color',
      message: "What's your favorite color",
      transformer: function (color, answers, flags) {
        const text = chalkPipe(color)(color);
        if (flags.isFinal) {
          return text + '!';
        }

        return text;
      },
    },
    {
      type: 'input',
      name: 'phone',
      message: "What's your phone number",
      validate: function (value) {
        var pass = value.match(
          /^([01]{1})?[-.\s]?\(?(\d{3})\)?[-.\s]?(\d{3})[-.\s]?(\d{4})\s?((?:#|ext\.?\s?|x\.?\s?){1}(?:\d+)?)?$/i
        );
        if (pass) {
          return true;
        }

        return 'Please enter a valid phone number';
      },
    },
    {
      type: 'password',
      message: 'Enter a password',
      name: 'password1',
      validate: requireLetterAndNumber,
    },
    {
      type: 'password',
      message: 'Enter a masked password',
      name: 'password2',
      mask: '*',
      validate: requireLetterAndNumber,
    },
  ])
  .then(answers => {
    console.log(JSON.stringify(answers, null, '  '));
  })

image

请注意,需要mask来隐藏实际的用户输入。

Editor - {type: 'editor'}

使用type, name, message[, default, filter, validate] 属性

在临时文件上启动用户首选编辑器的实例。一旦用户退出编辑器,就会将临时文件的内容作为结果读入。要使用的编辑器是通过读取$VISUAL$EDITOR 环境变量确定的。如果两者都不存在,则使用notepad (Windows上的)或vim (Linux或Mac上的)。

var inquirer = require('inquirer');
var chalkPipe = require('chalk-pipe');

const requireLetterAndNumber = (value) => {
  if (/\w/.test(value) && /\d/.test(value)) {
    return true;
  }

  return 'Password need to have at least a letter and a number';
};

inquirer
  .prompt([
    {
      type: 'editor',
      name: 'bio',
      message: 'Please write a short bio of at least 3 lines.',
      validate: function (text) {
        if (text.split('\n').length < 3) {
          return 'Must be at least 3 lines.';
        }
  
        return true;
      },
    },
  ])
  .then(answers => {
    console.log(JSON.stringify(answers, null, '  '));
  })

image
image

Plugins

autocomplete

显示用户类型的选项列表,与其他包兼容,比如fuzzy(用于搜索)
image

checkbox-plus

带有自动完成和其他添加的复选框列表
image

datetime

使用数字键盘和箭头键的可定制的日期/时间选择器
image

inquirer-select-line

提示在数组中选择添加新元素的索引
image

command

简单的提示与命令历史和动态自动完成

inquirer-fuzzy-path

提示选择模糊文件/目录。
image

inquirer-emoji

输入表情符号提示。
image

inquirer-chalk-pipe

Prompt for input chalk-pipe style strings

inquirer-search-checkbox

可搜索的询问者复选框

inquirer-search-list

发出询盘搜索列表
imageimage

inquirer-prompt-suggest

询问者提示您缺乏创造性的用户。
image

inquirer-s3

用于查询器的S3对象选择器。
image

inquirer-autosubmit-prompt

自动提交基于您当前的输入,保存一个额外的输入

inquirer-file-tree-selection-prompt

询问者提示选择文件树中的一个文件或目录

inquirer-table-prompt

询问者的类似表格的提示。
imageimage

参考

基本常用的方法场景就这些了,更完整的用法可以直接查阅文档

Inquirer.js


Afterward
621 声望62 粉丝

努力去做,对的坚持,静待结果