实现自定义脚手架mfy-cli(二)
写在前面:程序猿1024又要到了,在即将来临的周末,迎来了程序猿节,在这个“伟大”的节日,也收到了来自公司的零食~😄
步入正轨:
实现自定义脚手架mfy-cli(一) 实现自定义脚手架mfy-cli(二)
前一篇文章主要介绍了基础配置和创建模版的实现,这篇主要去介绍关于文件的操作;对于文件操作,其实更多的是对文件系统的依赖,写完创建文件,自己最大的感受其实不是技术上的难点,而是对交互流程的思考和设计,这个时候其实就是在我们想要创建的时候就去想下自己要实现什么?虽然在开始的时候也写下了大概的功能,但是在实际实现的时候还修改了很多,总觉的哪里交互不好;可能是被需求支配的后遗症哈哈哈哈!!!
命令创建文件
mfy-cli add <fileName> -f
mfy-cli add <fileName>
fileName 作为我们自主输入的文件名字,其中包含了很多内容;
- fileName 直接为文件,含文件后缀 此时我们直接校验创建即可
mfy-cli add mfy.js
- fileName 输入不包含文件后缀名字,此时需要进行文件后缀选择,创建相应的文件
mfy-cli add mfy
- fileName 如果输入的包含/等,则是文件目录/文件名字 包含文件名字后缀
mfy-cli add mfy/mfy.js
- fileName 输入也是文件/文件名字 不包含文件后缀 还是需要验证进行选择创建
mfy-cli add mfy/mfy
解析命令
program.command('add [path]') .description("add dirtory or file") .option("-f,--force", 'overwrite target if it exists') .option("-t,--template", 'create teemplate') .action((path, cmd) => { let args = clearArgs(cmd); //调用config if(!path && (args && !args.template)) { log(chalk.red.bold("You must to add the path")) return ; } require('../libs/command/add.js')(path, clearArgs(cmd)) })
根据输入的文件格式不同,进行不同的处理操作
1.校验输入的fileName 是否包含文件后缀,如果不包含,则直接进行选择框选择要创建的文件后缀
async function createSingleFile(fileDir, options) { //只能输入相对路径 if (path.isAbsolute(fileDir)) { log.error("pleacse input relative path") process.exit(0) } const { dir, base, ext } = path.parse(fileDir) //1.判断当前最后一个文件是否有后缀,如果没有后缀,则进行后缀选择,如果有直接创建文件 let currentfileName = base, resultExt = [ext]; if (!ext) { resultExt = await inquirer.prompt([ { name: 'fileExt', type: 'checkbox',//类型比较丰富 message: "please select file's type", choices: fileExtList,//文件后缀['.js', '.ts', '.vue', '.json', '.less', '.css', '.scss']
validate: function (value) { var done = this.async(); setTimeout(function () { if (value.length == 0) { done('You must select type of file'); return; } done(null, true); }, 0); } },] ) resultExt = resultExt.fileExt; } resultExt = resultExt.map(item => currentfileName + item) await createFileEvent(resultExt, dir, options.force) // log.success("All files have completed") }
选择完成后,将得到需要创建文件类型的后缀,数组
[ '.js', '.ts', '.vue', '.json', '.less' ]
根据得到的数组获取到当前要创建的文件的数据map
根据输入的文件夹目录和文件数组map进行多文件的创建即可;
创建文件的流程,基本和创建文件模版的流程大概类似
具体的判断代码实现:此时配置了一些基础模版,如果匹配上了,则直接进行模版匹配操作哦
async function createFile(fileName, force) { const { ext } = path.parse(fileName) const fileExt = ext; //2.判读文件是否存在ext if (fse.pathExistsSync(fileName)) { if (!force) { log.warning(`the file ${fileName} have existed`); //进行选择是否继续创建 如果继续创建则删除源文件 let op = await inquirer.prompt([{ name: 'confirm', type: 'confirm', message: `Do you want to continue to create file ? if this will delete the same file`, }]) if (!op.confirm) { return { type: false, msg: `${fileName} ^ Cancel create` } } } fse.removeSync(fileName); } //3.如果是.vue文件或者是.json文件进行文本的copy const tplPath = path.join(__dirname, '../../template/index' + fileExt); if (templateConfig.indexOf(fileExt) != -1) { await fse.copy(tplPath, fileName, err => { if (err) { log.error(err) process.exit(0) } }) } else { //3.进行创建文件 先获取创建文件的路径 await fse.ensureFile(fileName, err => { if (err) { log.error(err) process.exit(0) } }) } return { type: true, msg: `${fileName} ^ Create successful` }; }
🤯重点到了 ,我们创建文件的时候其实是存在异步创建的,这样我们创建多个文件的时候,每个文件存在的话都需要询问是否继续创建,就需要我们保证我们的多文件创建步骤是一个串行相互不影响的创建,借助node中的async的方法进行事件的顺序执行
const async = require('async') /** * 创建异步串行操作函数 函数之间无影响,进行串行执行即可 */ function createSerialEventNoRe(arrFun,singleCallback,allCallBack){ if(!singleCallback) { console.log("params error!") } //创建时间的队列 let funArr = arrFun.map((item) => { return async function (callback) { //调用单个执行完成的回调 singleCallback && await singleCallback(item); callback(null) } }) async.series(funArr, (err, result) => { if(err){ process.exit(0) } if (result.length == funArr.length) { allCallBack(result) } }) } module.exports={ createSerialEventNoRe:createSerialEventNoRe }
创建文件
/** * 创建文件的异步函数 * @param {*} arr 当前文件的文件名称 [index.js,c.js] * @param {*} dirName 文件夹名称 * @param {*} force 是否需要强制去更新 */ async function createFileEvent(arr, dirName, force) { let resArr=[] createSerialEventNoRe(arr,async (item)=>{ let fileName = path.join(dirName, item) let result = await createFile(fileName, force); resArr.push(result) },(res)=>{ printLog(resArr) }) }
简单的添加文件就完成啦~
当fileName 输入的内容是路径/文件名字格式的时候,其实我们创建不需要创建文件,fs模块会为我们创建的;
创建模版
写完创建文件后 ,觉得其实创建一个模版,基本原理也是一样的
mfy-cli add templateName -t
/** * 创建文件模版 * @param {*} templateName 输入要创建的模版名称 */ async function createTemplate(templateName) { /** * 1.模版名字输入是否合理 没有模版则进行添加文件名称 文件中不包含/字符 * */ //2.输入模版名字 //2.1 选择创建的样式类型 let createDirName = templateName; if (!createDirName) { let dirInputName =await inquirer.prompt([{ name: 'dirname', type: 'input', message: 'Please input the template name:' }]) createDirName = dirInputName.dirname; } if (/\//g.test(createDirName)) { log.error("The template name has special icon '/', Can't to create ,please operate again") process.exit(1) } //当前文件夹是否存在 if (fse.pathExistsSync(createDirName)) { log.warning("Dirtory has exists will delete...") fse.removeSync(createDirName) } //选择要进行生成的模版类型 let type = Object.keys(addTemplate); let resultTemType = await inquirer.prompt([{ name: 'type', type: 'list', choices: type, message: 'Please select template type' }]) let templateType = resultTemType.type; await createFileEvent(addTemplate[templateType], createDirName, true) log.success(`👋 ${createDirName} 👋Template create successfully`) }
这里需要注意的是,可以先配置好相应模版需要创建的文件
addTemplate:{ 'vue':['index.vue','index.less','index.js'], 'react':['index.jsx','index.less',] },
好了 模版也创建好啦
命令删除文件
配置命令
program.command('delete [path]') .description("delete dirtory or file") .action((path, cmd) => { //path console.log(path) console.log(cmd) //调用config if(!path) { log(chalk.red.bold("You must to add path")) return ; } //输入可能是正则 ,因此会匹配相关的文件,需要进行处理 let args = cmd.args; require('../libs/command/delete.js')(args, clearArgs(cmd)) })
解析命令
删除文件相对于创建文件而言就比较简单了,我们只需要进行参数的校验即可,此时我们支持删除的文件名字格式也是很多
- 删除文件夹
-
mfy-cli delete file
- 删除某个文件夹下的同名文件
-
mfy-cli delete file/f.*
- 删除某个文件夹下的同类型文件
-
mfy-cli delete file/*.js
当我们在命令行输入类似正则的是如*.js、index.* 在命令行中会获取到所有的匹配文件列表,不需要我们自己进行获取
查看代码:
/** * 删除项目 */ const { fse, inquirer} = require("../tools/module") const loading = require("../modules/Loading") const path = require("path") const log = require("../modules/Log") module.exports = async (fileDirArr, args) => { /** * 1.判断当前项目是否存在 * 2.存在进行提示 是否删除 * 3.不存在进行提示 */ //正则匹配到文件内容 let fileLists=[]; //正则匹配式 删除文件 if(fileDirArr.length>1){ fileLists = fileDirArr; //文件存在 判断是否是文件夹 如果是文件夹并且内部包含文件 则进行提示 选择 fileLists.forEach(file=>{ let filePath = file let fi = fse.statSync(filePath) if(fi.isFile()){ fse.removeSync(filePath) } }) log.success("\rDelete successfully") return ; } let fileDir = fileDirArr[0] if (!fse.existsSync(fileDir)) { log.warning("the filename of " + fileDir + " is not exists") process.exit(0) } let result = fse.statSync(fileDir); if (result.isDirectory()) { let files = fse.readdirSync(fileDir) if (files.length > 0) { let op = await inquirer.prompt([{ name: 'confirm', type: 'confirm', message: `This dirtory has file ,Do you want to continue to delete it ?`, }]) if (!op.confirm) { log.info("You cancel this operation!") process.exit(0) } } } //直接进行删除即可 loading.show("Deleting files....") try { fse.removeSync(fileDir) loading.succeed(); log.success("\rDelete successfully") } catch (err) { loading.fail(err); } }
删除文件夹时候,会先判断文件夹中的内容是否存在,进而进行删除
删除的命令 就完成啦~~