1、准备
1、建立core、commands、utils、models分包模块,将之前的packages中的core(更名为cli)、utils移入相关模块中并删除packages文件夹,修改lerna.json中内容
{"packages": ["core/*","commands/*","models/*","utils/*"],"version": "1.0.2"}
2、开发
1、import-local 优先使用本地的安装包
#!/usr/bin/env nodeconst imporLocal = require('import-local')//npmlog 日志if (imporLocal(__filename)) {require('npmlog').info('cli', '正在使用本地版本')} else {require('../lib/core')(process.argv.slice(2))}
require加载
注意:
require支持加载的模块类型 .js/json/.node
当加载 .js模块时,需使用module.export/export进行导出;
当加载.json模块时,会调用JSON.parse对模块解析,并返回一个对象。
当加载.node模块时,会使用一个c++插件,基本不用。
当加载任意类型的文件模块是,会当作.js去执行。如果内容不是js代码,那么会报错。
//lib/index.txtmodule.exports = corefunction core() {console.log('ah')}//lib/index.js'use strict'const index = require('./index.txt')module.exports = corefunction core() {index()}
2、定制log
步骤:
1、lerna create @haha-cli-dev/log // 使用 lerna 给 log 包
2、安装依赖 lerna add npmlog utils/log // 修改入口文件名为 index.js与 package.json 中的 main 字段为 lib/index.js
3、使用 lerna link
4、core/cli引用@haha-cli-dev/log,修改cli/package.json,并npm link
'use strict'const log = require('npmlog')// 从环境变量中读取 log.level// log.level 的作用是: 只有超过 level 设置的权重,log 才会生效log.level = process.env.LOG_LEVEL || 'info'// 定制 log 的 level 参数: (名称,权重,配置)log.addLevel('success', 2000, { fg: 'green' })//定义log的前缀// log.heading = 'haha'// 定制 log 前缀的样式log.headingStyle = { fg: 'blue', bg: 'green', bold: true }module.exports = log
3、检查版本号
'use strict'const Pkg = require('../package.json')const log = require('@haha-cli-dev/log')function core() {checkPkgVersion()}function checkPkgVersion() {log.success('当前版本:', Pkg.version)}module.exports = core
4、最低node版本检查
const LOWEST_NODE_VERSION = '12.0.0'module.exports = { LOWEST_NODE_VERSION }
'use strict'//外部引入的包放最外面,内部引入的包放里面const semver = require('semver')const colors = require('colors')const log = require('@haha-cli-dev/log')const Pkg = require('../package.json')const constant = require('./const')function core() {try {checkPkgVersion()checkNodeVersion()} catch (e) {//捕获异常,并给出提示console.log(e.message)}}function checkPkgVersion() {log.success('当前版本:', Pkg.version)}function checkNodeVersion() {const currentVersion = process.versionif (!semver.gte(currentVersion, constant.LOWEST_NODE_VERSION)) {//抛出异常!!throw new Error(colors.red('错误:node版本过低'))}}module.exports = core
当LOWEST_NODE_VERSION版本为17.0.0时,提示
5、root账号启动检查和自动降级
process.geteuid() 如果是普通用户,则打印是501,如果是root用户,打印是0。如果用root账户启动项目,创建文件,就是root账户的,当前的普通用户就没办法修改,就会遇到各种各样的权限报错。
lerna add root-check@1.0.0 core/cli/ (高版本会提示require() of ES modules is not supported.)
function checkRoot() {const rootCheck = require('root-check')rootCheck()}//使用root-check,会打印501,将root账户的,自动降级成普通用户
root-check源码
lerna add path-exists@4.0.0 core/cli/
import downgradeRoot from 'downgrade-root';import sudoBlock from 'sudo-block';export default function rootCheck(...arguments_) {try {//root降级downgradeRoot();} catch {}sudoBlock(...arguments_);}
import isRoot from 'is-root';import defaultUid from 'default-uid';export default function downgradeRoot() {//是否是root用户if (!isRoot()) {return;}// `setgid`` needs to happen before setuid to avoid EPERM.if (process.setgid) { //群组idconst gid = Number.parseInt(process.env.SUDO_GID, 10);if (gid && gid > 0) {process.setgid(gid);}}if (process.setuid) {//是root用户则设置euid,默认设置为501 核心!!const uid = Number.parseInt(process.env.SUDO_UID, 10) || defaultUid();if (uid && uid > 0) {process.setuid(uid);}}}
const DEFAULT_UIDS = {darwin: 501,freebsd: 1000,linux: 1000,sunos: 100};export default function defaultUid(platform = process.platform) {return DEFAULT_UIDS[platform];}
import chalk from 'chalk';import isRoot from 'is-root';import isDocker from 'is-docker';export default function sudoBlock(message) {//打印日志const defaultMessage = chalk`{red.bold You are not allowed to run this app with root permissions.}If running without {bold sudo} doesn't work, you can either fix your permission problems or change where npm stores global packages by putting {bold ~/npm/bin} in your PATH and running:{blue npm config set prefix ~/npm}See: {underline https://github.com/sindresorhus/guides/blob/main/npm-global-without-sudo.md}`;if (isRoot() && !isDocker()) {console.error(message || defaultMessage);process.exit(77); // eslint-disable-line unicorn/no-process-exit}}
6、用户主目录检查功能开发
//检查用户主目录function checkUserHome() {const userHome = require('user-home')console.log(userHome)//=>C:\Users\hahaconst pathExists = require('path-exists').sync(userHome)if (!userHome || !pathExists) {throw new Error(colors.red('当前登录用户主目录不存在'))}}
7、入参检查和debug模式开发
为什么在这一步检查入参?而不是在注册脚手架的时候。这一步主要是知道当前是否进入调试模式,因为我们在注册脚手架之前有可能要使用log.verbose()这个方式去打印debug的日志,因为打印脚手架的日志默认是关闭的。如果在脚手架注册之前就使用了debug,脚手架感应不到,所以需要这一步检查入参。
lerna add minimist core/cli/
//对入参内进行检查,防止在注册脚手架之前就使用了debug模式function checkInputArgs() {//minimist 解析参数argv = require('minimist')(process.argv.slice(2))console.log(argv)checkArgs()}// 判断是否开启 debug 模式,并在全局变量中设置 log 等级function checkArgs(argv) {if (argv?.debug) {process.env.LOG_LEVEL = 'verbose'} else {process.env.LOG_LEVEL = 'info'}log.level = process.env.LOG_LEVEL}
8、环境变量检查
function checkEnv() {//引入解析环境变量的库const dotenv = require('dotenv')const dotenvPath = path.resolve(userHome, '.env')if (pathExists.sync(dotenvPath)) {// 把.env的环境变量放在process.env里dotenv.config({ path: dotenvPath })}//创建默认的环境变量配置createDefaultConfig()log.verbose('环境变量', process.env.CLI_HOME)}function createDefaultConfig() {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 = cliConfig.cliHome}
const LOWEST_NODE_VERSION = '12.0.0'const DEFAULT_CLI_HOME = '.haha-cli-dev'module.exports = { LOWEST_NODE_VERSION, DEFAULT_CLI_HOME }
9、检查脚手架是否为最新的版本
通过 npm API: https://registry.npmjs.org/模块名
可以获取到某个模块的信息.
也可以换成其他的镜像 比如淘宝源 https://registry.npmmirror.com/模块名
// 通过 lerna 新建一个包 放在 utils 下面
lerna create @haha-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@4.0.1 utils/get-npm-info
// 安装 semver 用来做版本比对
lerna add semver utils/get-npm-info
'use strict'const axios = require('axios')const urlJoin = require('url-join')const semver = require('semver')//调用npm API,得到npm信息function getNpmInfo(npmName, register) {if (!npmName) return nullconst registerUrl = getRegister()const url = urlJoin(registerUrl, npmName)return axios.get(url).then(function (res) {if (res.status === 200) {return res.data}return null}).catch(function (error) {return Promise.reject(error)})}//得到镜像地址function getRegister(isOrigin = false) {return isOrigin ? 'https://registry.npmjs.org/' : 'https://registry.npmmirror.com/'}//获取所有的版本号async function getNpmVersions(npmName) {const npmInfo = await getNpmInfo(npmName)if (npmInfo) {return Object.keys(npmInfo.versions)}return []}//比对哪些版本号是大于当前版本号,并排序function getNpmSemverVersions(baseVersion, versions) {if (!versions || versions.length === 0) {return null}//(semver.gt(b, a) 返回true,false sort不能识别return versions.filter(version => semver.satisfies(version, `>=${baseVersion}`)).sort((a, b) => (semver.gt(b, a) ? 1 : -1))}//获取最新的版本号async function getNpmLastVersion(baseVersion, npmName, register) {const versions = await getNpmVersions(npmName)const newVersions = getNpmSemverVersions(baseVersion, versions)if (newVersions && newVersions.length > 0) {return newVersions[0]}return null}module.exports = {getNpmInfo,getRegister,getNpmVersions,getNpmSemverVersions,getNpmLastVersion}
/*** 1.获取当前版本号和模块名2.调用npm API ,获取所有的版本号3.提取所有的版本号,比对哪些版本号是大于当前版本号的4.获取最新的版本号,提示用户更新到该版本*/async function checkGlobalUpdate() {const cerrentVersion = pkg.versionconst npmName = pkg.nameconst { getNpmLastVersion } = require('@haha-cli-dev/get-npm-info')const lastVersion = await getNpmLastVersion(cerrentVersion, npmName)if (lastVersion && semver.gt(lastVersion, cerrentVersion)) {log.warn('友情提示', colors.yellow('请更新版本:当前的版本是:', lastVersion))}}
