例如我们要创建一个脚手架名字叫 my-cli, 安装命令 my-cli create <projectName>
- 准备插件
commander
命令行参数解析器inquirer
命令行交互模块axios
网络请求download-git-repo
git模板下载插件自动解压ora
命令行loadingchalk
命令行文字样式
- package.json 配置
javascript
"main": "lib/index.js",
"bin": {
"my-cli": "bin/my-cli.js"
},
"type": "module", //es6 模块
// bin 下面的配置会在客户端安装后创建符号连接 然后就可以使用my-cli 命令
// main 下面是需要导出的模块
- bin/my-cli.js
javascript
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, 这里会获取到force
module.default(projectName, options) //执行
})
})
program.parse(process.argv)
- lib/create.js
javascript
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
javascript
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(); //开始loading
try {
let data = await fn(...args);
spinner.succeed(); //结束loading
return 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)}`); //下载模板成功提示
}
}