自定义脚手架
例如我们要创建一个脚手架名字叫 my-cli, 安装命令 my-cli create <projectName>
- 准备插件
commander
命令行参数解析器inquirer
命令行交互模块axios
网络请求download-git-repo
git模板下载插件自动解压ora
命令行loadingchalk
命令行文字样式
- package.json 配置
"main": "lib/index.js","bin": {"my-cli": "bin/my-cli.js"},"type": "module", //es6 模块// bin 下面的配置会在客户端安装后创建符号连接 然后就可以使用my-cli 命令// main 下面是需要导出的模块
- bin/my-cli.js
import {program} from 'commander'
program
.command('create <projectName>') //定义命令
.description('创建项目') //描述
.option('-f, --force', 'can overwrite target directory if it exists') //配置参数,这里为是否覆盖 值true/false
.action((projectName, options) => { import('../lib/create.js').then(module => {//../lib/create.js 为模板下载文件// options 为提供的参数例如 my-cli create newPro --force, 这里会获取到forcemodule.default(projectName, options) //执行})
})
program.parse(process.argv)
- lib/create.js
import fs from "fs"; //node 文件模块
import path from "path";
import inquirer from "inquirer";
import Generate from "./generate.js"; //模板下载类
export default async function createTemplate(name, options) {const pwd = process.cwd(); //当前绝对路径const targetAir = path.join(pwd, name); //模板文件路径if (fs.existsSync(targetAir)) { //如果存在同名文件if (options.force) { //是否覆盖fs.rmSync(targetAir, { recursive: true, force: true }); //删除} else {const res = await inquirer.prompt([ //命令行交互 是否覆盖{type: "confirm",name: "cover",message: "Target directory already exists. Continue?",default: false,choices: [ //命令行选项{name: "Overwrite",value: true,},{name: "Cancel",value: false,},],},]);if (!res.cover) {return;}fs.rmSync(targetAir, { recursive: true, force: true });}}const generate = new Generate(name, targetAir);generate.create(); //开始创建
- lib/generate
import ora from "ora";
import { getRepoList, getTagList } from "./http.js"; //获取模板名称以及版本号,这两个函数就是axios请求,可以根据自己的模板实现下
//示例// export const getRepoList = async () => {
// const { data } = await axios.get('xxx')
// return data
// }import inquirer from "inquirer";
import downloadGitRepo from "download-git-repo";
import { promisify } from "util"; //node内置模块 回调函数转 Promise
import path from "path";
import chalk from "chalk"; //命令行文字颜色样式
//定义loading 函数
async function wrapLoading(fn, message, ...args) {message = message || "waiting download ...";const spinner = ora(message);spinner.start(); //开始loadingtry {let data = await fn(...args);spinner.succeed(); //结束loadingreturn data;} catch (error) {if(error.code === 'ENOENT' && error.syscall === 'link'){//这里为因为客户端windows原因download-git-repo创建符号链接错误,本地测试忽略return true}spinner.fail();throw error;}
}export default class Generate {constructor(name, targetAir) {this.name = name;this.targetAir = targetAir;this.downloadGitRepo = promisify(downloadGitRepo);}async create() {// 获取模板名称const repo = await this.getRepo(); //获取模板名称const tag = await this.getTag(repo); //获取版本号this.downTemplate(repo, tag); //下载模板}async getRepo() {const repos = await wrapLoading(getRepoList, "waiting download template list ... ");const repo = await inquirer.prompt([ //命令交互选择模板{type: "list",name: "repo",message: "please choose a template to create project",choices: repos.map(item => item.name),},]);return repo.repo;}async getTag(repo) { const tags = await wrapLoading(getTagList, "waiting download template tag list ... ", repo);const tag = tags[0]; //默认最新版本号return tag.name;}async downTemplate(repo, tag) {const templateLink = `xxx/${repo}#${tag}`; //这里注意下download-git-repo 插件会自动拼上 https://github.com/为头await wrapLoading(this.downloadGitRepo,"waiting download template ... ",templateLink,path.resolve(process.cwd(), this.name), //模板的绝对路径);console.log(`/r/nSuccessful download template ${chalk.cyan(this.name)}`); //下载模板成功提示}
}