1、项目的初始化
npm init -y
要求:需要安装node.js
注意:在初始化的时候,package.json文件里的name相当于命令行的句柄比如name为test,那么相当执行的时候句柄是 test ***, 当然可以设置别外
{
"name": "yf",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
2、创建可执行文件
在项目中新建bin目录,并且在bin目录下新建yf文件,当然这个名字可以自己起
在window创建文件夹用mkdir 文件夹名, 创建文件用 type nul>yf这样就会建立无后缀名的的yf文件,yf文件的开头如下
#!/usr/bin/env node ...正常的逻辑代码
注意第一行的 #!/usr/bin/env node
,#!
表示要指定脚本文件的解析程序,/usr/bin/env
表示要去哪里找解析程序,node
是解析程序的名字(表示这个要文件由 node.js
来运行)。
3、配置其他配置项
在package.json中配置bin的内容项如下:
如果命令行的命名与项目的名称一致,那么可以使用如下方式
{
"name": "yf",
"version": "1.0.0",
"description": "",
"bin": "./bin/index.js",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {}
}
以上运行命令用的是 yf
如果需要使用别名,那么可以使用json的形式进行别名定义
{
"name": "yf",
"version": "1.0.0",
"description": "",
"bin": {
"yf-cli": "./bin/index.js"
},
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {}
}
注意:如果有提示操作权限问题可做如下配置
为 bin/index
添加可执行权限,在package.json同级目录下执行
chmod +x bin/index //用git bash来执行这一命令
4、连接至本地环境
把对应的命令行放置到本地环境在项目的根目录下运行
npm link //如果有重复,那么可以使用npm link --force 进行覆盖
如果不需要该命令的时候运行
npm unlink //则会删除指定的命令
查询命令指定的位置
which [命令]
在开发脚手架的过程中,如果一个脚手架依赖当前正在开发中的库文件,那么可以使用以下方法进行建立关联
// 链接本地库文件
cd you-lib-dir
npm link
cd you-cli-dir
npm link you-lib
// 取消链接本地库文件
cd you-lib-dir
npm unlink
cd you-cli-dir
npm unlink you-lib
// 如果发布上线后在库文件与当前冲突,可以移除全局的node_modules, 再重新npm install进行安装即可
5、配置可执行的命令 --使用插件commander依赖
安装commander依赖
npm i commander --save
commander快速入门
#! /usr/bin/env node
const commander = require('commander')
const pkg = require('../package.json')
//使用commander提供的单例的模式
// const { program } = commander
// 自己创建类进行初始化
const program = new commander.Command()
program.version(pkg.version).parse(process.argv)
commander中的配置选项
#! /usr/bin/env node
const commander = require('commander')
const pkg = require('../package.json')
const { program } = commander
// 在commander中 <command>表示必填项 [options]表示可选项
program
.name(Object.keys(pkg.bin)[0])
.usage('<command> [options]')
.version(pkg.version)
.option('-d, --debug [options]', '是否开启调试模式', false) // 第三个参数表示默认参数
.option('-e, --envName <envName>', '获取环境变量')
.parse(process.argv)
// 获取option所对应的值的集合
console.log(program.opts())
// 当用户输入even-cli时就自动的输了help信息
program.outputHelp()
commander中的命令注册
在commander中命令注册有两种方式: a、command注册命令; b、addCommand 注册命令
a方式
#! /usr/bin/env node
const commander = require('commander');
const pkg = require('../package.json');
const { program } = commander;
// 在commander中 <command>表示必填项 [options]表示可选项
program
.name(Object.keys(pkg.bin)[0])
.usage('<command> [options]')
.version(pkg.version)
.option('-d, --debug [options]', '是否开启调试模式', false) // 第三个参数表示默认参数
.option('-e, --envName <envName>', '获取环境变量');
// 注册命令
const init = program.command('init <packageName> [destination]');
init
.description('初始化项目的命令')
.option('-f, --force [options]', '强制初始化', false)
.action((pkgName, dest, opts) => {
// 第一个参数与第二个参数分别对应的是init的参数,opts对应的option集合
console.log('this is init');
console.log(process.cwd()); // 获取当前cwd地址
require('./lib/create')(pkgName, dest, opts) // 通过require的方式进行分包处理
});
program.parse(process.argv);
// 当用户输入even-cli时, 并且什么参数都没有输入,那么就弹出帮助信息
if (!process.argv.slice(2).length) program.outputHelp();
注意:如果在-f, --force后面添加options, 那么在even-cli init --force param1 param2 那么param1会默认为是--force的参数,如果没有【options】 那么param1与2都是init的参数
b方式
通过addCommand 注册子命令
// 相当于在program中添加子命令
const service = new Command('service');
service.usage('<command> [options]'); // 自定义子命令的使用方式,相当于在even-cli service [options]
service
.command('start [port]')
.option('-f, --force', '忽略端口占用情况,强制启动')
.description('启动一个服务')
.action((port, opts) => {
console.log(port, opts);
});
service
.command('stop [serviceName]')
.description('停止指定服务')
.action(() => {
console.log('这个是停止服务');
});
// 注册子命令
program.addCommand(service);
command中的其它重要知识点
// 当所有命令匹配不上时,就会进行该程序中
program.arguments('<cmd> [options]').action((cmd, opts) => {
console.log(cmd, opts);
});
// 监听help选项
program.on('--help', () => {
console.log()
console.log(` Run ${chalk.cyan(`vue <command> --help`)} for detailed usage of given command.`)
console.log()
})
command的示例
import { program, Command } from 'commander';
import colors from 'colors';
import log from '../utils/log';
import leven from 'leven';
import { init } from '../command/init';
const pkg = require('../../package.json');
// 进行建议的命令枚举
const suggestCommands = (unknownCmd: string, cmdList: Array<string>) => {
let suggestion: string | undefined = undefined;
cmdList.forEach((cmd: string) => {
const isBestMatch =
leven(cmd, unknownCmd) < leven(suggestion || '', unknownCmd);
if (leven(cmd, unknownCmd) < 3 && isBestMatch) suggestion = cmd;
});
if (suggestion) {
console.log();
log.error('', colors.red(`Did you mean ${colors.yellow(suggestion)}?`));
}
};
// 自定义报错信息
const enhanceErrorMessages = (
methodName: string,
callBack: (opt: any, flag?: string) => string,
) => {
//@ts-ignore
Command.prototype[methodName] = function (opt: any, flag: string) {
//@ts-ignore
if (methodName === 'unknownOption' && this._allowUnknownOption) return;
this.outputHelp();
console.log();
log.error('', colors.red(callBack(opt, flag)));
process.exit(1);
};
};
// 注册命令
export const registerCommand = (): void => {
program.version(`${pkg.name} ${pkg.version}`).usage('<command> [options]');
// 开启debug模式,在prepare已做,需要配置该项才不会报错
program.option('-d, --debug', 'enabling the Debug mode');
// 初始化项目的命令
program
.command('init [projectName]')
.description('initialize the project')
.option('-f, --force', 'whether to force initialization of the project')
.action((projectName: string, opt: Record<string, boolean>) => {
init(projectName, opt);
});
enhanceErrorMessages('missingArgument', (argName) => {
return `Missing required argument ${colors.yellow(`<${argName}>`)}.`;
});
// 无法匹配到指定的选项报错
enhanceErrorMessages('unknownOption', (optionName) => {
return `Unknown option ${colors.yellow(optionName)}.`;
});
enhanceErrorMessages('optionMissingArgument', (option, flag) => {
return (
`Missing required argument for option ${colors.yellow(option.flags)}` +
(flag ? `, got ${colors.yellow(flag)}` : ``)
);
});
// 进行无效指令匹配
program.on('command:*', (list: Array<string>) => {
const cmd = list[0]; // 获取到没有匹配的命令
console.log();
program.outputHelp();
console.log();
log.error('', colors.red(`Unknown command ${cmd}.`));
suggestCommands(
cmd,
program.commands.map((cmd) => cmd.name()),
);
});
// 输出帮助信息时的附带信息
program.on('--help', () => {
console.log();
log.info(
'',
`Run ${colors.green(
'even-pro <command> --help',
)} for detailed usage of given command.`,
);
});
program.parse(process.argv); //解析参数
// 如果什么参数都没有输入,那么就弹出帮助信息
if (!process.argv.slice(2).length) program.outputHelp();
};
6、命令行的ui显示
chalk颜色渲染
import chalk, { Chalk } from 'chalk';
// 通过颜色的api进行调用
console.log(chalk.red('hello world'));
// 能过颜色的api进行颜色拼接
console.log(chalk.red('hello') + ' ~', chalk.blue('world'));
// 进行链式调用
console.log(chalk.red.bgGreen.bold('hello world'));
// 进行嵌套调用
console.log(
chalk.red('hello', chalk.underline.bold.italic.yellow.bgRed('world')),
);
// 使用rgb实现颜色自定义
console.log(chalk.green('hello', chalk.rgb(255, 215, 0)('world')));
// 使用16进制进行颜色表色
console.log(chalk.hex('#ffd700').bold('hello', chalk.red('world')));
// 使用new Chalk()来实现, 里面的等级与颜色支持等级有关, 当等级为0时则全部会转成无彩色
const customChalk = new Chalk({ level: 3 });
console.log(customChalk.red('hello', customChalk.underline('world~~~')));
注意:在使用chalk的时候,因为该库使用ems来编码的,所以需要用webpack进行编译,如果是测试可以暂用mjs的文件后缀来测试
7、命令行的loading效果
npm i ora --save
初步使用案例
import ora from 'ora';
const spinner = ora('prepare').start();
// 指定loading的色彩
spinner.color = 'red';
// loading的前置文本
spinner.prefixText = 'loading';
// 自定义加载的图标
spinner.spinner = {
interval: 80,
frames: ['——', '/', '|', '\']
}
let process = 0;
const handle = setInterval(() => {
process += 10;
// 更改loading的文本为loading
spinner.text = `process ${process}%`;
if (process >= 100) {
// 下载成功文本;
spinner.succeed('下载成功');
// 下载失败文本
// spinner.fail('下载失败');
// 停止loading
// spinner.stop();
clearInterval(handle);
}
}, 1000);
如果定制度比较低,那么可以使用oraPromise方式
import ora, { oraPromise } from 'ora';
const callBack = async () => {
return new Promise((resolve, reject) => {
let count = 0;
const handle = setInterval(() => {
count += 10;
if (count >= 100) {
clearInterval(handle);
resolve();
}
}, 1000);
});
};
// 可以配置常规的配置
oraPromise(callBack, {
text: 'loading...',
prefixText: 'status',
}).then(() => {
console.log('ok');
});
ora配合自制进度条
import ora from 'ora';
const spinner = ora('prepare').start();
// loading的前置文本
spinner.prefixText = 'loading';
let process = 0;
const processBar = (current, total, length = 25) => {
// const cell = '█';
// const empty = '░';
const cell = '=';
const empty = '-';
const percent = current / total;
const cellTotal = Math.floor(percent * length);
const emptyTotal = length - cellTotal;
return cell.repeat(cellTotal) + empty.repeat(emptyTotal);
};
const handle = setInterval(() => {
process += Math.floor(Math.random() * 10 + 1);
// 更改loading的文本为loading
spinner.text = processBar(process, 100);
if (process >= 100) {
// 下载成功文本;
spinner.succeed('下载成功');
// 下载失败文本
// spinner.fail('下载失败');
// 停止loading
// spinner.stop();
clearInterval(handle);
}
}, 1000);
8、进度条的显示
npm i progress --save
初步使用案例
let bar = new ProgressBar(':bar :current/:total', { total: 10 });
let timer = setInterval(function () {
bar.tick();
if (bar.complete) {
console.log('\ncomplete\n');
clearInterval(timer);
}
}, 100);
具体可见npm 搜索 progress的使用
9、插件inquirer插件的使用
inquirer的使用主要针对在命令行中的交互组件,相当于页面中的input comfirm等功能
安装inquirer插件
npm i inquirer --save
结果使用:
inquirer.prompt(promptList).then(res => { console.log(res) //返回用户所交互的对象,返回的是一个对象,里面的key是上面定义的name字段,value是用户选择的对象 })
常用类型以及配置
- type: String 类型 默认值:input 类型有:input,number,confirm,list,rawlist,expand,checkbox,password,editor
- name: String 名字 将答案存储在答案哈希中时使用的名称。设置的name字段在结果返回时会变成key
- message: String | Function 提示信息 表示提示信息,如果是函数,那么接收的参数为之前项的选择答案,需要返回一个string类型,如果没有对应的值,默认为name字段
- default: String|Number|Boolean|Array|Function 默认值 即如果没有输入,那么就返回的默认值,如果为函数则接收的是之前选的答案,需要返回一个值
- prefix: String 前置项 相当于在message前置一个信息项,可以用chalk进行颜色修饰
- validate: Function 验证 对用户输入的内容进行验证,如果返回true则往下走,否则返回的字符串将成为提示信息,接收参数为用户输入的内容或选项
- when: (params) => Boolean 验证是否展示 相当于该选项并非必须,会根据之前回答而定,接收参数为之前的输入回答,返回boolean类型,如果为true则展示,否则不展示
- filter: (params) => String 过滤 相当对用户输入的内容进行过滤或者调整
- transformer: (功能)接收用户输入,回答哈希和选项标志,并返回转换后的值以显示给用户。转换仅影响编辑时显示的内容。它不会修改答案哈希
- choices: Array<String> 选项 当为list或者checkbox的时候,那么该选项的内容就是选项的内容
每个type类型需要配置的项或多或少会有些不同,比如当type为checkbox时,需要配置choices这一项,而当type为number时就不需要。
input类型
let promptList = [ { type: 'input', //类型 message: '请设置一个用户名:', //label字段 name: 'name', //设置的name字段在结果返回时会变成key default: 'admin', //默认值,如果用户没有输入时会自动填充默认值 validate(str) { //输入后进行验证,如果返回的是字符串,那么就会提示在下方,除非返回true if(str.length < 3) return '用户名不能少于3个字符' return true } }, { type: 'input', message: '请输入你的手机号码:', name: 'phone', validate(phone) { if(phone.length !== 11) return '手机的位数不正确' return true; } } ] inquirer.prompt(promptList).then(res => { console.log(res) //返回用户所交互的对象,返回的是一个对象,里面的key是上面定义的name字段,value是用户选择的对象 })
confirm类型
{ type: 'confirm', name: 'confirm', message: '你今天是否需要加班', default: false }
checkbox多选框
{ type: 'checkbox', name: 'checkbox', message: '请问今天晚上加班到几点?', choices: ['8:00', '9:00', '10:00', '11:00'], // 也可以使用 choices: [{name: '8点',value: '8:00'}, ...] default: ['11:00'] }
list单选框
{ type: 'list', name: 'list', message: '请问今天晚上加班到几点?', choices: ['8:00', '9:00', '10:00', '11:00'], default: ['11:00'] }
rawlist类型
该类型有些类似list只是这边是由用户自己输入编号
{ type: 'rawlist', name: 'rawlist', message: '你喜欢吃哪种水果', choices: ['apple', 'pear', 'banana', 'grade'], default: 'pear' }
password类型
{ type: 'password', name: 'psw', message: '请输入密码', validate(str) { if(str.length < 6) return '请输入至少6位数的密码' return true } }
Expend类型
该类型相当input由用户自己输入
{ type: 'Expand', name: 'Expand', message: '你喜欢吃哪种水果', default: 'pear', validate(str) { if(str.length < 3) return '长度不能少于3个字符' return true } }
editor类型
{ type: "editor", message: "请输入备注:", name: "editor" }
10、在命令行中执行shell脚本
安装依赖
npm i shelljs --save
用法一:
#!/usr/bin/env node var name = process.argv[2]; var shell = require("shelljs"); shell.exec("echo hello " + name);
用法二:
require('shelljs/global'); if (!which('git')) { echo('Sorry, this script requires git'); exit(1); } mkdir('-p', 'out/Release'); cp('-R', 'stuff/*', 'out/Release'); cd('lib'); ls('*.js').forEach(function(file) { sed('-i', 'BUILD_VERSION', 'v0.1.2', file); sed('-i', /.*REMOVE_THIS_LINE.*\n/, '', file); sed('-i', /.*REPLACE_LINE_WITH_MACRO.*\n/, cat('macro.js'), file); }); cd('..'); if (exec('git commit -am "Auto-commit"').code !== 0) { echo('Error: Git commit failed'); exit(1); }
用法三
const command = `
cp -rf ./abc ./test
touch test.log
rm -rf ./test
`;
_spawn('bash', ['-c', command], {
cwd: 'C:\\Users\\even\\Desktop\\bf',
});
process.exit(0);
其他相关的库:
npmlog: 处理npm日志的库
semver: 版本比对
11、关于路径问题
#!/usr/bin/env node console.log(process.cwd()); //输出当前根目录 console.log(__dirname); //输出bin目录
注意:如果想从git上拉取代码,可以使用download-git-repo的依赖包
12、包的发布
首先注册npmjs.org账号 => npm adduser/npm login (登录) => npm publish(发布)
注意需要在有package.json的目录下进行发布,并且在淘宝镜像下进行操作有可能会失败
切换成npm原始源
npm config set registry https://registry.npmjs.org/
切换成淘宝源
npm config set registry https://registry.npm.taobao.org
12、使用node执行远程服务器的登录以及操作shell可以使用ssh2 ssh2-promise
13、在node中利用child-process在window下执行shell命令报错的问题处理
在window下不支持shell命令,但是可以使用git工具,但是在执行require('child-process').exec('ls -a')的时候报错,这是因为需要配置shell的使用环境, 按 以下步骤进行配置
a、打开bash here => 输入 which ls => 输出 /usr/bin/ls
b、cd /usr/bin => 再输入explorer . 会弹出对应的文件夹,复制文件夹地址 如 C:\Program Files\Git\usr\bin
c、打开环境变量配置,系统环境变量配置,把复制来的地址添加到path下的选项中
重启电脑后,这样就可以愉快的写代码了