only-allow源码调试与分析

在之前的文章《preinstall: “npx only-allow pnpm”的解析》中我们讲到了only-allow是用来强制在项目中使用特定的包管理器。本文接着对它进行源码调试及分析:

一、only-allow源码调试

1.下载代码

git clone https://github.com/pnpm/only-allow.git

2.安装依赖包

npm install

3.添加脚本命令

// package.json
{
	  "scripts": {
	    	"preinstall": "node bin.js pnpm"
	  }
}

bin.js就是only-allow的执行代码文件。

4.VS Code中启动调试

(1) 打开bin.js文件,并在第15行打上断点
(2)点击VS Code开发工具左侧“运行与调试”,点击“JavaScript调试终端
(3)在底部的“JavaScript Debug Terminal”终端窗口输入命令:yarn install

在这里插入图片描述
在这里插入图片描述
至此,我们进入了only-allow源码调试状态。

二、源码分析

only-allow的源码其实只有短短的56行,让我们先一起看一下:

#!/usr/bin/env node
// which-pm-runs可以获取到node运行使用的命令
const whichPMRuns = require('which-pm-runs') 

function box(s) {
  const lines = s.trim().split("\n")
  const width = lines.reduce((a, b) => Math.max(a, b.length), 0)
  const surround = x => '║   \x1b[0m' + x.padEnd(width) + '\x1b[31m   ║'
  const bar = '═'.repeat(width)
  const top = '\x1b[31m╔═══' + bar + '═══╗'
  const pad = surround('')
  const bottom = '╚═══' + bar + '═══╝\x1b[0m'
  return [top, pad, ...lines.map(surround), pad, bottom].join('\n')
}
// 获取到执行的命令,之后携带的参数
// process.argv的值类似于 ['E:\nodejs\node.exe', 'D:\workspace\study\only-allow\bin.js', 'pnpm'],因此下面的argv得到的就是[pnpm]
const argv = process.argv.slice(2)
if (argv.length === 0) {
  console.log('Please specify the wanted package manager: only-allow <npm|cnpm|pnpm|yarn|bun>')
  process.exit(1)
}
// 取出想要执行命令的值类例如node bin.js pnpm中的pnpm
const wantedPM = argv[0]
if (wantedPM !== 'npm' && wantedPM !== 'cnpm' && wantedPM !== 'pnpm' && wantedPM !== 'yarn' && wantedPM !== 'bun') {
  console.log(`"${wantedPM}" is not a valid package manager. Available package managers are: npm, cnpm, pnpm, yarn or bun.`)
  process.exit(1)
}
// 可以获取到执行命令用的那种方式和版本号,例如name:'yarn' version:'1.22.17'
const usedPM = whichPMRuns()
const cwd = process.env.INIT_CWD || process.cwd()
const isInstalledAsDependency = cwd.includes('node_modules')
// 对比想要使用的安装方式和正在用的安装方式是否一致,不一致给出警告并停止执行
if (usedPM && usedPM.name !== wantedPM && !isInstalledAsDependency) {
  switch (wantedPM) {
    case 'npm':
      console.log(box('Use "npm install" for installation in this project'))
      break
    case 'cnpm':
      console.log(box('Use "cnpm install" for installation in this project'))
      break
    case 'pnpm':
      console.log(box(`Use "pnpm install" for installation in this project.

If you don't have pnpm, install it via "npm i -g pnpm".
For more details, go to https://pnpm.js.org/`))
      break
    case 'yarn':
      console.log(box(`Use "yarn" for installation in this project.

If you don't have Yarn, install it via "npm i -g yarn".
For more details, go to https://yarnpkg.com/`))
      break

    case 'bun':
      console.log(box(`Use "bun install" for installation in this project.

If you don't have Bun, go to https://bun.sh/docs/installation and find installation method that suits your environment".`))
      break
  }
  process.exit(1)
}

通过读源码可以发现:
①通过process.argv来判断参数获取到想要使用的依赖包的安装方式,
②通过which-pm-runs这个依赖包货获取到使用什么方式下载的依赖包
③通过wantedPM和usedPM的对比是否值一样,如果不一样输出一些错误信息并且通过process.exit(1)停止命令的执行:

在这里插入图片描述
那么which-pm-runs是如何获取我们使用什么方式下载的依赖包?我看一下which-pm-runs的源码:
双击选中whichPMRuns,右击选择转到源定义

在这里插入图片描述

// /node_modules/which-pm-runs/index.js
'use strict'

module.exports = function () {
  // process.env.npm_config_user_agent是一个Node.js环境变量,它存储了npm配置的用户代理信息。
  // 用户代理信息通常包括npm的版本、操作系统等相关细节。它可以被npm和其他工具用来识别它们正在运行的环境。
  // process.env.npm_config_user_agent的值类似于'yarn/1.22.19 npm/? node/v16.18.0 win32 x64'
  if (!process.env.npm_config_user_agent) {
    return undefined
  }
  return pmFromUserAgent(process.env.npm_config_user_agent)
}

function pmFromUserAgent (userAgent) {
  const pmSpec = userAgent.split(' ')[0]
  const separatorPos = pmSpec.lastIndexOf('/')
  const name = pmSpec.substring(0, separatorPos)
  return {
    name: name === 'npminstall' ? 'cnpm' : name,
    version: pmSpec.substring(separatorPos + 1)
  }
}

在这里插入图片描述

可以发现,which-pm-runs其实使用的是process.env.npm_config_user_agent。process.env.npm_config_user_agent是一个Node.js环境变量,它存储了npm配置的用户代理信息。用户代理信息通常包括npm的版本、操作系统等相关细节。它可以被npm和其他工具用来识别它们正在运行的环境。

which-pm-runs对process.env.npm_config_user_agent进行了转换,其实也可以使用npm-config-user-agent-parser进行转换,我们可以结合npx来快速看一下结果:

npx npm-config-user-agent-parser 'yarn/1.7.0 npm/? node/v8.9.4 darwin x64'

在这里插入图片描述

现在用npm-config-user-agent-parser来转换process.env.npm_config_user_agent

const parser = require('npm-config-user-agent-parser');
const parsed = parser(process.env.npm_config_user_agent);
console.log(parsed);

输出结果:

{
  "yarn": {
    "raw": "1.7.0",
    "major": 1,
    "minor": 7,
    "patch": 0,
    "prerelease": [],
    "build": [],
    "version": "1.7.0"
  },
  "npm": null,
  "node": {
    "raw": "8.9.4",
    "major": 8,
    "minor": 9,
    "patch": 4,
    "prerelease": [],
    "build": [],
    "version": "8.9.4"
  },
  "platform": "darwin",
  "arch": "x64",
  "raw": "yarn/1.7.0 npm/? node/v8.9.4 darwin x64"
}

这里有个疑问:process.env.npm_config_user_agent相关的文档在哪里?似乎找遍了node与npm的文档,都没有具体体现npm_config_user_agent的说明。(这个调查过程,由于篇幅原因,将于明天的下一篇文章《探究process.env.npm_config_user_agent的来源》发表)

注:

npm 和 yarn 对待 preinstall 的调用时机不一致。npm 仅会在当前项目执行安装(即 npm install)时会触发该钩子调用,单独安装某个模块(即 npm install )时并不会触发;而 yarn 则在这两种情况下都会触发该钩子命令。这样一来,如果想通过该钩子命令去限制 npm 的使用者,就无法达到预期效果了。

最后,感兴趣对于process.env,我们查看一下它里面的值有哪一些:

{
ALLUSERSPROFILE: 'C:\\ProgramData'
APPDATA: 'C:\\Users\\xxx\\AppData\\Roaming'
CHROME_CRASHPAD_PIPE_NAME: '\\\\.\\pipe\\crashpad_23288_TAJLXOVQQOTATNQY'
COLORTERM: 'truecolor'
CommonProgramFiles: 'C:\\Program Files\\Common Files'
CommonProgramFiles(x86): 'C:\\Program Files (x86)\\Common Files'
CommonProgramW6432: 'C:\\Program Files\\Common Files'
COMPUTERNAME: 'DESKTOP-TAFICRN'
ComSpec: 'C:\\WINDOWS\\system32\\cmd.exe'
DevEco Studio: 'E:\\DevEco Studio\\bin;'
DriverData: 'C:\\Windows\\System32\\Drivers\\DriverData'
FPS_BROWSER_APP_PROFILE_STRING: 'Internet Explorer'
FPS_BROWSER_USER_PROFILE_STRING: 'Default'
GIT_ASKPASS: 'e:\\VSCode\\resources\\app\\extensions\\git\\dist\\askpass.sh'
HOMEDRIVE: 'C:'
HOMEPATH: '\\Users\\xxx'
INIT_CWD: 'D:\\workspace\\study\\only-allow'
LANG: 'zh_CN.UTF-8'
LOCALAPPDATA: 'C:\\Users\\xxx\\AppData\\Local'
LOGONSERVER: '\\\\DESKTOP-TAFICRN'
NODE: 'E:\\nodejs\\node.exe'
NODE_OPTIONS: ' --require e:/VSCode/resources/app/extensions/ms-vscode.js-debug/src/bootloader.js  --inspect-publish-uid=http'
npm_config_argv: '{"remain":[],"cooked":["install"],"original":["install"]}'
npm_config_bin_links: 'true'
npm_config_cache: 'D:\\workspace\\xxx\\clean'
npm_config_ELECTRON_MIRROR: 'https://npm.taobao.org/mirrors/electron/'
npm_config_ignore_optional: ''
npm_config_ignore_scripts: ''
npm_config_init_license: 'MIT'
npm_config_init_version: '1.0.0'
npm_config_registry: 'https://registry.npm.taobao.org'
npm_config_sass_binary_site: 'https://npm.taobao.org/mirrors/node-sass/'
npm_config_save_prefix: '^'
npm_config_strict_ssl: 'true'
npm_config_user_agent: 'yarn/1.22.19 npm/? node/v16.18.0 win32 x64'
npm_config_version_commit_hooks: 'true'
npm_config_version_git_message: 'v%s'
npm_config_version_git_sign: ''
npm_config_version_git_tag: 'true'
npm_config_version_tag_prefix: 'v'
npm_execpath: 'E:\\nvm\\v16.18.0\\node_modules\\yarn\\bin\\yarn.js'
npm_lifecycle_event: 'preinstall'
npm_lifecycle_script: 'node bin.js pnpm'
npm_node_execpath: 'E:\\nodejs\\node.exe'
npm_package_author_email: 'z@kochan.io'
npm_package_author_name: 'Zoltan Kochan'
npm_package_bin_only_allow1: 'bin.js'
npm_package_bugs_url: 'https://github.com/pnpm/only-allow/issues'
npm_package_dependencies_which_pm_runs: '^1.1.0'
npm_package_description: 'Force a specific package manager to be used on a project'
npm_package_files_0: 'bin.js'
npm_package_homepage: 'https://github.com/pnpm/only-allow#readme'
npm_package_keywords_0: 'pnpm'
npm_package_keywords_1: 'cnpm'
npm_package_keywords_2: 'npm'
npm_package_keywords_3: 'yarn'
npm_package_keywords_4: 'bun'
npm_package_license: 'MIT'
npm_package_main: 'bin.js'
npm_package_name: 'only-allow1'
npm_package_readmeFilename: 'README.md'
npm_package_repository_type: 'git'
npm_package_repository_url: 'git+https://github.com/pnpm/only-allow.git'
npm_package_scripts_preinstall: 'node bin.js pnpm'
npm_package_version: '1.2.1'
NUMBER_OF_PROCESSORS: '6'
NVM_HOME: 'E:\\nvm'
NVM_SYMLINK: 'E:\\nodejs'
OneDrive: 'C:\\Users\\xxx\\OneDrive'
OneDriveConsumer: 'C:\\Users\\xxx\\OneDrive'
ORIGINAL_XDG_CURRENT_DESKTOP: 'undefined'
OS: 'Windows_NT'
Path: 'C:\\Users\\xxx\\AppData\\Local\\Temp\\yarn--1702447226776-0.7481284080895285;D:\\workspace\\study\\only-allow\\node_modules\\.bin;C:\\Users\\xxx\\AppData\\Local\\Yarn\\Data\\link\\node_modules\\.bin;C:\\Users\\xxx\\AppData\\Local\\Yarn\\bin;E:\\libexec\\lib\\node_modules\\npm\\bin\\node-gyp-bin;E:\\lib\\node_modules\\npm\\bin\\node-gyp-bin;E:\\nodejs\\node_modules\\npm\\bin\\node-gyp-bin;C:\\Program Files\\Microsoft\\jdk-11.0.16.101-hotspot\\bin;E:\\Python310\\Scripts\\;E:\\Python310\\;C:\\WINDOWS\\system32;C:\\WINDOWS;C:\\WINDOWS\\System32\\Wbem;C:\\…stem32\\WindowsPowerShell\\v1.0\\;C:\\WINDOWS\\System32\\OpenSSH\\;E:\\Git\\cmd;E:\\TortoiseGit\\bin;E:\\nvm;E:\\nodejs;C:\\Program Files\\dotnet\\;C:\\Users\\xxx\\.windows-build-tools\\python27;C:\\Users\\xxx\\.windows-build-tools\\python27\\Scripts;;E:\\TortoiseSVN\\bin;C:\\Program Files (x86)\\NVIDIA Corporation\\PhysX\\Common;C:\\Program Files\\NVIDIA Corporation\\NVIDIA NvDLISR;E:\\微信web开发者工具\\dll;C:\\Users\\xxx\\AppData\\Local\\Microsoft\\WindowsApps;E:\\VSCode\\bin;E:\\nvm;E:\\nodejs;C:\\Users\\xxx\\.dotnet\\tools;E:\\DevEco Studio\\bin;'
PATHEXT: '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.PY;.PYW;.CPL'
PROCESSOR_ARCHITECTURE: 'AMD64'
PROCESSOR_IDENTIFIER: 'Intel64 Family 6 Model 158 Stepping 10, GenuineIntel'
PROCESSOR_LEVEL: '6'
PROCESSOR_REVISION: '9e0a'
ProgramData: 'C:\\ProgramData'
ProgramFiles: 'C:\\Program Files'
ProgramFiles(x86): 'C:\\Program Files (x86)'
ProgramW6432: 'C:\\Program Files'
PROMPT: '$P$G'
PSModulePath: 'C:\\Users\\xxx\\Documents\\WindowsPowerShell\\Modules;C:\\Program Files\\WindowsPowerShell\\Modules;C:\\WINDOWS\\system32\\WindowsPowerShell\\v1.0\\Modules'
PUBLIC: 'C:\\Users\\Public'
SESSIONNAME: 'Console'
SystemDrive: 'C:'
SystemRoot: 'C:\\WINDOWS'
TEMP: 'C:\\Users\\xxx\\AppData\\Local\\Temp'
TERM_PROGRAM: 'vscode'
TERM_PROGRAM_VERSION: '1.84.2'
TMP: 'C:\\Users\\xxx\\AppData\\Local\\Temp'
USERDOMAIN: 'DESKTOP-TAFICRN'
USERDOMAIN_ROAMINGPROFILE: 'DESKTOP-TAFICRN'
USERNAME: 'xxx'
USERPROFILE: 'C:\\Users\\xxx'
VSCODE_GIT_ASKPASS_EXTRA_ARGS: '--ms-enable-electron-run-as-node'
VSCODE_GIT_ASKPASS_MAIN: 'e:\\VSCode\\resources\\app\\extensions\\git\\dist\\askpass-main.js'
VSCODE_GIT_ASKPASS_NODE: 'E:\\VSCode\\Code.exe'
VSCODE_GIT_IPC_HANDLE: '\\\\.\\pipe\\vscode-git-a88be4b604-sock'
VSCODE_INJECTION: '1'
VSCODE_INSPECTOR_OPTIONS: '{"inspectorIpc":"\\\\\\\\.\\\\pipe\\\\node-cdp.19900-c54c63d2-14.sock","deferredMode":false,"waitForDebugger":"","execPath":"E:\\\\nodejs\\\\node.exe","onlyEntrypoint":false,"autoAttachMode":"always","openerId":"a700dfbe62a133fa414d7fe5"}'
windir: 'C:\\WINDOWS'
[[Prototype]]: Object
}

  • 43
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晓风伴月

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值