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()代表文件 |
semver | npm的语义版本器 |
leven | 测量两字符串之间的差异<br/>最快的JS实现之一 |
lru cache | 删除最近最少使用的项的缓存对象 |
portfinder | 自动寻找 8000 至65535 内可用端口号 |
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调用获得机器的默认网关 |
joi | JavaScript最强大的模式描述语言和数据验证器。 |
fs-extra | 添加了未包含在原生fs 模块中的文件系统方法,并向fs 方法添加了promise支持 |
Acorn | 一个小而快速的JavaScript解析器,完全用JavaScript编写。 |
zlib.js | ZLIB.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交互工具库 -- leven, lru cache和portfinder
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 cheesenode 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 Commandererror: 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: 100node Commander -i 123
integer: 123node 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
时,若没有指定其他子命令,则会默认执行这个命令
参考
基本常用的方法场景就这些了,更完整的用法可以直接查阅文档
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
启动提示界面(查询会话)
- questions (数组)包含 Question Object(使用reactive interface,您还可以传递一个
Rx.Observable
实例) - 返回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) 选择数组或函数返回的选择数组。如果定义为函数,第一个参数将是当前查询者会话的回答。数组值可以是简单的
numbers
、strings
或包含name
的objects
(在列表中显示)、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、expand
或checkbox
时将呈现的行数。 - 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, ' '));
})
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, ' '));
})
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, ' '));
})
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, ' '));
})
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, ' '));
})
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, ' '));
})
请注意,需要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, ' '));
})
Plugins
autocomplete
显示用户类型的选项列表,与其他包兼容,比如fuzzy(用于搜索)
checkbox-plus
带有自动完成和其他添加的复选框列表
datetime
使用数字键盘和箭头键的可定制的日期/时间选择器
inquirer-select-line
提示在数组中选择添加新元素的索引
command
简单的提示与命令历史和动态自动完成
inquirer-fuzzy-path
提示选择模糊文件/目录。
inquirer-emoji
输入表情符号提示。
inquirer-chalk-pipe
Prompt for input chalk-pipe style strings
inquirer-search-checkbox
可搜索的询问者复选框
inquirer-search-list
发出询盘搜索列表
inquirer-prompt-suggest
询问者提示您缺乏创造性的用户。
inquirer-s3
用于查询器的S3对象选择器。
inquirer-autosubmit-prompt
自动提交基于您当前的输入,保存一个额外的输入
inquirer-file-tree-selection-prompt
询问者提示选择文件树中的一个文件或目录
inquirer-table-prompt
询问者的类似表格的提示。
参考
基本常用的方法场景就这些了,更完整的用法可以直接查阅文档
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。