750 字
4 分钟
写一个cli脚手架
1 新建项目目录
mkdir my-clicd my-clinpm init —y # 生成package.json 文件2 新建 bin 目录 创建脚本 test.js
#!/usr/bin/env node //指定一个解释器 告诉shell 用node解释器去执行脚本console.log('test')3 在package.json 配置bin 字段
bin字段用来指定各个内部命令对应的可执行文件的位置。
test 有毒不生效 所以这里新建了一个test2.js 脚本(命令)
"bin": { "test": "./bin/test.js", // 好像test 命令有毒 不打印 其他可以 "test2": "./bin/test2.js" },4 运行 npm link (参考博客 npm link 详解)
将包中的任何bin链接到{prefix}/bin/{name} npm config get prefix 查看前缀
test2 命令已被添加到环境变量中

5 执行test2

6 引入commander 添加子命令
test2.js 初始化项目
#!/usr/bin/env nodeconst program = require('commander')// 以package.json 的version 作为test2 的versionprogram.version(require('../package.json').version)program .command('init <name>') .description('init project ') .action(require('../lib/init'))
// 解析主进程参aprogram.parse(process.argv)7 使用 figlet chalk 输出欢迎界面
const { promisify } = require('util')const figlet = promisify(require('figlet'))const chalk = require('chalk') // chalk 5 是ESM chalk 4 是CSMconst log = content => console.log(chalk.green(content))module.exports = async name => { const data = await figlet('Welcome') log(data) log(`下载项目: ${name}`)}
8 download-git-repo 从远程仓库拉取项目
const {clone} = require('./download')// init.slog(`下载项目: ${name}`)// 现在默认分支不是master了 所以要指定分支mainawait clone('github:cxyxiaoyuyu/xinyi-ui#main',name)使用 ora 在下载时显示loading
const { promisify } = require('util')const ora = require('ora')
module.exports.clone = async function(repo,desc){ const download = promisify(require('download-git-repo')) const process = ora(`下载..... ${repo}`) process.start() await download(repo,desc) console.log(repo,desc) process.succeed()}执行完成后项目会多了一个执行命令时输入的目录
9 执行命令 自动安装依赖
使用 spawn 执行 命令
const {spawn} = require('child_process')/**spawn 开一个子进程 用来执行脚本(暂且这么了解吧)_spawn 自己封装的函数 开一个子进程 让子进程的输出信息打印给主进程 并监听子进程结束直接用spawn 命*/const _spawn = async (...args) => { return new Promise((resolve)=>{ const proc = spawn(...args) proc.stdout.pipe(process.stdout) // 为了打印出日志输出 proc.stderr.pipe(process.stderr) proc.on('close',()=>{ // 知道子进程结束了 resolve() }) })}log('安装依赖')await _spawn('npm',['install'],{cwd: `./${name}`})log(`安装完成`)10 自动启动项目
使用open 打开浏览器
log('启动')// 直接写spawn 是在主进程执行 不会打印输出信息// 如果需要打印信息 需要使用自己封装的_spawn 但是这个进程不会结束 所以下面的open 不会执行 所以open 要放在上面// spawn('npm',['run','dev'],{cwd: `./${name}`})
open(`http://localhost:3001`)_spawn('npm',['run','dev'],{cwd: `./${name}`})11 实现约定路由功能
根据views下的文件 自动生成路由 使用handlebars 模板引擎
const fs = require('fs')const handlebars = require('handlebars')
const program = require('commander')program .command('create component <name>') .description('init project ') .action(refresh)program.parse(process.argv)
// module.exports = async () => {async function refresh (name){ console.log(name) // 获取列表 const list = fs.readdirSync('./src/views') .filter(v => v !== 'Home.vue') .map(v => ({ name: v.replace('.vue','').toLowerCase(), file: v })) console.log(list)
// 生成路由定义 compile({list},'./src/router/index.js','./template/router.js.hbs')
// 生成菜单 compile({list},'./src/App.vue','./template/App.vue.hbs')
/** * 模板编译 * @param {*} meta * @param {*} filePath * @param {*} templatePath */ function compile(meta,filePath,templatePath){ if(fs.existsSync(templatePath)){ const content = fs.readFileSync(templatePath).toString() const result = handlebars.compile(content)(meta) fs.writeFileSync(filePath,content) console.log(`${filePath}创建成功`) } }}