自定义脚手架

例如我们要创建一个脚手架名字叫 my-cli, 安装命令 my-cli create <projectName>

  • 准备插件
    • commander 命令行参数解析器
    • inquirer 命令行交互模块
    • axios 网络请求
    • download-git-repo git模板下载插件自动解压
    • ora 命令行loading
    • chalk 命令行文字样式
  • 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)}`); //下载模板成功提示
  }
}
相关推荐
子兮曰5 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰5 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万5 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝5 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋5 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁5 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
李少兄6 天前
JavaScript 隐式全局变量解析
javascript
汉堡大王95276 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大6 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师6 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端