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 node
const 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.txt
module.exports = core
function core() {
console.log('ah')
}
//lib/index.js
'use strict'
const index = require('./index.txt')
module.exports = core
function 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.version
if (!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) { //群组id
const 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\haha
const 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 null
const 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.version
const npmName = pkg.name
const { 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))
}
}