通用脚手架命令Command类封装

脚手架Command类进行优化;

model目录下创建一个新的模块command

js 复制代码
lerna create @mj-cli-dev/command

init模块改成class,并继承于class command;

js 复制代码
"use strict";
const Command = require("@mj-cli-dev/command");

class InitCommand extends Command {}

function init(argv) {
  console.log("init---执行--->", argv);
  return new InitCommand(argv);
}
module.exports = init;
module.exports.InitCommand = InitCommand;

command模块class command的实现;

js 复制代码
"use strict";
class Command {
  constructor(argv) {
    // console.log("Command constructor", argv);
  }

  init() {}

  exec() {}
}
module.exports = Command;

exec模块修改,将类数组转成数组传入init方法:

js 复制代码
require(rootFile).call(null, Array.from(arguments));

终端输入如下命令,即可跑通流程:

js 复制代码
mj-cli-dev init --targetPath /Users/Minjie/Documents/vue3/mj-cli-dev/commands/init --debug

检查Node版本

将core模块中检查Node版本的方法checkNodeVersion移到command模块中;

js 复制代码
class Command {
  constructor(argv) {
    console.log("Command constructor");
    this._argv = argv;
    let runner = new Promise((resolve, reject) => {
      let chain = Promise.resolve();
      chain = chain.then(() => this.checkNodeVersion());
      chain.catch((err) => {
        console.log("err--->", err.message);
      });
    });
  }

  // 检查Node版本放到这里执行
  checkNodeVersion() {
    // 1.获取当前Node版本号
    const currentVersion = process.version;
    // 2.比对最低版本号
    const lowestVersion = constant.LOWEST_NODE_VERSION;
    // 当前版本号 < 最低版本号
    if (!semver.gte(currentVersion, lowestVersion)) {
      throw new Error(
        log.error(`mj-cli-dev需要安装 v${lowestVersion}以上版本的Node.js`)
      );
    }
  }
}

参数初始化操作

全局try catch,但是exec模块里抛出的错误没有捕获,因为是异步所以需要额外的捕获;所以在exec模块中加入try catch进行捕获;

js 复制代码
try {
      require(rootFile).call(null, Array.from(arguments));
    } catch (e) {
      log.error(e.message);
}

在command模块class Command中实现init方法;

command模块的完整代码:

js 复制代码
"use strict";
const log = require("@mj-cli-dev/log");
const semver = require("semver");
const constant = require("./const");
class Command {
  constructor(argv) {
    console.log("Command constructor");
    if (!argv) {
      throw new Error("参数不能为空!");
    }
    if (!Array.isArray(argv)) {
      throw new Error("参数必须为数组!");
    }
    if (argv.length < 1) {
      throw new Error("参数长度必须大于1!");
    }
    this._argv = argv;
    let runner = new Promise((resolve, reject) => {
      let chain = Promise.resolve();
      chain = chain.then(() => this.checkNodeVersion());
      chain = chain.then(() => this.initArgs());
      chain = chain.then(() => this.init());
      chain = chain.then(() => this.exec());
      chain.catch((err) => {
        console.log("err--->", err.message);
      });
    });
  }

  // 检查Node版本放到这里执行
  checkNodeVersion() {
    // 1.获取当前Node版本号
    const currentVersion = process.version;
    // 2.比对最低版本号
    const lowestVersion = constant.LOWEST_NODE_VERSION;
    // 当前版本号 < 最低版本号
    if (!semver.gte(currentVersion, lowestVersion)) {
      throw new Error(
        log.error(`mj-cli-dev需要安装 v${lowestVersion}以上版本的Node.js`)
      );
    }
  }
  // 参数初始化
  initArgs() {
    this._cmd = this._argv[this._argv.length - 1];
    this._argv = this._argv.slice(0, this._argv.length - 1);
  }
  // 命令的准备阶段
  init() {
    throw new Error("init 必须实现");
  }

  exec() {
    throw new Error("exec 必须实现");
  }
}
module.exports = Command;

init模块的完整代码:

js 复制代码
"use strict";
const Command = require("@mj-cli-dev/command");
const log = require("@mj-cli-dev/log");

class InitCommand extends Command {
  init() {
    this.projectName = this._argv[0] || "";
    this.force = !!this._cmd.force;
    log.info("projectName", this.projectName, this.force);
  }
  exec() {
    console.log("int exec--->");
  }
}

function init(argv) {
  console.log("init---执行--->", argv);
  return new InitCommand(argv);
}
module.exports = init;
module.exports.InitCommand = InitCommand;

利用Node多进程动态执行命令

第一种实现方式是用spawn方法;

js 复制代码
const code = "";
const child = cp.spawn("node", ["-e", code], {
cwd: process.cwd(),
});
child.stdout.on("data", (chunk) => {});
child.stderr.on("data", (chunk) => {});

第二种实现方式用inherit,将process.stdin、process.stdout、process.stderr直接和父进程进行绑定,:

js 复制代码
const code = "console.log(1)";
const child = cp.spawn("node", ["-e", code], {
    cwd: process.cwd(),
    stdio: "inherit",
});
child.on("err", (e) => {
    log.error(e.message);
    process.exit(1);
});
child.on("exit", (e) => {
    log.info("命令执行完成", e);
    process.exit(e);
});

再次优化代码:

js 复制代码
  const args = Array.from(arguments);
  const cmd = args[args.length - 1];
  // 去除cmd中无用的属性
  const obj = Object.create(null);
  Object.keys(cmd).forEach((key) => {
    if (
      cmd.hasOwnProperty(key) &&
      !key.startsWith("_") &&
      key !== "parent"
    ) {
      obj[key] = cmd[key];
    }
  });
 
  args[args.length - 1] = obj;
  const code = `require('${rootFile}').call(null, ${JSON.stringify(args)})`;

  const child = cp.spawn("node", ["-e", code], {
    cwd: process.cwd(),
    stdio: "inherit",
  });
  child.on("error", (e) => {
    log.error(e.message);
    process.exit(1);
  });
  child.on("exit", (e) => {
    log.info("命令执行完成", e);
    process.exit(e);
  });

兼容windows系统,封装成通用的方法:

js 复制代码
function spawn(command, args, options) {
  // 判断是不是win32
  // win32的命令:cp.spawn("cmd", ["/c", "node", "-d", code])
  const win32 = process.platform === "win32";

  const cmd = win32 ? "cmd" : command;
  const cmdArgs = win32 ? ["/c"].concat(command, args) : args;

  return cp.spawn(cmd, cmdArgs, options || {});
}
相关推荐
码农秋26 分钟前
Element Plus DatePicker 日期少一天问题:时区解析陷阱与解决方案
前端·vue.js·elementui·dayjs
未来之窗软件服务30 分钟前
未来之窗昭和仙君(五十六)页面_预览模式——东方仙盟筑基期
前端·仙盟创梦ide·东方仙盟·昭和仙君·东方仙盟架构
top_designer32 分钟前
Illustrato:钢笔工具“退休”了?Text to Vector 零基础矢量生成流
前端·ui·aigc·交互·ux·设计师·平面设计
星哥说事37 分钟前
星哥带你玩飞牛NAS-13:自动追番、订阅下载 + 刮削,动漫党彻底解放双手!
前端
donecoding1 小时前
前端AI开发:为什么选择SSE,它与分块传输编码有何不同?axios能处理SSE吗?
前端·人工智能
安_1 小时前
<style scoped>跟<style>有什么区别
前端·vue
姝然_95271 小时前
Claude Code 命令完整文档
前端
wjcroom1 小时前
web版进销存的设计到实现一
前端
无知的前端1 小时前
Flutter常见问题以及解决方案
前端·flutter·dart
BD_Marathon1 小时前
Vue3_Vite构建工程化前端项目
前端