脚手架安装项目模板功能实现

脚手架安装项目模板架构设计

mongodb中数据修改,增加一个字段type,为normalcustom

判断是否为自定义模板

js 复制代码
 async installTemplate() {
    if (this.templateInfo) {
      if (!this.templateInfo.type) {
        // 赋默认值
        this.templateInfo.type = TEMPLATE_TYPE_NORMAL;
      }
      if (this.templateInfo.type === TEMPLATE_TYPE_NORMAL) {
        // 标准安装
        await this.installNormalTemplate();
      } else if (this.templateInfo.type === TEMPLATE_TYPE_CUSTOM) {
        // 自定义安装
        await this.installCustomTemplate();
      } else {
        throw new Error("项目模版类型无法识别!");
      }
    } else {
      throw new Error("项目模版信息不存在!");
    }
  }

拷贝项目模板功能

js 复制代码
 // 标准安装
  async installNormalTemplate() {
    let spinner = spinnerStart("正在安装模版");
    try {
      //拷贝模版代码至当前目录
      const templatePath = path.resolve(
        this.templateNpm.cacheFilepath,
        "template"
      );
      const targetPath = process.cwd();

      // 确保目录存在,不存在会创建一个
      fse.ensureDirSync(templatePath);
      fse.ensureDirSync(targetPath);
      fse.copySync(templatePath, targetPath);
      await sleep();
    } catch (e) {
      throw e;
    } finally {
      spinner.stop(true);
      log.success("模版安装成功");
    }
  }

项目模板安装依赖和启动命令

步骤如下: 1.依赖安装 2.启动命令执行

修改一下mongodb的数据;

js 复制代码
db.project.update({'name':'vue3标准模版'},{$set:{'installCommand':'npm install', 'startCommand':'npm run serve'}})
commands/init/lib/index.js 复制代码
async installTemplate() {
    if (this.templateInfo) {
      if (!this.templateInfo.type) {
        // 赋默认值
        this.templateInfo.type = TEMPLATE_TYPE_NORMAL;
      }
      if (this.templateInfo.type === TEMPLATE_TYPE_NORMAL) {
        // 标准安装
        await this.installNormalTemplate();
      } else if (this.templateInfo.type === TEMPLATE_TYPE_CUSTOM) {
        // 自定义安装
        await this.installCustomTemplate();
      } else {
        throw new Error("项目模版类型无法识别!");
      }
    } else {
      throw new Error("项目模版信息不存在!");
    }
  }
  // 标准安装
  async installNormalTemplate() {
    let spinner = spinnerStart("正在安装模版");
    try {
      //拷贝模版代码至当前目录
      const templatePath = path.resolve(
        this.templateNpm.cacheFilepath,
        "template"
      );
      const targetPath = process.cwd();

      // 确保目录存在,不存在会创建一个
      fse.ensureDirSync(templatePath);
      fse.ensureDirSync(targetPath);
      fse.copySync(templatePath, targetPath);
      await sleep();
    } catch (e) {
      throw e;
    } finally {
      spinner.stop(true);
      // log.success("模版安装成功");
    }
    // 依赖安装
    const { installCommand, startCommand } = this.templateInfo;
    let installRes;
    if (installCommand && installCommand.length > 0) {
      const cmdArr = installCommand.split(" ");
      const cmd = cmdArr[0];
      const args = cmdArr.slice(1);

      installRes = await execAsync(cmd, args, {
        stdio: "inherit",
        cwd: process.cwd(),
      });
    }
    if (installRes !== 0) {
      throw new Error("依赖安装失败");
    }
    // 启动命令执行
    let startRes;
    if (startCommand && startCommand.length > 0) {
      const cmdArr = startCommand.split(" ");
      const cmd = cmdArr[0];
      const args = cmdArr.slice(1);

      startRes = await execAsync(cmd, args, {
        stdio: "inherit",
        cwd: process.cwd(),
      });
    }
  }

白名单命令检测功能

防止mongodb中isntallCommand被人篡改;所以在执行命令之前增加白名单检测;

js 复制代码
// 白名单命令
const WHITE_COMMAND = ["npm", "cnpm"];

// 白名单命令检测
checkCommand(cmd) {
    if (WHITE_COMMAND.includes(cmd) > 0) {
      return cmd;
    }
    return null;
}
async execCommand(commands, errorMsg) {
    let commandsRes;
    if (commands && commands.length > 0) {
      const cmdArr = commands.split(" ");
      const cmd = this.checkCommand(cmdArr[0]);
      const args = cmdArr.slice(1);

      if (cmd) {
        commandsRes = await execAsync(cmd, args, {
          stdio: "inherit",
          cwd: process.cwd(),
        });
      } else {
        throw new Error("白名单命令检测失败");
      }
    }
    if (installRes !== 0) {
      throw new Error(errorMsg);
    }
    return commandsRes;
}
async installNormalTemplate() {
    await this.execCommand(installCommand, "依赖安装失败");
    await this.execCommand(startCommand, "项目启动失败");
}

ejs相关开发

项目名称自动格式化

mj-cli-dev-template/mj-cli-dev-template-vue3/templage/package.json中修改:

package.json 复制代码
{
  "name": "<%= className %>",
  "version": "<%= version %>",
js 复制代码
lerna add kebab-case@1.0.0 commands/init 

packageName格式化:AbcEfg => abc-efg

commands/init/lib/index.js 复制代码
getProjectInfo(){
    // 略
    if (projectInfo.projectName) {
        // AbcEfg => abc-efg
        projectInfo.className = require("kebab-case")(
          projectInfo.projectName
        ).replace(/^-/, "");
    }
    // 添加两个变量,ejs模板使用
      if (projectInfo.projectVersion) {
        projectInfo.version = projectInfo.projectVersion;
      }
}
commands/init/lib/index.js 复制代码
async installNormalTemplate() {
    const opts = {
         ignore: ["node_modules/**", "public/**"],
    };
    await this.ejsRender(opts);
}

// ejs模板关键方法
  async ejsRender(options) {
    const dir = process.cwd();
    return new Promise((resolve, reject) => {
      require("glob")(
        "**",
        {
          cwd: dir,
          ignore: options.ignore,
          nodir: true,
        },
        (err, files) => {
          if (err) {
            reject(err);
          }
          Promise.all(
            files.map((file) => {
              const filePath = path.join(dir, file);

              return new Promise((resolve1, reject1) => {
                ejs.renderFile(
                  filePath,
                  this.projectInfo,
                  {},
                  (err, result) => {
                    if (err) {
                      reject1(err);
                    } else {
                      // ejs模板转换不成功,渲染结果需重新写入
                      fse.writeFileSync(filePath, result);
                      resolve1(result);
                    }
                  }
                );
              });
            })
          )
            .then(() => {
              resolve();
            })
            .catch((err) => {
              reject(err);
            });
        }
      );
    });
  }

安装成功后,本地的目录pacakgeName和version就是我们手动输入的值;

跑一下整个流程是可以成功启动项目的!

init命令直接传入项目名称功能支持

如果输入的命令中带有projectName,就不显示【请输入版本号】的命令;

getProjectInfo方法改造;

commands/init/lib/index.js 复制代码
// 获取基本信息
  async getProjectInfo() {
    function isValidName(v) {
      return /^[a-zA-Z]+([-][a-zA-Z][a-zA-Z0-9]*|[_][a-zA-Z][a-zA-Z0-9]*|[a-zA-Z0-9])*$/.test(
        v
      );
    }
    // 1.选择创建项目或组件
    let type = "";
    let projectInfo = {};
    let isProjectNameValid = false;

    if (this.projectName != "" && isValidName(this.projectName)) {
      isProjectNameValid = true;
      projectInfo.projectName = this.projectName;
    }
    type = (
      await inquirer.prompt([
        {
          type: "chioce",
          message: "请选择初始化类型",
          default: TYPE_PROJECT,
          name: "type",
          chioces: [
            {
              name: "项目",
              value: TYPE_PROJECT,
            },
            {
              name: "组件",
              value: TYPE_COMPONENT,
            },
          ],
        },
      ])
    ).type;

    if (type === TYPE_PROJECT) {
      // 2.获取项目的基本信息
      let projectPrompt = [
        {
          type: "input",
          name: "projectVersion",
          message: "请输入版本号",
          default: "1.0.0",
          validate: function (v) {
            const done = this.async();

            setTimeout(function () {
              if (!!!semver.valid(v)) {
                done("请输入合法的版本号");
              } else {
                done(null, true);
              }
            }, 0);
          },
          filter: function (v) {
            if (!!semver.valid(v)) {
              return semver.valid(v);
            } else {
              return v;
            }
          },
        },
        {
          type: "list",
          name: "projectTemplate",
          message: "请选择项目模板",
          choices: this.createTemplateChioice(),
        },
      ];
      const projectNamePrompt = {
        type: "input",
        name: "projectName",
        message: "请输入项目名称",
        default: "",
        validate: function (v) {
          const done = this.async();

          // Do async stuff
          setTimeout(function () {
            // 1.输入的首字符;
            // 2.尾字符必须是英文字符或数字,不能为字符
            // 3.字符近允许是"-_""
            if (!isValidName(v)) {
              done("请输入合法的项目名称");
            } else {
              done(null, true);
            }
          }, 0);
        },
        filter: function (v) {
          return v;
        },
      };

      if (!isProjectNameValid) {
        projectPrompt.unshift(projectNamePrompt);
      }
      const obj = await inquirer.prompt(projectPrompt);
      projectInfo = {
        ...projectInfo,
        type,
        ...obj,
      };
      if (projectInfo.projectName) {
        // AbcEfg => abc-efg
        projectInfo.className = require("kebab-case")(
          projectInfo.projectName
        ).replace(/^-/, "");
      }
      if (projectInfo.projectVersion) {
        projectInfo.version = projectInfo.projectVersion;
      }
    } else if (type === TYPE_COMPONENT) {
    }

    // return 项目的基本信息object
    return projectInfo;
  }

项目初始化的流程就结束了。

相关推荐
Jiaberrr7 分钟前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho1 小时前
【TypeScript】知识点梳理(三)
前端·typescript
安冬的码畜日常2 小时前
【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
开发语言·前端·javascript·信息可视化·数据可视化·d3.js
小白学习日记2 小时前
【复习】HTML常用标签<table>
前端·html
丁总学Java3 小时前
微信小程序-npm支持-如何使用npm包
前端·微信小程序·npm·node.js
yanlele3 小时前
前瞻 - 盘点 ES2025 已经定稿的语法规范
前端·javascript·代码规范
懒羊羊大王呀3 小时前
CSS——属性值计算
前端·css
DOKE4 小时前
VSCode终端:提升命令行使用体验
前端
xgq4 小时前
使用File System Access API 直接读写本地文件
前端·javascript·面试