1、准备

1、建立core、commands、utils、models分包模块,将之前的packages中的core(更名为cli)、utils移入相关模块中并删除packages文件夹,修改lerna.json中内容

  1. {
  2. "packages": [
  3. "core/*",
  4. "commands/*",
  5. "models/*",
  6. "utils/*"
  7. ],
  8. "version": "1.0.2"
  9. }

2、在core/cli下的删除之前的软链接,重新建立链接。

2、开发

1、import-local 优先使用本地的安装包

  1. #!/usr/bin/env node
  2. const imporLocal = require('import-local')
  3. //npmlog 日志
  4. if (imporLocal(__filename)) {
  5. require('npmlog').info('cli', '正在使用本地版本')
  6. } else {
  7. require('../lib/core')(process.argv.slice(2))
  8. }

require加载

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

  1. //lib/index.txt
  2. module.exports = core
  3. function core() {
  4. console.log('ah')
  5. }
  6. //lib/index.js
  7. 'use strict'
  8. const index = require('./index.txt')
  9. module.exports = core
  10. function core() {
  11. index()
  12. }

运行haha-cli-dev
image.png

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

  1. 'use strict'
  2. const log = require('npmlog')
  3. // 从环境变量中读取 log.level
  4. // log.level 的作用是: 只有超过 level 设置的权重,log 才会生效
  5. log.level = process.env.LOG_LEVEL || 'info'
  6. // 定制 log 的 level 参数: (名称,权重,配置)
  7. log.addLevel('success', 2000, { fg: 'green' })
  8. //定义log的前缀
  9. // log.heading = 'haha'
  10. // 定制 log 前缀的样式
  11. log.headingStyle = { fg: 'blue', bg: 'green', bold: true }
  12. module.exports = log

3、检查版本号

  1. 'use strict'
  2. const Pkg = require('../package.json')
  3. const log = require('@haha-cli-dev/log')
  4. function core() {
  5. checkPkgVersion()
  6. }
  7. function checkPkgVersion() {
  8. log.success('当前版本:', Pkg.version)
  9. }
  10. module.exports = core

4、最低node版本检查

  1. const LOWEST_NODE_VERSION = '12.0.0'
  2. module.exports = { LOWEST_NODE_VERSION }
  1. 'use strict'
  2. //外部引入的包放最外面,内部引入的包放里面
  3. const semver = require('semver')
  4. const colors = require('colors')
  5. const log = require('@haha-cli-dev/log')
  6. const Pkg = require('../package.json')
  7. const constant = require('./const')
  8. function core() {
  9. try {
  10. checkPkgVersion()
  11. checkNodeVersion()
  12. } catch (e) {
  13. //捕获异常,并给出提示
  14. console.log(e.message)
  15. }
  16. }
  17. function checkPkgVersion() {
  18. log.success('当前版本:', Pkg.version)
  19. }
  20. function checkNodeVersion() {
  21. const currentVersion = process.version
  22. if (!semver.gte(currentVersion, constant.LOWEST_NODE_VERSION)) {
  23. //抛出异常!!
  24. throw new Error(colors.red('错误:node版本过低'))
  25. }
  26. }
  27. module.exports = core

当LOWEST_NODE_VERSION版本为17.0.0时,提示
image.png

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.)

  1. function checkRoot() {
  2. const rootCheck = require('root-check')
  3. rootCheck()
  4. }
  5. //使用root-check,会打印501,将root账户的,自动降级成普通用户

root-check源码

lerna add user-home core/cli/

lerna add path-exists@4.0.0 core/cli/

  1. import downgradeRoot from 'downgrade-root';
  2. import sudoBlock from 'sudo-block';
  3. export default function rootCheck(...arguments_) {
  4. try {
  5. //root降级
  6. downgradeRoot();
  7. } catch {}
  8. sudoBlock(...arguments_);
  9. }
  1. import isRoot from 'is-root';
  2. import defaultUid from 'default-uid';
  3. export default function downgradeRoot() {
  4. //是否是root用户
  5. if (!isRoot()) {
  6. return;
  7. }
  8. // `setgid`` needs to happen before setuid to avoid EPERM.
  9. if (process.setgid) { //群组id
  10. const gid = Number.parseInt(process.env.SUDO_GID, 10);
  11. if (gid && gid > 0) {
  12. process.setgid(gid);
  13. }
  14. }
  15. if (process.setuid) {
  16. //是root用户则设置euid,默认设置为501 核心!!
  17. const uid = Number.parseInt(process.env.SUDO_UID, 10) || defaultUid();
  18. if (uid && uid > 0) {
  19. process.setuid(uid);
  20. }
  21. }
  22. }
  1. const DEFAULT_UIDS = {
  2. darwin: 501,
  3. freebsd: 1000,
  4. linux: 1000,
  5. sunos: 100
  6. };
  7. export default function defaultUid(platform = process.platform) {
  8. return DEFAULT_UIDS[platform];
  9. }
  1. import chalk from 'chalk';
  2. import isRoot from 'is-root';
  3. import isDocker from 'is-docker';
  4. export default function sudoBlock(message) {
  5. //打印日志
  6. const defaultMessage = chalk`
  7. {red.bold You are not allowed to run this app with root permissions.}
  8. 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:
  9. {blue npm config set prefix ~/npm}
  10. See: {underline https://github.com/sindresorhus/guides/blob/main/npm-global-without-sudo.md}`;
  11. if (isRoot() && !isDocker()) {
  12. console.error(message || defaultMessage);
  13. process.exit(77); // eslint-disable-line unicorn/no-process-exit
  14. }
  15. }

6、用户主目录检查功能开发

  1. //检查用户主目录
  2. function checkUserHome() {
  3. const userHome = require('user-home')
  4. console.log(userHome)
  5. //=>C:\Users\haha
  6. const pathExists = require('path-exists').sync(userHome)
  7. if (!userHome || !pathExists) {
  8. throw new Error(colors.red('当前登录用户主目录不存在'))
  9. }
  10. }

7、入参检查和debug模式开发

为什么在这一步检查入参?而不是在注册脚手架的时候。这一步主要是知道当前是否进入调试模式,因为我们在注册脚手架之前有可能要使用log.verbose()这个方式去打印debug的日志,因为打印脚手架的日志默认是关闭的。如果在脚手架注册之前就使用了debug,脚手架感应不到,所以需要这一步检查入参。
lerna add minimist core/cli/

  1. //对入参内进行检查,防止在注册脚手架之前就使用了debug模式
  2. function checkInputArgs() {
  3. //minimist 解析参数
  4. argv = require('minimist')(process.argv.slice(2))
  5. console.log(argv)
  6. checkArgs()
  7. }
  8. // 判断是否开启 debug 模式,并在全局变量中设置 log 等级
  9. function checkArgs(argv) {
  10. if (argv?.debug) {
  11. process.env.LOG_LEVEL = 'verbose'
  12. } else {
  13. process.env.LOG_LEVEL = 'info'
  14. }
  15. log.level = process.env.LOG_LEVEL
  16. }

image.png

8、环境变量检查

  1. function checkEnv() {
  2. //引入解析环境变量的库
  3. const dotenv = require('dotenv')
  4. const dotenvPath = path.resolve(userHome, '.env')
  5. if (pathExists.sync(dotenvPath)) {
  6. // 把.env的环境变量放在process.env里
  7. dotenv.config({ path: dotenvPath })
  8. }
  9. //创建默认的环境变量配置
  10. createDefaultConfig()
  11. log.verbose('环境变量', process.env.CLI_HOME)
  12. }
  13. function createDefaultConfig() {
  14. const cliConfig = {
  15. home: userHome
  16. }
  17. if (process.env.CLI_HOME) {
  18. cliConfig['cliHome'] = path.join(userHome, process.env.CLI_HOME)
  19. } else {
  20. cliConfig['cliHome'] = path.join(userHome, constant.DEFAULT_CLI_HOME)
  21. }
  22. process.env.CLI_HOME = cliConfig.cliHome
  23. }
  1. const LOWEST_NODE_VERSION = '12.0.0'
  2. const DEFAULT_CLI_HOME = '.haha-cli-dev'
  3. 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

  1. 'use strict'
  2. const axios = require('axios')
  3. const urlJoin = require('url-join')
  4. const semver = require('semver')
  5. //调用npm API,得到npm信息
  6. function getNpmInfo(npmName, register) {
  7. if (!npmName) return null
  8. const registerUrl = getRegister()
  9. const url = urlJoin(registerUrl, npmName)
  10. return axios
  11. .get(url)
  12. .then(function (res) {
  13. if (res.status === 200) {
  14. return res.data
  15. }
  16. return null
  17. })
  18. .catch(function (error) {
  19. return Promise.reject(error)
  20. })
  21. }
  22. //得到镜像地址
  23. function getRegister(isOrigin = false) {
  24. return isOrigin ? 'https://registry.npmjs.org/' : 'https://registry.npmmirror.com/'
  25. }
  26. //获取所有的版本号
  27. async function getNpmVersions(npmName) {
  28. const npmInfo = await getNpmInfo(npmName)
  29. if (npmInfo) {
  30. return Object.keys(npmInfo.versions)
  31. }
  32. return []
  33. }
  34. //比对哪些版本号是大于当前版本号,并排序
  35. function getNpmSemverVersions(baseVersion, versions) {
  36. if (!versions || versions.length === 0) {
  37. return null
  38. }
  39. //(semver.gt(b, a) 返回true,false sort不能识别
  40. return versions.filter(version => semver.satisfies(version, `>=${baseVersion}`)).sort((a, b) => (semver.gt(b, a) ? 1 : -1))
  41. }
  42. //获取最新的版本号
  43. async function getNpmLastVersion(baseVersion, npmName, register) {
  44. const versions = await getNpmVersions(npmName)
  45. const newVersions = getNpmSemverVersions(baseVersion, versions)
  46. if (newVersions && newVersions.length > 0) {
  47. return newVersions[0]
  48. }
  49. return null
  50. }
  51. module.exports = {
  52. getNpmInfo,
  53. getRegister,
  54. getNpmVersions,
  55. getNpmSemverVersions,
  56. getNpmLastVersion
  57. }
  1. /**
  2. * 1.获取当前版本号和模块名
  3. 2.调用npm API ,获取所有的版本号
  4. 3.提取所有的版本号,比对哪些版本号是大于当前版本号的
  5. 4.获取最新的版本号,提示用户更新到该版本
  6. */
  7. async function checkGlobalUpdate() {
  8. const cerrentVersion = pkg.version
  9. const npmName = pkg.name
  10. const { getNpmLastVersion } = require('@haha-cli-dev/get-npm-info')
  11. const lastVersion = await getNpmLastVersion(cerrentVersion, npmName)
  12. if (lastVersion && semver.gt(lastVersion, cerrentVersion)) {
  13. log.warn('友情提示', colors.yellow('请更新版本:当前的版本是:', lastVersion))
  14. }
  15. }