脚手架结构图.png

核心模块开发

【2022.02】脚手架核心流程开发 - 图2

准备阶段

image.png
核心库:

  • import-local:当前项目中的 node_modules 中存在一个脚手架命令,和全局的 node 环境中也存在一个脚手架命令的时候,它会优先选用 node_modules 中的本地版本
  • commander:命令注册

工具库:

  • npmlog:打印日志
  • fs-extra:文件操作
  • semver:版本比对
  • colors:在终端打印不同的颜色文本
  • user-home:快速拿到用户的主目录
  • dotenv:获取环境变量
  • root-check:root 账号的检查和自动降级

    流程简介

  1. 使用 import-local 优先运行本地脚手架 ```typescript

    ! /usr/bin/env node

const importLocal = require(‘import-local’);

// 判断 importLocal(filename) 为 true 的时候 会输出一行 log // 判断本地 node_modules 中是否存在脚手架 // filename: 返回当前模块文件被解析过后的绝对路径 if (importLocal(__filename)) { require(‘npmlog’).info(‘cli’, ‘正在使用 temp-cli-dev 本地版本’); } else { require(‘../lib’)(process.argv.slice(2)); //从进程中获取参数 }

  1. 2. 检查版本号
  2. ```typescript
  3. const log = require('npmlog');
  4. // 加载 .json 时会使用 JSON.parse 进行转换编译从而得到一个 json 对象
  5. const pkg = require('../package.json');
  6. // 检查版本
  7. function checkPkgVersion () {
  8. log.info('cli', pkg.version);
  9. }
  1. 检查 node 版本

使用 semver 实现版本号的解析和比较

  1. const semver = require('semver');
  2. const LOWEST_NODE_VERSION = '12.0.0'; // 当前可用的最低 node 版本
  3. // 检查 node 版本
  4. function checkNodeVersion () {
  5. const currentVersion = process.version; // 当前 Node 版本
  6. const lastVersion = LOWEST_NODE_VERSION;
  7. // gte(v1, v2): v1 >= v2
  8. if (!semver.gte(currentVersion, lastVersion)) {
  9. throw new Error(colors.red(`当前脚手架需要安装v${lastVersion}以上版本的Node.js`));
  10. }
  11. }
  1. 检查是否 root 账号

如果进行相关操作的时候使用的是 root 账号,那么会带来很多权限问题。root-check 会判断出当前系统登录账号是否是 root 账号,如果是,会自动帮我们降级,避免后续的权限问题。

  1. // 检查登录账号的级别,是否需要降级
  2. function checkRoot () {
  3. //使用后,检查到root账户启动,会进行降级为用户账户
  4. const rootCheck = require('root-check');
  5. rootCheck();
  6. }
  1. 检查用户主目录是否存在

path-exists 该库的最新版不再支持 commonJS 模式

  1. const userHome = require('user-home'); // 获取当前用户主目录
  2. const pathExists = require('path-exists').sync; //判断目录是否存在
  3. const colors = require('colors');
  4. // 检查用户主目录
  5. function checkUserHome () {
  6. if (!userHome || !pathExists(userHome)) {
  7. throw new Error(colors.red('当前登录用户主目录不存在!!!'));
  8. }
  9. }
  1. 检查入参

minimist 是一个轻量级的命令行参数解析引擎,利用它来解析命令行的参数,进行相应的配置

  1. // 检查入参
  2. function checkInputArgs() {
  3. const minimist = require('minimist');
  4. let args = minimist(process.argv.slice(2));
  5. checkArgs(args);
  6. }
  7. function checkArgs(args) {
  8. if (args.debug) {
  9. process.env.LOG_LEVEL = 'verbose';
  10. } else {
  11. process.env.LOG_LEVEL = 'info';
  12. }
  13. log.level = process.env.LOG_LEVEL;
  14. }
  1. 检查环境变量

.env 文件中存储的数据格式是 key=value 这种格式的,获取到 .env 文件后使用 dotenv 会将里面的数据挂载到 process.env 上

  1. const path = require('path');
  2. // 检查环境变量
  3. function checkEnv () {
  4. const dotenv = require('dotenv');
  5. const dotenvPath = path.resolve(__dirname, '../../../.env');
  6. if (pathExists(dotenvPath)) {
  7. // config will read your .env file, parse the contents, assign it to process.env,
  8. // and return an Object with a parsed key containing the loaded content or an error key if it failed.
  9. dotenv.config({
  10. path: dotenvPath
  11. });
  12. }
  13. log.info('环境变量', process.env.CLI_HOME_PORT);
  14. }
  15. function createDefaultConfig() {
  16. const cliConfig = {
  17. home: userHome
  18. }
  19. if (process.env.CLI_HOME) {
  20. cliConfig['cliHome'] = path.join(userHome, process.env.CLI_HOME);
  21. } else {
  22. cliConfig['cliHome'] = path.join(userHome, constants.DEFAULT_CLI_HOME);
  23. }
  24. process.env.CLI_HOME_PATH = cliConfig.cliHome;
  25. }
  1. 检查是否是最新版本,是否需要更新 ```typescript async function checkGlobalUpdate() { //1.获取当前版本号和模块名 const currentVersion = pkg.version; const npmName = pkg.name; //2.调用npm API,获取所有版本号 const { getNpmSemverVersion } = require(‘@temp-cli-dev/get-npm-info’); //3.提取所有版本号,比对哪些版本号是大于当前版本号 const lastVersion = await getNpmSemverVersion(currentVersion, npmName); if (lastVersion && semver.gt(lastVersion, currentVersion)) {
    1. //4.获取最新的版本号,提示用户更新到该版本
    2. log.warn(colors.yellow(`请手动更新${npmName},当前版本:${currentVersion},最新版本:${lastVersion}
    3. 更新命令:npm install -g ${npmName}`))
    } }

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

function getNpmInfo(npmName,registry) { if (!npmName) { return null; } const registryUrl = registry || getDefaultRegistry(); const npmInfoUrl = urlJoin(registryUrl,npmName); return axios.get(npmInfoUrl).then(response => { if (response.status === 200) { return response.data; } return null; }).catch(err => { return Promise.reject(err); }) }

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 => semver.satisfies(version,^${baseVersion}) ).sort((a,b)=> semver.gt(b,a)); }

async function getNpmSemverVersion(baseVersion,npmName,registry) { const versions = await getNpmVersions(npmName, registry); const newVersions = getSemverVersions(baseVersion, versions); if (newVersions && newVersions.length > 0) { return newVersions[0]; } }

async function getNpmLatestVersion(npmName,registry) { let versions = await getNpmVersions(npmName,registry); if (versions) { return versions.sort((a,b)=> semver.gt(b,a))[0]; } return null; } ```