我正在参加「掘金·启航计划」
什么是脚手架?
操作系统的可执行文件,可以通过java、python等语音编写
脚手架执行流程
以vue举例
1.在终端输入 vue create my-app
2.在环境变量 $PATH 中查询 vue 命令(相当于执行 which vue)
3.查询实际链接文件
4.通过 /usr/bin/env node 执行文件
脚手架执行原理
- 在终端输入 vue create my-app
- 终端解析出 vue 命令
- 终端在环境变量中找到 vue 命令
- 终端根据 vue 命令 链接到实际文件 vue.js
- 终端利用 node 执行 vue.js
- vue.js 解析 command/options
- vue.js执行 command
- 执行完毕,退出执行
脚手架开发入门
脚手架开发流程
- 创建 npm 项目
- 创建脚手架入口文件,最上方添加
#!/usr/bin/env node
3.配置 package.json, 添加 bin 属性
4.编写脚手架代码
5.将脚手架发布到 Npm
脚手架搭建和本地调试
1.新建npm 项目
mkdir cf-ls
cd cf-ls
npm init -y
2.配置 package.json
"bin": {
"cf-ls": "./bin/index.js"
},
3.新建入口文件
#!/usr/bin/env node
console.log('hello world')
4.通过link方式进行本地调试,在终端输入npm link
npm link
5.在终端输入 cf-ls,就能看到本地的效果, 也可以看到软链接的指向。
实现 ls 的功能
参数解析
在Node中 通过 process.argv获取参数
#!/usr/bin/env node
console.log(process.argv)
编写 封装 参数 解析方法
// parseArgs.js
module.exports = function parse(){
let isAll = false; // -a
let isList = false; // -l
const args = process.argv.slice(2)
console.log(args)
args.forEach(arg => {
if(arg.indexOf('a') >= 0){
isAll = true;
}
if(arg.indexOf('l') >= 0){
isList = true;
}
})
return {
isAll,
isList,
args
}
}
实现 ls 的效果
#!/usr/bin/env node
const fs = require('fs')
const parse = require('./parseArgs')
const dir = process.cwd();
const { isAll, isList, args} = parse()
if(!isAll && !isList){
let files = fs.readdirSync(dir);
// 过滤 以 . 开头的文件
files = files.filter(file => file.indexOf('.') !== 0)
// 格式化
let output = '';
files.forEach(file => output+=file + '\t')
// 打印
console.log(output)
}
看下效果
实现 ls -a
#!/usr/bin/env node
const fs = require('fs')
const parse = require('./parseArgs')
const dir = process.cwd();
const { isAll, isList, args} = parse()
let files = fs.readdirSync(dir);
let output = '';
if(!isAll && !isList){
// 过滤 以 . 开头的文件
files = files.filter(file => file.indexOf('.') !== 0)
// 格式化
files.forEach(file => output+=file + '\t')
}else if(isAll && !isList){
// 实现 ls -a
files.forEach(file => output+=file + '\t')
}
// 打印
console.log(output)
实现 ls -l
#!/usr/bin/env node
const fs = require('fs')
const parse = require('./parseArgs')
const dir = process.cwd();
const { isAll, isList, args} = parse()
let files = fs.readdirSync(dir);
let output = '';
if(!isAll){
// 过滤 以 . 开头的文件
files = files.filter(file => file.indexOf('.') !== 0)
}
if(!isList){
// 格式化
files.forEach(file => output+=file + '\t')
}else{
// ls -l
files.forEach((file, index) => {
if(index === files.length - 1){
output+=file
}else{
output+=file + '\n'
}
})
}
// 打印
console.log(output)
文件系统
bin: rwx r-x r-x
package.json: rw- r-- r--
r: 访问,w: 编辑 x: 执行
分三组:
第一组:u:当前登录用户
第二组:g:当前登录用户所在分组
第三组:o:其他用户
修改权限:
chmod +r file
\
Unix存储文件类型和权限
Unix使用32位二进制存储文件类型和权限
0000 0000 0000 0000
分组:
0000(文件类型), 000(特殊权限), 000(用户权限), 000(分组权限), 000(其他权限)
实现ls -l显示读写权限
// auth.js
const fs = require('fs')
module.exports = function auth (mode){
let authStr = ''
// user
const canUserRead = mode & fs.constants.S_IRUSR;
const canUserWrite = mode & fs.constants.S_IWUSR;
const canUserExecute = mode & fs.constants.S_IXUSR;
// group
const canGroupRead = mode & fs.constants.S_IRGRP;
const canGroupWrite = mode & fs.constants.S_IWGRP;
const canGroupExecute = mode & fs.constants.S_IXGRP;
// other
const canOtherRead = mode & fs.constants.S_IROTH;
const canOtherWrite = mode & fs.constants.S_IWOTH;
const canOtherExecute = mode & fs.constants.S_IXOTH;
canUserRead ? authStr+= 'r' : authStr+='-'
canUserWrite ? authStr+= 'w' : authStr+='-'
canUserExecute ? authStr+= 'x' : authStr+='-'
canGroupRead ? authStr+= 'r' : authStr+='-'
canGroupWrite ? authStr+= 'w' : authStr+='-'
canGroupExecute ? authStr+= 'x' : authStr+='-'
canOtherRead ? authStr+= 'r' : authStr+='-'
canOtherWrite ? authStr+= 'w' : authStr+='-'
canOtherExecute ? authStr+= 'x' : authStr+='-'
return authStr;
}
在index.js中引入auth.js
const auth = require('./auth')
const authStr = auth(mode)
if(index === files.length - 1){
output+= authStr + '\t' + file
}else{
output+= authStr + '\t' + file + '\n'
}
效果:
实现 ls -l 显示是否文件夹、文件、链接
// getFileType
const fs = require('fs')
module.exports = function getFileType(mode){
const isDir = mode & fs.constants.S_IFDIR;
const isFile= mode & fs.constants.S_IFREG;
const isLink= mode & fs.constants.S_IFLNK;
if(isDir){
return 'd'
}else if(isFile){
return '-'
}else if(isLink){
return 'l'
}
}
在Index.js中引入
const getFileType = require('./getFileType')
const fileType = getFileType(mode)
if(index === files.length - 1){
output+= fileType + authStr + '\t' + file
}else{
output+= fileType + authStr + '\t' + file + '\n'
}
效果:
使用id获取当前用户信息
在终端输入id,可以显示当前用户的权限信息,如下:
从上图看出,当前uid是502,在终端输入 id -un 502, 就能获取当前用户名
在终端输入 id -gn 502,就能获取当前所在组
在终端输入 id -Gn 502 获取所有组
因为是在另一个进程打印,所以需要使用Nodejs提供的child_process来实现在另外一个子进程获取这些信息。
const cp = require('child_process');
module.exports = function getFileUser(stat){
const { uid, gid } = stat;
let userName = cp.execSync('id -un ' + uid).toString().trim();
const groupIdsStr = cp.execSync('id -G ' + uid).toString().trim();
const groupIds = groupIdsStr.split(' ');
const groupIdsNameStr = cp.execSync('id -Gn ' + uid).toString().trim();
const groupIdsName = groupIdsNameStr.split(' ');
const index= groupIds.findIndex(id => +id === + gid)
const groupName = groupIdsName[index];
return userName+ ' ' + groupName;
}
在index.js中引入
const getFileUser = require('./getFileUser')
const fileUser = getFileUser(stat)
if(index === files.length - 1){
output+= fileType + authStr + '\t' + fileUser + '\t' + file
}else{
output+= fileType + authStr + '\t' + fileUser + '\t' + file + '\n'
}
效果:
实现 ls -l 显示创建时间和文件容量
// getFileSizeDate.js
module.exports = function getFileSizeAndDate(stat){
const { mtimeMs, size } = stat;
const birthTime = new Date(mtimeMs)
const month = birthTime.getMonth() + 1;
const date = birthTime.getDate();
const hour = birthTime.getHours();
const minute = birthTime.getMinutes();
return size + ' '+month + '月 ' + date + ' ' + hour + ' ' + minute;
}
在index.js中引入
const getFileSizeAndDate = require('./getFileSizeAndDate')
const fileSizeDate = getFileSizeAndDate(stat);
if(index === files.length - 1){
output+= fileType + authStr + '\t' + fileUser + ' '+ fileSizeDate + ' ' + file
}else{
output+= fileType + authStr + '\t' + fileUser + ' ' + fileSizeDate + ' ' + file + '\n'
}
效果:
实现 文件夹下级文件数量
// index.js
let size = 1;
if(isDir){
// 下级文件
const subDir = fs.readdirSync(file)
size = subDir.length;
}
if(index === files.length - 1){
output+= fileType + authStr + ' ' + size + '\t' + fileUser + ' '+ fileSizeDate + ' ' + file
}else{
output+= fileType + authStr + ' ' + size+ '\t' + fileUser + ' ' + fileSizeDate + ' ' + file + '\n'
}
效果:
增加 自动化测试
安装 mocha
npm install mocha --save-dev
在根目录创建test文件夹
举个例子:新建test.js
const assert = require('assert');
describe('Array', function(){
describe('#indexOf()', function(){
it('should return -1 when the value is not present', function(){
assert.equal([1,2,3].indexOf(4), -1)
})
})
})
配置scripts
"scripts": {
"test": "mocha test/test.js"
},
执行 npm run test
测试通过