全局软件包
全局软件包是使用 pnpm add -g 安装到系统范围内的 CLI 工具和实用程序。在 pnpm v11 中,全局包管理被重新设计,以提升隔离性和可靠性。
安装全局软件包#
pnpm add -g <pkg>
例如:
pnpm add -g typescript prettier eslint
注意
请勿通过 sudo 运行这些命令。 pnpm 将全局包与配置保存在调用者的主目录下,因此在 sudo 下它们会静默地作用于 root 用户的主目录,而非你的主目录。自 v11.21.0 起,以 root 身份运行 pnpm setup、pnpm self-update 以及任何修改全局安装的命令都会打印警告,而在 pnpm v12 中会以 ERR_PNPM_SUDO_NOT_SUPPORTED 失败。像 pnpm bin -g 这样的只读命令不受影响。
独立安装#
每个全局安装的软件包(或一起安装的一组软件包)都有其自己的独立安装目录,其中包含自己的 package.json、node_modules/ 和锁文件。这样可以阻止全局包之间因对等依赖冲突、提升更改或版本错位而相互干扰。
隔离安装存储在 {pnpmHomeDir}/global/v11/{hash}/,其中哈希值是从一起安装的软件包集合中派生出来的。
例如,运行以下两个命令:
pnpm add -g typescript
pnpm add -g prettier
创建两个独立的隔离安装——typescript 和 prettier 各自拥有自己的 node_modules 树,并且不会影响彼此的依赖项解析。
在单个命令中安装多个空格分隔 软件包也会为每个软件包单独创建一个单独的安装:
pnpm add -g eslint prettier
eslint和prettier都会得到它们自己的 node_modules 树和锁pnpm文件并可以被独立删除 — — pnpm remove -g eslint 保持 prettier 不变。
若要将多个包捆绑到同一个隔离安装环境中——从而使它们共享 node_modules 树和锁文件、相互解析对等依赖项,并可被一并移除——请将它们作为逗号分隔的列表进行传递:
pnpm add -g eslint,prettier
这里的 eslint 和 prettier 形成了一个安装组。使用 pnpm remove -g 删除任一项都会删除整个组。
这两种形式可以混合使用。例如:
pnpm add -g eslint,prettier typescript
将 eslint 和 prettier 打包为一次孤立安装,同时单独安装 typescript。
目录布局#
{pnpmHomeDir}/global/v11/ 的内容如下所示:
{pnpmHomeDir}/global/v11/
├── {hash-A} → 符号链接 → ./{hash-A-target}/
├── {hash-A-target}/ ← 隔离的安装目录
│ ├── package.json ← 列出一起安装的软件包
│ ├── pnpm-lock.yaml ← 此安装组的锁定文件
│ └── node_modules/
│ ├── <pkg>/ ← 顶级依赖项,符号链接到全局虚拟存储
│ └── .pnpm/
├── {hash-B} → 符号链接 → ./{hash-B-target}/
├── {hash-B-target}/ ← 另一个隔离的安装目录
└── store/ ← 共享的全局虚拟存储
└── ...
{hash}条目是符号链接;pnpm 会扫描它们以枚举活动的安装。- 目标是真实的目录,它们就像普通的 pnpm 项目一样——每个项目都有自己的
package.json和 lockfile。 - 共享的
store/目录保存了 全局虚拟存储。每个安装组的直接依赖项(即其node_modules/根目录下的条目)都是指向该存储的符号链接,因此实际的包内容是共享的,而不是按组复制的。 - 二进制 shims 位于
{pnpmHomeDir}/bin/中,并指向相应的安装组的node_modules。
当一个软件包被删除或其安装组被替换时,哈希符号链接会被更新,孤立的目标目录最终会被 pnpm store prune 清理掉。
列出全局软件包#
pnpm list -g
pnpm list -g --json # 机器可读
pnpm list -g --parseable # 仅路径
由于每个安装组都有自己的锁文件,因此跨多个组列出只能可靠地聚合它们一起安装的顶级软件包——来自不同组的传递依赖关系树无法一致地合并。因此:
pnpm list -g(默认--depth=0)始终有效,并显示所有全局安装的软件包。pnpm list -g --depth=<n>(其中n > 0)仅在以下情况下显示完整的依赖关系树:- 只有一个全局安装组,或者
- 位置参数将请求范围缩小到单个安装组,例如
pnpm list -g eslint --depth=1。
如果请求 --depth>0 但无法将请求缩小到单个安装组,pnpm 将报错 ERR_PNPM_GLOBAL_LS_DEPTH_NOT_SUPPORTED。
管理全局软件包#
| 命令 | 描述 |
|---|---|
pnpm add -g <pkg> | 全局安装软件包 |
pnpm remove -g <pkg> | 移除一个全局安装的软件包(如果该软件包被打包在某个安装组中,则整个组将被移除) |
pnpm update -g [pkg] | 更新全局软件包(重新安装到新的独立目录中) |
pnpm list -g | 列出所有全局安装的软件包 |
注意
不再支持 pnpm install -g (无参数)。使用 pnpm add -g <pkg> 安装特定软件包。
二进制文件位置#
全局安装的二进制文件存储在 PNPM_HOME 的 bin 子目录中(即 $PNPM_HOME/bin/)。这样可以保持 PNPM_HOME 目录的清洁——当 PNPM_HOME 在 PATH 中时,像 global/ 和 store/ 这样的内部目录不会污染 shell 自动补全。
升级到 pnpm v11 后,运行 pnpm setup 来更新你的 shell 配置,以便 $PNPM_HOME/bin 在你的 PATH 中。
你可以通过以下方式检查当前的全局 bin 目录:
pnpm bin -g
项目感知的全局二进制#
添加于:v12.0.0-rc.2(仅 pnpm v12)
全局命令可以跟随你当前所在的项目。当你在项目中运行全局命令,而该项目要求同一个工具的另一版本时,pnpm 会运行该项目所要求的版本。
最实用的场景是 Node.js 本身。假定某个项目固定了运行时版本:
{
"devEngines": {
"runtime": {
"name": "node",
"version": "^22.0.0",
"onFail": "download"
}
}
}
在该目录内执行裸 node 就会运行 Node.js 22,即便你全局的 Node.js 属于另一个主版本:
cd ~/projects/legacy-app
node --version # v22.x.x — 该版本由项目固定
cd ~
node --version # 你全局安装的版本
无需任何 shell 钩子、.bashrc 修改或 use 式命令 — 调度由 pnpm 写入全局 bin 目录的填充程序自行完成。
版本的选定方式#
pnpm 会自当前工作目录向上寻找最近的、提供该命令的项目,随后:
- 对于运行时(
node、deno、bun),只考虑清单中固定的版本 — 即devEngines.runtime,其次是engines.runtime。固定的版本会按需下载至全局虚拟存储并直接执行。对于运行时,绝不会查询项目的node_modules/.bin,因此依赖不可能提供你运行的node。 - 对于**包管理器**(
npm、yarn、bun),该支持于 v12.0.0-rc.6 加入:以项目的packageManager或devEngines.packageManager固定版本为准,pnpm 会按需准备该版本。该锁定版本优先级高于全局安装的对应包管理器副本,因为它是项目自身指定的安装版本。 - 对于其他任何包,将使用项目的
node_modules/.bin/<name>。
pnpm 主目录内部的目录会被跳过,因为全局安装不属于项目。
如果项目未提供任何内容,或该查找被拒绝,则运行全局安装的版本 — 命令绝不会仅仅因为未应用调度而失败。
信任#
你 cd 进入的项目,并不会自动获准用它自己的二进制文件替代你全局的二进制文件。
本节描述默认的 auto 策略。在 auto 下:
-
稳定的 Node.js 发布会在运行前依据 Node.js 发布团队的签名进行验证,因此无需询问即可切换。(在基于 musl 的系统上,对应构建未签名,因此会归入需询问的情形。)
-
其余一切 — Deno、Bun、Node.js 预发布版本,以及你启用的任何普通包 — 都会按项目与候选项各询问一次:
The project at "/home/user/projects/app" provides its own "tsc", which will be used instead of the globally installed one. Do you trust this project? [y/N]
回答会保存在机器本地的注册表中,以项目目录以及该确切二进制文件的指纹作为键。若二进制文件或其提供者包发生变化,pnpm 会再次询问,而不会复用原来的批准。
在 CI 以及任何非交互式会话中,无法发起询问,因此会运行全局版本,且不会记录任何内容。
另外两个策略会影响哪些候选项会进入该询问。 prompt 会使每个候选项都经过该询问,包括已通过签名验证的稳定版 Node.js 发布;回答依旧会被记住,因此按项目与候选项各询问一次,而不是每次运行都询问。 always 会完全跳过询问并直接切换 — 这正是它能在 CI 中使用的原因,因为否则提示会回退到全局版本。
还有一道与策略无关的防护:项目的命令必须来自与全局命令相同的包。若某个项目提供的是来自其他包的仿冒 tsc,则不匹配,此时运行的是你的全局 tsc。
参与其中的包#
默认只有运行时会参与。自 v12.0.0-rc.6 起,全局安装包管理器(pnpm add -g yarn)也会为其添加一个条目,因此它会像全局安装的 Node.js 已经遵循 devEngines.runtime 那样遵循项目的固定版本。由你自行设置的条目(包括 false)会保持你设定的状态。
通过 globalShims 可启用其他包,键为提供命令的包名:
globalShims:
typescript: true
因为 pnpm 在安装时决定为哪些可执行文件写入调度填充程序,所以在启用某个已全局安装的包后,需要重新安装它:
pnpm add -g typescript
将某个包关闭无需重新安装 — 该设置在每次调度时都会重新读取。同一设置可以按包禁用调度,或用 globalShims: false 整体禁用。若只针对单条命令,可设置 PNPM_SHIM_BYPASS=1:
PNPM_SHIM_BYPASS=1 node --version
完全没有全局安装的包,也能借助 pnpm shim add 得到调度命令 — 这正是让只有 pnpm 的机器在 Yarn 项目中使用 yarn 的关键。
注意
globalShims 刻意不从项目的 pnpm-workspace.yaml 读取 — 只读取全局配置文件、pnpm 主目录自身的 pnpm-workspace.yaml 以及环境变量。具体细节见该设置的文档。
全局虚拟存储#
全局安装使用全局虚拟存储。软件包存储在 {storeDir}/links 中,并在全局安装之间共享。这样可以避免多个全局包依赖于相同库时进行冗余获取。
全局注册本地软件包#
要使本地软件包的二进制文件在系统范围内可用,请在软件包目录中使用 pnpm add -g . 命令:
cd ~/projects/my-tool
pnpm add -g .
这样就注册了软件包的 bin 条目,以便可以从任何地方调用它们。请参阅 pnpm link 了解更多详情。
构建脚本批准#
包含构建脚本(例如 postinstall)的全局软件包需要获得批准。当你安装需要运行构建脚本的全局软件包时,pnpm 会提示你以交互方式批准或拒绝构建。
你还可以使用 --allow-build 标志预先批准构建:
pnpm add -g --allow-build=esbuild esbuild