【架构师(第七篇)】脚手架之准备阶段编写

2022-12-10 13:23:30 浏览数 (1)


根据模块划分调整工程结构

  • 核心模块: Core
  • 命令模块: Command
  • 模型模块: Model
  • 工具模块: Utils
代码语言:javascript复制
hzw-cli-dev
├── command // 命令模块
├── core // 核心模块
├── utils // 工具模块
├── models // 模型模块
├── node_modules // 依赖
├── package-lock.json 
├── package.json 
└── lerna.json // lerna 配置文件

准备阶段流程图

import-local

代码语言:javascript复制
// coreclibinindex.js
#! /usr/bin/env node

const importLocal = require('import-local')

// 如果当前项目中的 node_modules 中存在一个脚手架命令,全局的 node 环境中也存在一个脚手架命令的时候
// import-local 会优先选用项目中 node_modules 的版本,然后输出 log
if (importLocal(__filename)) {
  require('npmlog').info('提示', '正在使用当前项目中 hzw-cli-dev 的版本')
} else {
  // 使用全局下的脚手架命令
  require('../lib')(process.argv.slice(2))
}

require 支持加载的模块类型 .js/.json/.node。 当加载 .js 模块时,需要使用 module.exports/exports 进行导出。 当加载 .json 模块时,会调用 JSON.parse 对模块进行解析,并返回一个对象。 当加载 .node 模块时,会使用一个 c 插件,基本不用。 当加载 任意类型的文件 模块时,会当作 .js 去执行,如果内容不是 js 代码,那么会报错。

log 工具

创建 log

代码语言:javascript复制
// 使用 lerna 创建包
lerna create @hzw-cli-dev/log
// 使用 lerna  给 log 包 安装依赖
lerna add npmlog  utils/log
// 修改入口文件名为 index.js
// 修改 package.json 中的 main 字段为 lib/index.js
// 给 coreclipackage.json 添加依赖
"dependencies": {
    "@hzw-cli-dev/utils": "^1.0.1",
    "@hzw-cli-dev/log": "^1.0.1"
}
// 使用 lerna link 安装本地依赖 (不知道为啥 file:xx 这种形式不好用)
lerna link

定制 log

代码语言:javascript复制
'use strict';

// 引入 npmlog 模块
const log = require('npmlog');

// 从环境变量中读取 log.level
// log.level 的作用是: 只有超过 level 设置的权重,log 才会生效
log.level = process.env.LOG_LEVEL || 'info';

// 定制 log 的 level    参数: (名称,权重,配置)
log.addLevel('success', 2000, { fg: 'red', bg: 'yellow', bold: true });

// 定制 log 的前缀
log.heading = 'hzw';
// 定制 log 前缀的样式
log.headingStyle = { fg: 'blue', bg: 'green', bold: true };

module.exports = log;

调用 log

代码语言:javascript复制
// coreclilibindex.js
// 引入我们封装的 npmlog 工具
const log = require('@hzw-cli-dev/log');
log.success('test', 'success...');

效果如下,还是挺好玩的,这样就可以根据自己的喜好高度定制 log

检查版本号

代码语言:javascript复制
'use strict';

// 引入当前脚手架的 package.json 文件
const pkg = require('../package.json');

// 引入我们封装的 npmlog 工具
const log = require('@hzw-cli-dev/log');

/**
 * @description: 核心方法
 * @param {*}
 * @return {*}
 */
function core() {
  // 检查版本号
  checkPkgVersion();
}

/**
 * @description: 检查 package.json 中的 version 版本号
 * @param {*}
 * @return {*}
 */
function checkPkgVersion() {
  log.success('友情提示,当前的版本是:', pkg.version);
}

module.exports = core;

检查 node 版本

  • 安装版本对比的第三方库 semver
  • 安装定义脚手架输出颜色的库 colors
代码语言:javascript复制
lerna add semver core/cli
lerna add colors core/cli

定义最低 node 版本号

代码语言:javascript复制
// coreclilibconst.js
const LOWEST_NODE_VERSION = '17.0.0';

module.exports = {
  LOWEST_NODE_VERSION,
};

检查 node 版本号是否符合要求

代码语言:javascript复制
// coreclilibindex.js
'use strict';

// 引入版本比对第三方库 semver
const semver = require('semver');
// 引入颜色库 colors
const colors = require('colors/safe');
// 引入当前脚手架的 package.json 文件
const pkg = require('../package.json');
// 引入我们封装的 npmlog 工具
const log = require('@hzw-cli-dev/log');
// 引入配置文件
const constant = require('./const');

/**
 * @description: 核心方法
 * @param {*}
 * @return {*}
 */
function core() {
  try {
    // 检查版本号
    checkPkgVersion();
    // 检查 node 版本
    checkNodeVersion();
  } catch (error) {
    log.error(error.message);
  }
}

/**
 * @description: 检查当前的 node 版本,防止 node 版本过低调用最新 api 出错
 * @param {*}
 * @return {*}
 */
function checkNodeVersion() {
  // 获取当前 node 版本号
  const currentVersion = process.version;
  log.info('友情提示,当前的node版本是:', process.version);
  // 获取最低 node 版本号
  const lowestVersion = constant.LOWEST_NODE_VERSION;
  // 对比最低 node 版本号
  if (!semver.gte(currentVersion, lowestVersion)) {
    throw new Error(colors.red('错误:node版本过低'));
  }
}

module.exports = core;

效果如下

检查 root 账号启动

安装第三方库 root-check , 要指定版本,不然 2.0 是用 es module 写的,会报错。

代码语言:javascript复制
lerna add root-check@1.0.0 core/cli/
代码语言:javascript复制
// coreclilibindex.js
/**
 * @description: 检查root账户并自动降级
 * @param {*}
 * @return {*}
 */
function checkRoot() {
  // 检查 root 等级并自动降级
  const rootCheck = require('root-check');
  rootCheck();
}

检查用户主目录

安装第三方库 user-home , 跨操作系统获取用户主目录。

安装第三方库 path-exists , 检查文件是否存在。

代码语言:javascript复制
lerna add user-home core/cli/
lerna add path-exists@4.0.0 core/cli/
代码语言:javascript复制
// coreclilibindex.js
/**
 * @description:检查用户主目录
 * @param {*}
 * @return {*}
 */
function checkUserHome() {
  // 引入user-home 跨操作系统获取用户主目录
  const userHome = require('user-home');
  // 检查文件是否存在
  const pathExists = require('path-exists').sync;
  // 如果主目录不存在,抛出异常
  if (!userHome || !pathExists(userHome)) {
    throw new Error(colors.red('当前登录用户主目录不存在'));
  }
}

检查入参

安装第三方库 minimist ,解析参数。

代码语言:javascript复制
lerna add minimist core/cli/
代码语言:javascript复制
/**
 * @description: 解析参数,判断是否开启 debug 模式,并在全局变量中设置 log 等级
 * @param {*}
 * @return {*}
 */
let args;
function checkInputArgs() {
  const minimist = require('minimist');
  args = minimist(process.argv.slice(2));
  // 判断是否开启 debug 模式,并在全局变量中设置 log 等级
  checkArgs();
}

/**
 * @description: 判断是否开启 debug 模式,并在全局变量中设置 log 等级
 * @param {*}
 * @return {*}
 */
function checkArgs() {
  if (args.debug) {
    process.env.LOG_LEVEL = 'verbose';
  } else {
    process.env.LOG_LEVEL = 'info';
  }
  // 设置 log 的等级
  log.level = process.env.LOG_LEVEL;
}

检查环境变量

安装第三方库 dotenv

代码语言:javascript复制
lerna add dotenv core/cli/
代码语言:javascript复制
/**
 * @description: 检查环境变量
 * @param {*}
 * @return {*}
 */
function checkEnv() {
  // 引入解析环境变量的库 dotenv
  const dotenv = require('dotenv');
  // 环境变量的路径
  const dotenvPath = path.resolve(userHome, '.env');
  // 如果路径存在
  if (pathExists(dotenvPath)) {
    // 把.env的环境变量放在process.env里
    dotenv.config({
      path: dotenvPath,
    });
  }
  // 创建默认的环境变量配置
  createDefaultConfig();
  log.verbose('环境变量', process.env.CLI_HOME_PATH);
}

/**
 * @description: 创建默认的环境变量配置
 * @param {*}
 * @return {*}
 */
function createDefaultConfig() {
  const cliConfig = {
    home: userHome,
  };
  // 如果 CLI_HOME 存在 使用CLI_HOME
  if (process.env.CLI_HOME) {
    cliConfig['cliHome'] = path.join(userHome, process.env.CLI_HOME);
  } else {
    // 如果 CLI_HOME 不存在 使用默认配置
    cliConfig['cliHome'] = path.join(userHome, constant.DEFAULT_CLI_HOME);
  }
  // 设置 process.env.CLI_HOME_PATH
  process.env.CLI_HOME_PATH = cliConfig.cliHome;
}

通用 npm API 模块封装

通过 npm API: https://registry.npmjs.org/模块名

可以获取到某个模块的信息.

也可以换成其他的镜像 比如淘宝源 https://registry.npmmirror.com/模块名

代码语言:javascript复制
// 通过 lerna 新建一个包 放在 utils 下面
lerna create @hzw-cli-dev/get-npm-info ./utils/get-npm-info
// 修改文件名和 main 属性为 index.js
// core模块引入
// lerna link 安装本地依赖
// 安装 axios 用来发起网络请求
lerna add axios utils/get-npm-info
// 安装 url-join 帮助拼接url
lerna add url-join utils/get-npm-info
// 安装 semver 用来做版本比对
lerna add semver utils/get-npm-info

封装工具包 get-npm-info

代码语言:javascript复制
// utilsget-npm-infolibindex.js
'use strict';

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

/**
 * @description: 获取 npm 模块的信息
 * @param {*} npmName npm 模块名称
 * @param {*} register npm 镜像地址
 * @return {*}
 */
async function getNpmInfo(npmName, register) {
  // 如果 npmName 不存在直接返回
  if (!npmName) {
    return null;
  }
  // 获取镜像地址 ,如果没有传递参数则默认使用 npm 源
  const registerUrl = register || getRegister('taobao');
  // 拼接url
  const npmInfoUrl = urlJoin(registerUrl, npmName);
  // 调用 npm API 获取数据
  return axios
    .get(npmInfoUrl)
    .then((res) => {
      if (res.status === 200) {
        return res.data;
      }
      return null;
    })
    .catch((e) => {
      return Promise.reject(e);
    });
}

/**
 * @description: 获取 npm 镜像地址
 * @param {*} origin 源
 * @return {*} 镜像地址
 */
function getRegister(origin) {
  const originList = {
    npm: 'https://registry.npmjs.org/',
    taobao: 'https://registry.npmmirror.com/',
  };
  return originList[origin];
}

/**
 * @description: 获取模块版本号数组
 * @param {*} npmName npm 模块名称
 * @param {*} register npm 镜像地址
 * @return {*} 模块的版本号
 */
async function getNpmVersions(npmName, register) {
  const data = await getNpmInfo(npmName, register);
  if (data) {
    return Object.keys(data.versions);
  }
  return [];
}

/**
 * @description: 获取符合条件的版本号(大于当前版本的版本号)
 * @param {*} baseVersion  当前版本
 * @param {*} versions 版本号数组
 * @return {*} 大于当前版本的版本号数组
 */
function getNpmSemverVersions(baseVersion, versions) {
  if (!versions || versions.length === 0) {
    return [];
  }
  return versions
    .filter((version) => semver.satisfies(version, `>=1.0.0`))
    .sort((a, b) => semver.gt(b, a));
}

/**
 * @description:从 npm 获取符合条件的版本号(大于当前版本的最新版本号)
 * @param {*} npmName npm 模块名称
 * @param {*} register npm 镜像地址
 * @param {*} baseVersion  当前版本
 * @return {*} 最新版本号
 */
async function getNpmSemverVersion(baseVersion, npmName, register) {
  const versions = await getNpmVersions(npmName, register);
  const newVersions = getNpmSemverVersions(baseVersion, versions);
  return newVersions[0] || null;
}

module.exports = {
  getNpmInfo,
  getNpmVersions,
  getNpmSemverVersion,
};

获取 npm 包的信息

代码语言:javascript复制
// coreclilibindex.js

/**
 * @description: 检查是否需要全局更新
 *   1.获取当前版本号和模块名
     2.调用npm API ,获取所有的版本号
     3.提取所有的版本号,比对哪些版本号是大于当前版本号的
     4.获取最新的版本号,提示用户更新到该版本
 * @param {*}
 * @return {*}
 */
async function checkGlobalUpdate() {
  const currentVersion = pkg.version;
  const npmName = pkg.name;
  const { getNpmSemverVersion } = require('@hzw-cli-dev/get-npm-info');
  // getNpmInfo(npmName);
  const lastVersion = await getNpmSemverVersion(currentVersion, 'vue');
  // 如果最新版本存在并且大于当前版本
  if (lastVersion && semver.gt(lastVersion, currentVersion)) {
    log.warn(
      '友情提示',
      colors.yellow('请更新版本:当前的版本是:', lastVersion),
    );
    log.warn(
      '友情提示',
      colors.yellow('更新命令:', `npm install -g ${npmName}`),
    );
  }
}

0 人点赞