npm i chalk commander download-git-repo inquirer ora request -S
commander: https://github.com/tj/commander.js
Inquirer: https://github.com/SBoudrias/Inquirer.js
chalk: https://github.com/chalk/chalk
Simple Git: https://github.com/steveukx/git-js#readme
2020.9.23 星期三 12:51
cli 搭建
简要
我们实现了一个脚手架的基本功能,大致分为三个流程(拉取模板->创建项目->收尾清理
1) commander 创建可执行的node命令
2) command:init: 复制模版
- 可提交到git,从服务端下载
- 本地缓存(和cli一起),不用从服务端下载最新
- 模版可以写死然后重写;
或者使用模版文件ejs等
;可添加其他命令,init,built,publish,test 等。
3) 创建bin命令;包括package.json中配置。
4) 测试;npm link
5) 发布到npm
git commit提交规范:
版本规范:
standard
参考
npm i chalk commander download-git-repo inquirer ora request -S
1
2
3
4
5
6
7
8
9
10
11
12const commander = require('commander');
const chalk = require('chalk');
const fs = require('fs-extra');
const path = require('path');
const request = require('request');
const progress = require('request-progress');
const exec = require('child_process').exec;
// 实际
import fs from 'fs-extra';
import str from 'underscore.string';
import ejs from 'ejs';
import readdir from 'fs-readdir-recursive';
目录
|– bin
|– co
|– co2
|– command
|– download.js
|– generator.js
|– utils
|– api.js
|– template
|– src
|– views/
|– utils/
|– /
|– mainl.js
|– package.json
|– index.js
|– package.json
创建bin命令
1 |
|
在脚手架的package.json中配置bin
“bin”: {
“easy-cli-react”: “index.js”
}
模板下载
1 | const downloadZipName = 'template.zip'; |
模板样例
1 | |____.babelrc # babel配置 |
commander
文档:https://github.com/tj/commander.js/blob/master/Readme_zh-CN.md
github: https://github.com/tj/commander.js
实际
program.parse(process.argv)
后才可以访问program.args
parse一般放在最后- program监听不到事件,只好在command的action中 注入函数
如果每一个命令前都需要,那就。。
基础
1 | // const program = require('commander') |
inrequirer
github: https://github.com/SBoudrias/Inquirer.js
实际
返回的Promise,可以通过async/await 同步方式使用。不在回调中1
2
3
4
5
6
7
8
9
10
11export async function getText(message = '') {
return (
await inquirer.prompt([{
name: 'input',
message: message,
validate: (name) => {
return Boolean(name.trim());
}
}])
).input;
}
基础
使用脚手架的时候最明显的就是与命令行的交互,如果想自己做一个脚手架或者在某些时候要与用户进行交互,这个时候就不得不提到inquirer.js了。
type:表示提问的类型,包括:input, confirm, list, rawlist, expand, checkbox, password, editor;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
39const inquirer = require('inquirer');
const promptList = [
// 具体交互内容
];
inquirer.prompt(promptList).then(answers => {
console.log(answers); // 返回的结果
})
// ## input
const promptList = [{
type: 'input',
message: '设置一个用户名:',
name: 'name',
default: "test_user" // 默认值
},{
type: 'input',
message: '请输入手机号:',
name: 'phone',
validate: function(val) {
if(val.match(/\d{11}/g)) { // 校验位数
return val;
}
return "请输入11位数字";
}
}];
// ## confirm
const promptList = [{
type: "confirm",
message: "是否使用监听?",
name: "watch",
prefix: "前缀"
},{
type: "confirm",
message: "是否进行文件过滤?",
name: "filter",
suffix: "后缀",
when: function(answers) { // 当watch为true的时候才会提问当前问题
return answers.watch
}
}];
node方式
- fs.readSync + process.stdin 同步读取用户输入
- readline.question 获取用户输入
- 基于 nodejs 模块 readline-sync
readline-sync: https://www.npmjs.com/package/readline-sync1
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// ## 1 fs.readSync + process.stdin
const fs = require('fs');
function readSyncByfs(tips) {
let response;
tips = tips || '> ';
process.stdout.write(tips);
process.stdin.pause();
response = fs.readSync(process.stdin.fd, 1000, 0, 'utf8');
process.stdin.end();
return response[0].trim();
}
console.log(readSyncByfs('请输入任意字符:'));
// ## 2 readline.question
const readline = require('readline');
function readSyncByRl(tips) {
tips = tips || '> ';
return new Promise((resolve) => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question(tips, (answer) => {
rl.close();
resolve(answer.trim());
});
});
}
readSyncByRl('请输入任意字符:').then((res) => {
console.log(res);
});
// ##3 readline-sync
var readlineSync = require('readline-sync');
// Wait for user's response.
var userName = readlineSync.question('May I have your name? ');
console.log('Hi ' + userName + '!');
chalk
chalk: https://github.com/chalk/chalk
Terminal string styling done right1
2
3console.log(chalk.blue('Hello world!'));
log(chalk.blue('Hello') + ' World' + chalk.red('!'));
log(chalk.blue.bgRed.bold('Hello world!'));
ora
ora: https://github.com/sindresorhus/ora1
2
3
4
5
6
7
8
9
10
11
12const ora = require('ora');
const spinner = ora('Loading unicorns').start();
setTimeout(() => {
spinner.color = 'yellow';
spinner.text = 'Loading rainbows';
}, 1000);
spinner..stop()
// .succeed(text?) ,.fail(text?) ,...
/* # color of the text */
const ora = require('ora');
const chalk = require('chalk');
const spinner = ora(`Loading ${chalk.red('unicorns')}`).start();
Yeoman
除了上述方法,我们也可以直接通过大名鼎鼎的Yeoman来创建,不过个人觉得没必要,毕竟这玩意也不难。
yeman: https://github.com/yeoman/yeoman
yeoman: https://yeoman.io/
Simple Git
Simple Git: https://github.com/steveukx/git-js#readme
A lightweight interface for running git commands in any node.js application.
提交信息规范
1 | npm install --save-dev husky |
1 | "husky": { |
其他
- package.json 中的 bin 字段
一个 npm 模块,如果在 package.json 中指定了 bin 字段,那说明该模块提供了可在命令行执行的命令,这些命令就是在 bin 字段中指定的。
package.json1
2
3
4
5
6
7
8
9{
"bin": {
"myapp": "./cli.js"
},
// 如果你的 npm 包只提供了一个可执行的命令.
// "name": "my-program",
// "version": "1.2.5",
// "bin": "./path/to/program"
}
程序安装后会可在命令行执行 myapp 命令,实际执行的就是指定的这个 cli.js 文件。
如果是全局安装,会将这个目标 js 文件映射到 prefix/bin 目录下,而如果是在项目中安装,则映射到 ./node_modules/.bin/ 目录下。
在安装第三方带有bin字段的npm,那可执行文件会被链接到当前项目的./node_modules/.bin中,在本项目中,就可以很方便地利用npm执行脚本(package.json文件中scripts可以直接执行:’node node_modules/.bin/myapp’);
- npm scripts 原理
npm 脚本的原理非常简单。每当执行npm run,就会自动新建一个 Shell,在这个 Shell 里面执行指定的脚本命令。因此,只要是 Shell(一般是 Bash)可以运行的命令,就可以写在 npm 脚本里面。
比较特别的是,npm run新建的这个 Shell,会将当前目录的node_modules/.bin子目录加入PATH变量,执行结束后,再将PATH变量恢复原样。
这意味着,当前目录的node_modules/.bin子目录里面的所有脚本,都可以直接用脚本名调用,而不必加上路径。比如,当前项目的依赖里面有 Mocha,只要直接写mocha test就可以了。
- #!/usr/bin/env node 到底是什么? shabang,shebang: sharp/hash/mesh;shell. bang;#!
当你输入一个命令的时候,npm是如何识别并执行对应的文件的呢?
简单的理解,就是输入命令后,会有在一个新建的shell中执行指定的脚本,在执行这个脚本的时候,我们需要来指定这个脚本的解释程序是node。
npm link
本地调试的时候,在根目录下执行npm link
即可把cli命令绑定到全局,以后就可以直接以cli作为命令开头而无需敲入长长的node cli之类的命令了。