一.使用脚手架原因
1.问题汇总
2.脚手架完整的实现流程
3.脚手架执行原理
vue/which/env/node本质都是脚手架
二.创建脚手架
1.脚手架的开发流程
- 创建
npm
项目
//创建完项目在package.json中配置
"bin":{
//imooc-ls这个key表示生成脚手架的名称
//"./bin/index.js"这个value指向脚手架的源码路径
"imooc-ls":"./bin/index.js"
}
- 创建脚手架入口文件,最上方添加
#!/usr/bin/env node
//在入口的index.js文件最上方加上下面这段代码
#!/usr/bin/env node
- 配置
package.json
,添加bin
属性 - 编写脚手架代码
- 将脚手架发布到
npm
//如果本地测试执行
//1.创建可执行文件的权限 终端执行 ./bin/index.js
//2. 执行 npm link 脚手架放到全局 执行本地测试
//3. 可在本地终端上 执行 imooc-ls
执行imooc-ls命令 出现此错误后
解决:
1》Windows+R
2》输入PowerShell
3》运行 Set-ExecutionPolicy -Scope CurrentUser
4》出现ExecutionPolicy: ,填入RemoteSigned
5》输入Y
6》关闭PowerShel ,然后执行get-ExecutionPolicy,发现变成了RemoteSigned,就成功了
2.模拟实现 ls 查看文件列表功能
parseArgs.js文件
module.exports = function parse() {
const args = process.argv.slice(2);
let isAll = false;
let isList = false;
args.forEach((arg) => {
if (arg.indexOf("a") >= 0) {
isAll = true;
}
if (arg.indexOf("l") >= 0) {
isList = true;
}
});
return {
args,
isAll,
isList,
};
};
入口index.js文件
#!/usr/bin/env node
const fs = require("fs");
const parse = require("./parseArgs");
const dir = process.cwd(); //获取文件本地目录
const { args, isAll, isList } = parse();
//console.log(args, isAll, isList);
let files = fs.readdirSync(dir); //读取文件
let output = "";
if(!isAll){
//去掉以点开头的文件
files = files.filter((file) => file.indexOf(".") !== 0);
}
if (!isList) {
// -a
files.forEach((file) => (output += file + "\t"));
console.log(output);
}else {
//-l
files.forEach((file,index) => {
//最后一个元素不加 \n
if(index === files.length - 1){
output += file
}else{
output += file + "\n"
}
});
console.log(output)
}
3.Macos(含linux)文件类型和存储原理
- 3.1 nodejs 判断是不是文件夹
//-l
files.forEach((file,index) => {
const stat = fs.statSync(file)
//! 第一种:node 给了stat.isDirectory()判断是不是文件夹类型
//! 第二种:通过原理 isDir 与 操作老判断 isDir>0为文件夹 等于0则不是文件夹
const mode = stat.mode
const isDir = mode & fs.constants.S_IFDIR;
// bin 16822 => 0100 0001 1011 0110
//fs.constants.S_IFDIR 16384 => 0100 0000 0000 0000
//bin目录对应的权限 0100 000 110 110 110
//---------------- 文件 rw-rw-rw-
//两个二进制数进行 & 操作 两个都为1才为1
console.log(mode,stat.isDirectory(),isDir>0,fs.constants.S_IFDIR)//bin文件夹 打印 16822 true true 16384
//最后一个元素不加 \n
if(index === files.length - 1){
output += file
}else{
output += file + "\n"
}
});
- 3.2 添加权限代码 node中 fs.constans 权限吗介绍
const auth = require('./auth')
const getFileType = require('./getFileType')
//在执行 -l 操作时
files.forEach((file,index) => {
const stat = fs.statSync(file)
//! 2.通过原理 isDir 与 操作老判断 isDir>0为文件夹 等于0则不是文件夹
const mode = stat.mode
const authStr = auth(mode)
const fileType = getFileType(mode)
//最后一个元素不加 \n
if(index === files.length - 1){
output += fileType+authStr +"\t" + file
}else{
output += fileType+authStr +" \t" + file + "\n"
}
});
console.log(output)
新建auth.js判断权限
const fs = require("fs");
module.exports = function auth(mode){
let authString = ''
// u 当前登陆用户的权限
console.log(fs.constants)
const canUserRead = mode & fs.constants.R_OK
const canUserWrite = mode & fs.constants.W_OK
const canUserExecute = mode & fs.constants.S_OK
//依次类推 编写下面两种权限
// 1.group 当前登录用户所在分组
// 2.other 其他用户
canUserRead ? authString += 'r' : authString += '-'
canUserWrite ? authString += 'w' : authString += '-'
canUserExecute ? authString += 'x' : authString += '-'
return authString
}
新建getFileType.js 判断文件类型
const fs = require('fs')
module.exports = function getFileType(mode){
const isDirectory = mode & fs.constants.S_IFDIR===fs.constants.S_IFDIR //判断是否为文件夹
const isFile = mode & fs.constants.S_IFREG===fs.constants.S_IFREG //是否为文件
const isLink = mode & fs.constants.S_IFLNK===fs.constants.S_IFLNK //
if(isDirectory){
return 'd'
}else if(isFile){
return '-'
}else if(isLink){
return 'l'
}
}
4.commander的使用(完整的 node.js 命令行解决方案)
npm install commander
//program方式创建脚手架 处理处理命令后面的参数
const { program } = require('commander')
program.option('--first')
.option('-s,--separator <char>')
program.parse()
const options = program.opts()
const limit = options.first?1:undefined
//imooc-build --first a/b/c
console.log(program.args)//结果 ['a/b/c']
console.log(options.separator,"==")
//输入命令 imooc-build a/b/c --first -s / 以 / 做分隔符 分割 a/b/c这个参数
console.log(program.args[0].split(options.separator,limit))// 结果 ['a']
//console.log(option)
- 使用子命令并带有帮助描述的更完整的程序
生成脚手架的帮助文档 imooc-build -h
生成脚手架command的帮助文档 例如:下面的split
imooc-build help split 或 imooc-build split -h
#!/usr/bin/env node
const { Command } = require('commander')
const program = new Command()
program
.name('imooc-build') //脚手架名称
.description('CLI to build javascript project') //描述 这个脚手架时干嘛的
.version('0.0.1') //脚手架版本号
/*
脚手架默认支持两个命令
1. -h 帮助
2. -V 获取版本号
*/
//编写一个支持 split的功能
//! 通过创建 不同的command 从而让脚手架干不同的事
program
.command('split')
.description('Split string to array') //描述脚手架功能
.argument('<arguments>','string to split')// 参数
.option('--first','display just the first substring')
.option('-s,--separator <char>','separator char',',')//后面 , 表示默认以 , 作为分割符
.action((args,options)=>{//在这里处理脚手架功能
//执行命令 imooc-build split a/b/c --first -s /
console.log(args,options)// a/b/c { separator: ',', first: true }
const limit = options.first?1:undefined
console.log(options.separator) // 结果为 /
console.log(args.split(options.separator,limit))//['a']
// imooc-build split a?b?c --first 上面打印 ['a']
// imooc-build split a?b?c 上面打印 [ 'a', 'b', 'c' ]
})
program.parse()
- options四种调用方式
//当把下面的<char>换成[char]表示后面的字符可以不输入
1. -s <char> 例如 -s /
2. -s<char> 例如 -s/
3. --separator <char> 例如 --separator /
4. --separator=<char> 例如 --separator=/
- option写法表示
//1.以这种写法 结果是boolean值
.option('-e,--ext','display just the first substring')
//2.以下面尖括号的写法 就是具体的value 而且必须要输入它
.option('-a,--add <string>','separator string',',')
//3.以下面中括号的写法 就是具体的value 非必须要填
.option('-a,--add [string]','separator string',',')
- 定义全局脚手架option
在所有脚手架里面都能用
//直接program.option()定义全局的
program
.option('-d, --debug', 'output extra debugging')
.option('-s, --small', 'small pizza size')
.option('-p, --pizza-type <type>', 'flavour of pizza');
//可以用 program.opts()来获取这个option
const options = program.opts();
console.log(options);
const globalOption = program.optsWithGlobals()//和.opts()类似
console.log(globalOption)
//局部的option如上定义在command下 program.opts()获取不到
- 常用选项options可以连写(细节可以参考文档关于options)
多个布尔短选项可以在破折号之后组合在一起,并且可以跟一个取值的单一选项。 例如
-d -s -p cheese
可以写成-ds -p cheese
甚至-dsp cheese
- 必填的option .requiredOption()
可以添加默认值在后面再加上一个参数
program
.requiredOption('-c, --cheese <type>', 'pizza must have cheese');
program.parse();
不填会报下面的错
$ pizza
error: required option '-c, --cheese <type>' not specified
- Variadic option(同时输入多个参数)
定义选项时,可以通过使用
...
来设置参数为可变长参数。在命令行中,用户可以输入多个参数,解析后会以数组形式存储在对应属性字段中。在输入下一个选项前(-
或--
开头),用户输入的指令均会被视作变长参数。与普通参数一样的是,可以通过--
标记当前命令的结束
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);
imooc-build -n 1 2 3 --letter a b c
Options: { number: [ '1', '2', '3' ], letter: [ 'a', 'b', 'c' ] }
Remaining arguments: []
imooc-build --letter=A -n80 operand
Options: { number: [ '80' ], letter: [ 'A' ] }
Remaining arguments: [ 'operand' ]
imooc-build --letter -n 1 -n 2 3 -- operand
Options: { number: [ '1', '2', '3' ], letter: true }
Remaining arguments: [ 'operand' ]
- Version option(版本选项)
参照官网
//执行以下 后 将-V改成 -v 并附加了说明
program.version('0.0.1', '-v, --vers', 'output the current version');
- More configuration(其他选项配置)
参照官网
//测试 addOption
program
.command('test')
//imooc-build test -s
.addOption(new Option('-s,--select','select something').hideHelp())//.hideHelp()隐藏掉-s的帮助文档
//imooc-build test -t 10 表示延迟10秒执行
.addOption(new Option('-t,--timeout <delay>','timeout seconds').default(60,'one minute'))//default默认60秒
//选项功能 imooc-build test -c small
// -c后面只能输入['small','medium','large']这三个选项否则会报错
.addOption(new Option('-c,--choice <choice>','you choice').choices(['small','medium','large']))
//从环境变量里面取值 console.log(process.env) 打印出所有环境变量
//手动指定80端口 imooc-build test -p80
//PORT=80 TEST=1 imooc-build test 对PORT 和 TEST这两个环境变量赋值再输出 可以在git的命令行中试
.addOption(new Option('-p,--port <number>','port number').env('PORT'))
//对输入的结果处理 数量转换 默认支持boolean和string
.addOption(new Option('--donate [amount]','donation in dollars').preset('20').argParser(parseFloat))
//定义此命令和哪些命令产生冲突 互斥
//--disable-server 这个命令和'port','choice'互斥
.addOption(new Option('--disable-server', 'disables the server').conflicts(['port','choice']))
.action((options,cmd)=>{
console.log(cmd.optsWithGlobals())
})
- 自定义选项处理
#!/usr/bin/env node
const commander = require('commander')
const {Command,Option} = commander
function myParseInt(value){
const intValue = parseInt(value,10)
if(isNaN(intValue)){
//commander自带的参数类型校验
throw new commander.InvalidArgumentError('not a int Number')
}
return value
}
function increased(value,previous){
const v = +value
const p = previous || 0
return v + p
}
function collect (value,previous){
return previous.concat([value])
//return value.split(" ")
}
program
.command('custom')
//将输入的字符转成float类型
.option('-f,--float <number>','float argument',parseFloat)
.option('-i,--integer <number>','integer argument',myParseInt)
//对单个参数做出累加 输入imooc-build custom --varbose 10 --varbose 20
//结果:{ varbose: 30 }
.option('--varbose <number>','increased',increased)
//1.针对与集合处理 输入 imooc-build custom -c a -c b -c c
//结果生成abc的数组:{ collect: [ 'a', 'b', 'c' ] }
//2.输入imooc-build custom -c "a b c"要拿到上面的结果 需把collect函数注释的return打开
.option('-c,--collect <value>','collect',collect,[])//默认[]
.action((options,cmd)=>{
console.log(cmd.optsWithGlobals())
})
-
command讲解
login的脚手架
-
.addCommand()讲解