脚手架core模块之执行准备

136 阅读4分钟

脚手架拆包策略

1.核心流程: core

2.命令:commands

  • 初始化
  • 发布
  • 清除缓存

3.模型层: models

  • Command命令
  • Project项目
  • Component项目
  • Npm模块
  • Git仓库

4.支撑模块: utils

  • Git操作
  • 云构建
  • 工具方法
  • API请求
  • Git API

image.png

参考lerna源码的思路,core/command/index.js就完成了完整的脚手架;前期例行检查,然后是脚手架的准备阶段,调用runCommand运行命令;运行命令时,先调用初始化方法this.initialize(),然后调用命令的执行方法this.execute();

core模块技术方案

命令执行流程

1.准备阶段

image.png

2.命令注册(本篇文章暂不讲)
3.命令执行(本篇文章暂不讲)

涉及技术点

核心库

  • import-local
  • commander

工具库

  • npmlog
  • fs-extra
  • semver
  • colors
  • user-home
  • dotenv
  • root-check

代码编写

脚手架框架代码搭建

首先在根目录创建core、commands、models、utils四个文件夹; 将我们之前在pakages下面创建的utils和core分别移动到新创建的文件夹中; 在lerna.json加入:

"packages": ["core/*", "models/*", "commands/*", "utils/*"],

进入core/core目录,执行npm link

core/bin/index.js文件:

#! /usr/bin/env node

const importLocal = require("import-local");

if (importLocal(__filename)) {
  require("npmlog").info("cli", "正在使用mj-cli-dev 本地版本");
} else {
  require("../lib/index")(process.argv.slice(2));
}

core/lib/index.js文件中加一个log;

"use strict";

module.exports = core;

function core() {
  // TODO

  console.log("exec core--->");
}

core模块中安装:

cnpm i -S import-local
cnpm i -S npmlog

打印出log

检查版本号功能开发

在core/lib/index.js文件中:

"use strict";

module.exports = core;

const pkg = require("../package.json");

function core() {
  // TODO

  console.log("exec core--->");
  checkPkgVersion();
}

// 检查版本号
function checkPkgVersion() {
  console.log("version--->", pkg.version);
}

输入mj-cli-dev命令;

image.png

require支持加载资源的类型是.js、.json、.node三种;

  • .js时,通过module.exports/exports输出模块;
  • .json文件,通过JSON.parse解析,输出一个对象;
  • .node文件,C++的插件AddOns,process.dlopen去打开一个C++插件;(不常用)
  • 其他文件,默认像.js一样解析

log更加复杂的信息;

 lerna create @mj-cli-dev/log

 lerna add npmlog utils/log/

在log/lib/index.js中修改:

"use strict";

module.exports = index;

const log = require("npmlog");

function index() {
  log.info("cli", "test");
}

然后在core模块中使用log模块; 在core的packages.json中加一个依赖:

"@mj-cli-dev/log": "file:../../utils/log"

接着去utils/log目录下执行npm link;

再回到core目录下执行npm link @mj-cli-dev/log

此时执行mj-cli-dev命令,就可以打印出log日志;

image.png

阅读一下npmlog的源码: 最上面定义了一个log对象,在log上增加一些像useColor等属性,再到最后调用addLevel,addLevel中加的就是我们直接可以调用的方法,比如info;如果我们想调用的方法addLevel中没有,那就不能成功调用了;addLevel中还可以对命令的颜色进行定制;

utils/log/lib/index.js中增加一个success方法:

log.addLevel("success", 2000);

core/core/lib/index.js中调用success方法:

log.success("test", "gogogog");

我们的log就变得五颜六色了

image.png

log中level的设置;

// level支持自定义,支持从环境变量中取
log.level = process.env.LOG_LEVEL ? process.env.LOG_LEVEL : "info";
// 添加一个前缀
log.heading = "mj";

最低Node版本检查

// 检查node版本号
function checkNodeVersion() {
  // 1.获取当前Node版本号
  console.log(process.version);
  // 2.比对最低版本号
}

新建core/lib/const.js文件;

const LOWEST_NODE_VERSION = "12.0.0";

module.exports = {
  LOWEST_NODE_VERSION,
};

安装semver包;

lerna add semver@7.3.4 core/core/

lerna add colors@1.4.0 core/core/

修改core/lib/index.js代码;

"use strict";

module.exports = core;

const semver = require("semver");
const colors = require("colors");
const pkg = require("../package.json");
const log = require("@mj-cli-dev/log");
const constant = require("./const");

function core() {
  try {
    checkPkgVersion();
    checkNodeVersion();
  } catch (e) {
    log.error(e.message);
  }
}
// 检查版本号省略

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

最后报错信息显示;

image.png

检查root启动

root-check可以检查当前是否是root账户登录;如果是的话会自动帮我们降级;

lerna add root-check@1.0.0 core/core/

修改core/lib/index.js文件:

// 检查root
// uid是0就是root账户
function checkRoot() {
  const rootCheck = require("root-check");

  rootCheck();

  console.log(process.geteuid());
}

使用root-check前:

image.png

使用root-check后:

image.png

root-check的原理就是判断当前账户的uid,然后调用setuid方法修改uid,它还会获取当前默认账户的uid,比如502,降级时会修改成502,也可自定义;

检查用户主目录

使用user-home的库,用它帮我们跨操作系统获取用户主目录;还有path-exists这个库,用来判断文件是否存在;

lerna add user-home@2.0.0 core/core/

lerna add path-exists@4.0.0 core/core/

core/lib/index.js增加检查主目录方法:

//检查主目录
function checkUserHome() {
  // userHome不存在
  if (!userHome || !pathExists(userHome)) {
    throw new Error(colors.red("当前登录用户主目录不存在!"));
  }
}

如果主目录不存在会抛出异常;

image.png

user-home的原理:首先获取环境变量process.env,判断当前的环境是win32还是darwin(也就是MacOs),或者是linux;在darwin下会/Users/' + user拼接成主目录;

检查入参

为什么要检查入参呢?主要是看当前是否进使用调试模式,如果要打印debug日志默认是关闭,需要在命令行加上--debug去打印,所以得去全局监听是否有--debug命令,如果存在就需要对log进行设置;是要设置环境变量的LOG_LEVEL,让它等于verbose,这样才能输出verbose日志;

安装minimist库,用它来帮我们检查入参;

lerna add minimist@1.2.5 core/core/

require是一个同步方法;import不是这样,需要放最上面;

core/lib/index.js中增加检查入参的方法;

// 检查入参
// log日志要在checkInputArgs之后
// 第一种改法:checkInputArgs得在require(log)之前执行,才能生效
function checkInputArgs() {
  const minimist = require("minimist");
  args = minimist(process.argv.slice(2));
  console.log("argv", args);
  checkArgs();
}

// 判断参数中是否存在--debug
function checkArgs() {
  // 如果参数中存在--debug
  if (args.debug) {
    process.env.LOG_LEVEL = "verbose";
  } else {
    process.env.LOG_LEVEL = "info";
  }
  // 第二种改法
  log.level = process.env.LOG_LEVEL;
}

检查环境变量

可以在操作系统中配置一些环境变量,将用户名、密码等敏感信息保存在用户本地,不用集成在代码当中,需要的时候就可以实时进行读取;

安装dotenv库

lerna add dotenv@8.2.0 core/core/

修改core/lib/index.js的代码

// 检查环境变量
function checkEnv() {
  // 这一段好像没发挥啥作用
  // const dotenv = require("dotenv");
  // const dotenvPath = path.resolve(userHome, ".env");
  // // 读取环境变量
  // if (pathExists(dotenvPath)) {
  //   config = dotenv.config({
  //     path: dotenvPath,
  //   });
  // }
  createDefaultCliConfig();

  log.verbose("环境变量", config, process.env.CLI_HOME_PATH);
}

// CLI_HOME的默认配置
function createDefaultCliConfig() {
  // 脚手架的主目录
  const cliConfig = {
    home: userHome,
  };
  if (process.env.CLI_HOME) {
    cliConfig["cliHome"] = path.join(userHome, process.env.CLI_HOME);
  } else {
    cliConfig["cliHome"] = path.join(userHome, constant.DEFAULT_CLI_HOME);
  }
  process.env.CLI_HOME_PATH = cliConfig.cliHome;
}

core/lib/const.js中增加一个变量:

const DEFAULT_CLI_HOME = "mj-cli-dev";

最后打印出来prcocess.env.CLI_HOME_PATH;

image.png

检查是否是最新版本

如果别人下载脚手架到本地,运行时有可能不是最新的版本,这时候就需要提醒用户下载最新的版本;

首先在utils/新建一个包,叫做get-npm-info

 lerna create @mj-cli-dev/get-npm-info
 
 lerna add axios@0.21.0 utils/get-npm-info
 
 // 拼接url的库
 lerna add url-join@4.0.1 utils/get-npm-info
 
 // semver
 lerna add semver@7.3.4 utils/get-npm-info

get-npm-info/lib/index.js

"use strict";

const axios = require("axios");
const urlJoin = require("url-join");
const semver = require("semver");

// 发请求查询线上包信息
function getNpmInfo(npmName, registry) {
  if (!npmName) {
    return null;
  }
  const registryNew = registry || getDefaultRegistry();
  const npmInfoUrl = urlJoin(registryNew, npmName);
  console.log("npmName", npmInfoUrl);

  return axios
    .get(npmInfoUrl)
    .then((res) => {
      console.log("res", res.status);

      if (res.status == 200) {
        return res.data;
      } else {
        return null;
      }
    })
    .catch((err) => {
      return Promise.reject(err);
    });
}

// 获取默认的registry
function getDefaultRegistry(isOriginal = false) {
  return isOriginal
    ? "https://registry.npmjs.org/"
    : "https://registry.npm.taobao.org/";
}

async function getNpmVersions(npmName, registry) {
  const data = await getNpmInfo(npmName, registry);
  if (data) {
    return Object.keys(data.versions);
  } else {
    return [];
  }
}

// 查询满足条件的版本号
function getSemverVersions(baseVersion, versions) {
  // 版本号排序
  return versions
    .filter((version) => {
      return semver.satisfies(version, `^${baseVersion}`);
    })
    .sort((a, b) => semver.gt(b, a));
}

async function getNpmserverVersion(baseVersion, npmName, registry) {
  const versions = await getNpmVersions(npmName, registry);
  const newVersions = getSemverVersions(baseVersion, versions);

  if (newVersions && newVersions.length > 0) {
    return newVersions[0];
  } else {
    return [];
  }
}
module.exports = {
  getNpmInfo,
  getNpmVersions,
  getNpmserverVersion,
};

core/lib/index.js增加检查是否是最新版本的方法:

// 检查是否是最新版本
async function checkGlobalUptate() {
  // 1.获取当前的版本号和模块名
  // const currentVersion = pkg.version;
  const currentVersion = "0.0.1";

  // const npmName = pkg.name;
  const npmName = "mj-cli-dev";

  // 2.调用npm API,获取所有的版本号 registry.npms.org+你的模块名称
  const { getNpmserverVersion } = require("@mj-cli-dev/get-npm-info");

  // 3.提取所有的版本号,比对哪些版本号是大于当前版本号的
  const lastestVersion = await getNpmserverVersion(currentVersion, npmName);

  // 4.获取最新的版本号,提示用户更新到该版本
  if (lastestVersion && semver.gt(lastestVersion, currentVersion)) {
    log.warn(
      colors.yellow(
        `更新提示,请手动更新${npmName}的版本号!当前的版本是${currentVersion},最新版本是${lastestVersion}!`
      )
    );
  }
}