超好用的脚手架生成工具cli-creator源码解析

317 阅读1分钟

终端主流程派发

src/main.js

  • 从终端读取用户输入的命令为字符串数组 ["sto","init","vue-manager","mymanager"]
  • 将init命令派发给apply("init","vue-manager","mymanager")
  • 核心代码
/**
 * 定义命令速查表
 * sto commands
 * - config
 * - init
 */
let actionMap = {

  /* init命令配置 */
  init: {
    // init命令描述 终端:sto
    description: "generate a new project from a template",

    // init命令用法提示
    usages: [`${cmdName} init templateName projectName`],
  },

  /* config命令配置 */
  config: {
    // config别名
    alias: "cfg",

    // config 命令描述
    description: `config ${rcFile.name}`,

    // config 用法提示
    usages: [
      `${cmdName} config set <k> <v>`,
      `${cmdName} config get <k>`,
      `${cmdName} config remove <k>`,
    ],
  },

  //other commands
};

/* 派发命令给指定的处理器函数 */
Object.keys(actionMap).forEach((action) => {

  commander
    .command(action)//接收终端命令 init/config
    .description(actionMap[action].description)//添加命令描述
    .alias(actionMap[action].alias) //添加别名

    /* 定义命令的执行逻辑 */
    .action(() => {
      switch (action) {

        case "config":
          /* config真正的命令执行逻辑 */
          // sto config set key value
          // apply("config",key,value)
          apply(action, ...process.argv.slice(3));
          break;

        case "init":
          /* init真正的命令执行逻辑 */
          // sto init templateName projectName
          // apply("init",templateName projectName)
          apply(action, ...process.argv.slice(3));
          break;

        default:
          break;
      }
    });

});

命令派发器

src/index.js

  • 对外导出apply函数
  • apply函数会根据action的名字查询到具体的执行文件(例如init.js)
  • 核心代码
/* 
派发init命令给init.js 
派发config命令给config.js 
*/

// sto init vue-manager mymanager
let apply = (action, ...args) => {
    // 找到init.js导出的处理器函数 调用之
    // init("vue-manager","mymanager")
    require(`./${action}`)(...args);
};

export default apply;

init命令执行器

src/init.js

  • 从终端读取用户输入的工程描述和作者名字
  • 调用init("vue-manager","mymanager")
  • 从配置文件中的用户名对应的github代码仓库中下载vue-manager模板代码,存储在mymanager目录
  • 下载完毕后,根据用户的输入修改package.json文件
  • 核心代码
/* init命令的执行逻辑 下载templateName对应的代码到本地 + 修改目录名称为projectName */
let init = async (templateName, projectName) => {

    //项目不存在
    if (!fs.existsSync(projectName)) {

        //命令行询问器
        inquirer.prompt([
            // 要求用户输入项目描述 将来写入package.json中description字段
            {
                name: 'description',
                message: 'Please enter the project description: '
            },
            {
                name: 'author',
                message: 'Please enter the author name: '
            }
        ])
        
        /* 用户输入完毕以后执行下载 */
        .then(async (answer) => {
            //下载模板 选择模板
            //通过配置文件,获取模板信息
            let loading = ora('🚀🚀 downloading template ...');
            loading.start();

            /* 下载templateName对应的代码到本地 + 修改目录名称为projectName */
            downloadLocal(templateName, projectName)
            
            // 下载完毕
            .then(() => {
                // 标记下载成功
                loading.succeed();

                // 准备写出myserver/package.json
                const fileName = `${projectName}/package.json`;

                // 如果myserver/package.json已经存在
                if(fs.existsSync(fileName)){

                    // 读入本来数据
                    const data = fs.readFileSync(fileName).toString();
                    let json = JSON.parse(data);
                    
                    /* 修改package.json中的数据 */
                    json.name = projectName;
                    json.author = answer.author;
                    json.description = answer.description;
                    
                    // 修改项目文件夹中package.json文件 + 成功提示
                    fs.writeFileSync(fileName, JSON.stringify(json, null, '\t'), 'utf-8');
                    console.log(symbol.success, chalk.green('Project initialization finished!'));
                }

            }, () => {
                loading.fail();
            });

        });

    }
    
    else {
        //项目已经存在
        console.log(symbol.error, chalk.red('The project already exists'));
    }
}

module.exports = init;

下载逻辑

src/utils/get.js

  • 调用download-git-repo的下载方法执行下载
  • 下载后的模板工程目录重命名为指定名称
  • 核心代码
import { getAll } from "./rc";
import downloadGit from "download-git-repo";

import { templates } from "../../project.config.json";

export const downloadLocal = async (templateName, projectName) => {

  let config = await getAll();

  // baseUrl就是github.com 后续内容ouyangsuo/vue-manager
  let api = `${config.registry}/${templateName}`;
  
  console.log();
  console.log(`开始下载[${templates[templateName]}] from`, api);

  return new Promise((resolve, reject) => {
    // 根据配置好的api地址从github上下载模板代码存储在projectName命名的路径下
    downloadGit(api, projectName, (err) => {
      if (err) {
        reject(err);
      }
      resolve();
    });
  });
};