首页
/ AIRI Monorepo 中的 pnpm 配置体系:以 pnpm-workspace.yaml 为核心的设置分层与实战

AIRI Monorepo 中的 pnpm 配置体系:以 pnpm-workspace.yaml 为核心的设置分层与实战

2026-09-05 22:22:00作者:尤峻淳Whitney

本篇以 AIRI 仓库内置的 pnpm 技能文档 core-config.md 为主体,系统讲解 pnpm 当前的配置分层模型:所有安装与解析设置收敛到 pnpm-workspace.yaml 与全局 config.yaml(YAML、camelCase 键名),.npmrc 只保留认证与 registry 凭据。结合 AIRI 这个 pnpm monorepo 的真实配置,你可以掌握如何在大型工作区中集中管理依赖版本(catalog)、强制版本(overrides)、构建脚本审批(allowBuilds)与包管理器/运行时锁定(packageManager / devEngines)。

一、配置二分法:设置与凭据严格分离

当前 pnpm 最重要的配置概念是:设置(settings)与凭据(credentials)分属两类文件。文档将其归纳为一张表:

类别 存放位置 格式
全部 pnpm/安装设置nodeLinkerhoistPatternautoInstallPeersoverridescatalog 等) pnpm-workspace.yaml(项目级)与 config.yaml(全局) YAML,camelCase 键名
认证与 registry 凭据_authTokencertkey 等) .npmrc(项目级,gitignore)与全局 rc INI

这里有三条必须牢记的行为变化:

  1. pnpm 不再读取 package.json 中的 pnpm 字段,该字段被彻底弃用;
  2. .npmrc 现在仅用于认证/registry 凭据,其他一切设置都归 pnpm-workspace.yaml
  3. YAML 中的键名是 camelCase(如 nodeLinker),而不是旧 .npmrc 时代沿用的 kebab-case。

AIRI 仓库本身就是这一模型的完整示范:根目录不存在 .npmrc(凭据不落库),全部解析行为都由 pnpm-workspace.yaml 声明,SKILL.md 中的技能说明也强调"在 pnpm 项目中检查 pnpm-workspace.yaml 获取设置与工作区结构,只在认证场景看 .npmrc"。

二、pnpm-workspace.yaml:主配置文件

该文件放在 workspace/项目根目录,即使是单包项目也用这个文件存放 pnpm 设置。文档给出的完整示例覆盖了近期的核心设置项:

# Workspace packages (omit for a single-package repo)
packages:
  - 'packages/*'
  - 'apps/*'
  - '!**/test/**'

# Common install settings (camelCase)
nodeLinker: isolated          # isolated (default) | hoisted | pnp
autoInstallPeers: true
strictPeerDependencies: false
savePrefix: '^'
saveExact: false
hoistPattern:
  - '*eslint*'
  - '*babel*'
publicHoistPattern: []
shamefullyHoist: false
dedupeDirectDeps: false
resolutionMode: highest       # highest | time-based | lowest-direct

# Centralized version management
catalog:
  react: ^18.2.0

# Force dependency versions (root only)
overrides:
  lodash: ^4.17.21
  'foo@^1.0.0>bar': ^2.0.0

# Extend/patch broken package manifests
packageExtensions:
  react-redux:
    peerDependencies:
      react-dom: '*'

# Peer dependency rules
peerDependencyRules:
  ignoreMissing:
    - '@babel/*'
  allowedVersions:
    react: '17 || 18'

逐段解读各设置组的语义:

  • 安装行为设置nodeLinker 决定 node_modules 的链接形态(isolated 为默认,另有 hoistedpnp);autoInstallPeers 控制是否自动安装 peer 依赖;savePrefix / saveExact 控制 pnpm add 写入版本范围时使用的前缀(^~ 或精确);hoistPattern / publicHoistPattern / shamefullyHoist 控制虚拟 store 中的提升行为;resolutionMode 在多个可用版本中的取舍策略为 highesttime-basedlowest-direct 三选一。
  • overrides:强制依赖版本的唯一入口,且只在根项目生效。键可以是包名,也可以是 直接依赖>传递依赖 的嵌套路径,用于把某个特定上游引入的间接依赖钉死到指定版本。
  • packageExtensions:在不改源码的前提下"修补"第三方包的 manifest,典型用途是为缺少 peer 声明的包补上 peerDependencies
  • peerDependencyRules:对 peer 依赖的告警/报错做规则化处理,如忽略缺失(ignoreMissing)或放宽版本要求(allowedVersions)。

AIRI 的真实 pnpm-workspace.yaml:一个生产级范本

AIRI 根目录的 pnpm-workspace.yaml 展示了上述机制在大型 monorepo 中的组合用法,几个值得注意的点:

catalogMode: prefer
minimumReleaseAge: 4320
minimumReleaseAgeExclude:
  - '@moeru/*'
  - '@proj-airi/*'
  # ... 其他内部/自有 scope
shellEmulator: true

packages:
  - packages/**
  - plugins/**
  - integrations/**
  - services/**
  - examples/**
  - docs/**
  - engines/**
  - apps/**
  - server/**
  - '!**/dist/**'

overrides:
  '@types/hast': 'catalog:'
  axios: npm:feaxios@^0.0.23
  hono: 4.13.3
  # ... npm: 别名替换若干上游包

patchedDependencies:
  mineflayer-pathfinder: patches/mineflayer-pathfinder.patch
  pixi-live2d-display: patches/pixi-live2d-display.patch
  uiohook-napi@1.5.5: patches/uiohook-napi@1.5.5.patch
  • packages 使用 ** 通配并配 !**/dist/** 负向排除:说明工作区 glob 支持任意深度匹配与排除规则,AIRI 用它把 packages/apps/server/ 等九个顶层目录全部纳入同一 workspace 并共用一个 lockfile。
  • overrides 中直接写 'catalog:':表示"该包以 catalog 中登记的版本为准",让强制版本与集中版本管理打通——@types/hast: 'catalog:' 就是把全仓所有 @types/hast 解析结果钉到 catalog 值。
  • npm: 别名 + overrides 组合axios: npm:feaxios@^0.0.23 表示全仓任何位置引入的 axios 都会被替换为 feaxios 这个别名包,这是一种典型的"依赖换皮"手段;patchedDependencies 则把 patches/ 目录下的补丁文件(如 patches/uiohook-napi@1.5.5.patch)应用到了指定版本之上,两者都属于"不修改上游源码即可修正依赖行为"的官方机制。
  • 供应链安全设置minimumReleaseAge: 4320(仅接受发布满指定小时数的版本)配合 minimumReleaseAgeExclude 对自有 scope 豁免,这正是新版"供应链接近安全"配置项的实际落地;技能文档目录中的 features-supply-chain-security.md 对该主题有更完整的论述。

三、catalog:集中化版本管理的仓库级应用

文档示例中的 catalog 只有单条目,AIRI 的 pnpm-workspace.yaml 则维护了 400 余条目的 catalog,覆盖 vuevitetypescriptelectronmineflayer 全家桶等全部关键依赖。其使用方式分三层:

  1. 子包声明版本为 catalog:。以 packages/plugin-sdk/package.json 为例:

    "dependencies": {
      "@moeru/eventa": "catalog:",
      "@moeru/std": "catalog:",
      "@proj-airi/plugin-protocol": "workspace:*",
      "nanoid": "catalog:"
    }
    

    子包不再写具体版本,只引用 catalog 键;workspace:* 则用于指向同 workspace 内的本地包。全仓 apps/packages/integrations/ 下的大量 package.json 都是这一模式。

  2. 命名 catalog(catalogs)为特定依赖组建独立版本集。AIRI 在根配置中声明了 vitestxsai 两个命名 catalog(pnpm-workspace.yaml):

    catalogs:
      vitest:
        '@vitest/browser-playwright': ^4.1.11
        '@vitest/coverage-v8': ^4.1.11
        vitest: ^4.1.11
      xsai:
        unspeech: ^0.1.16
    

    子包中即可引用 catalog:vitest 取该命名空间下的版本,例如根 package.json 里的 "@vitest/browser-playwright": "catalog:vitest""vitest": "catalog:vitest"。这样 vitest 生态可以整体升级而互不干扰。

  3. catalogMode: prefer 让 catalog 优先作为版本来源参与解析。解析结果最终沉淀进 pnpm-lock.yamlcatalogs 段,每个包记录 specifier 与实际 version,供审计与 pnpm ci 冻结安装使用。

这套机制的价值在于:升级 vue 只需改一行 catalog 条目,而不是逐个扫描 40 多个 package.json;AIRI 根 package.json 中的 up 脚本(taze -w -r -I && pnpm prune && pnpm dedupe)则用 taze 工具批量刷新版本后配合 prune/dedupe 收尾,构成完整的升级工作流。

四、全局配置:config.yaml 的位置

用户级(非认证)设置存放在全局 YAML 文件 config.yaml,按平台的查找顺序为:

  • $XDG_CONFIG_HOME/pnpm/config.yaml(若设置了该环境变量)
  • Linux:~/.config/pnpm/config.yaml
  • macOS:~/Library/Preferences/pnpm/config.yaml
  • Windows:~/AppData/Local/pnpm/config/config.yaml

同目录下还有一个名为 rc 的伴生全局文件,只承载 registry/认证设置。这条分层与项目级形成对照:项目行为看 pnpm-workspace.yaml,机器级默认看 config.yaml,密钥永远只进 .npmrc/rc

五、workspace 内按包设置:packageConfigs

新版 pnpm 取消了子包各自的 .npmrc,改为在根 pnpm-workspace.yaml 中用 packageConfigs 为单个包覆写设置。文档给出两种形态:

packageConfigs:
  # Map form: keyed by package name
  project-1:
    saveExact: true
  project-2:
    savePrefix: '~'
  # Array form: pattern-matched rules
  # - match: ['project-1', 'project-2']
  #   modulesDir: node_modules
  #   saveExact: true
  • Map 形态以包名为键,精确匹配某个 workspace 包;
  • 数组形态以 match 做模式匹配,可对一组包批量下发规则(如统一 saveExactmodulesDir 等)。

对多子包 monorepo 来说,这取代了过去在子目录散落 .npmrc 的做法,让"每个包的行为差异"也在根文件内一处可查、一处可改。

六、.npmrc:只装认证,且按优先级读取

项目级认证文件保持在 .npmrc,但要加入 .gitignore 防止 token 入库。认证文件的读取优先级从高到低:

  1. <workspace root>/.npmrc(项目级,gitignored)
  2. <pnpm config>/auth.ini(由 pnpm login 写入)
  3. ~/.npmrc(兼容 npm 的兜底)

文档给出的标准 INI 示例(引用环境变量注入 token,而非明文):

//registry.npmjs.org/:_authToken=${NPM_TOKEN}
@myorg:registry=https://npm.myorg.com/
//npm.myorg.com/:_authToken=${MYORG_TOKEN}

注意 registry 的非机密配置(默认 registry、scope 映射、命名 registry 别名)应写在 pnpm-workspace.yaml 而不是 .npmrc

registries:
  default: https://registry.npmjs.org/
  '@my-org': https://private.example.com/
# Named registry aliases usable as a prefix, e.g. `pnpm add work:@corp/lib`
namedRegistries:
  work: https://npm.work.example.com/

namedRegistries 允许把私有 registry 起别名后作为前缀使用(pnpm add work:@corp/lib),使安装命令不必再携带完整 URL。

安全要点:自 v11 起,项目级 .npmrc 中 registry/proxy URL 与凭据键的环境变量展开被禁用,目的是防止恶意仓库通过伪造 .npmrc 窃取已注入的环境变量密钥。动态 token 行应放入用户级认证文件(第 2 优先级的 auth.ini)。AIRI 仓库根目录没有提交任何 .npmrc,与"凭据不进库、认证走本地/CI secret"的约定一致。

七、pnpm config 命令:读取与写入

pnpm config 子命令是操作上述配置的入口,行为同样遵循"设置进 YAML、认证进 rc"的分离:

# Writes to global config.yaml / rc by default
pnpm config set nodeVersion 22.0.0
pnpm config set --location=project nodeVersion 22.0.0   # writes pnpm-workspace.yaml

# JSON values create arrays/objects
pnpm config set --location=project --json allowBuilds '{"react": true}'

# get/list print JSON (no longer INI) since v11
pnpm config get nodeLinker
pnpm config get 'allowBuilds.react'
pnpm config list

行为要点:

  • 不带 --location 时写入全局 config.yaml / rc;加 --location=project 才写入 pnpm-workspace.yaml
  • --json 把值按 JSON 解析,从而能写入数组/对象(如 allowBuilds 这种映射型设置);
  • v11 起 get/list 的输出统一为 JSON(不再是 INI),方便脚本消费;get 支持 allowBuilds.react 这样的点路径取值。

八、环境变量:只认 pnpm_config_*

环境变量注入设置的命名空间也发生了变化:使用 pnpm_config_*(或大写 PNPM_CONFIG_*),不再读取 npm_config_*

pnpm_config_save_exact=true pnpm add foo

这对 CI 场景意味着:沿用 npm 时代 npm_config_* 的注入脚本会静默失效,需要按新前缀改写。

九、改名/移除的设置项对照表

从旧版 .npmrc 时代迁移时,以下设置项已更名或删除,文档给出了完整对照:

旧名(已移除) 替代项 说明
onlyBuiltDependenciesneverBuiltDependenciesignoredBuiltDependenciesonlyBuiltDependenciesFile allowBuilds: { name: true|false } 单一映射表统一控制构建脚本审批
managePackageManagerVersionspackageManagerStrictpackageManagerStrictVersionCOREPACK_ENABLE_STRICT pmOnFail: download|ignore|warn|error 实际运行的 pnpm 版本与声明版本不一致时的行为
useNodeVersion devEngines.runtime(写在 package.json 运行时锁定
auditConfig.ignoreCves auditConfig.ignoreGhsas 改用 GHSA 编号
allowNonAppliedPatches allowUnusedPatches ignorePatchFailures 已删除(补丁失败现在总是抛错)
package.json#pnpm 字段 pnpm-workspace.yaml 完全不再读取

AIRI 的根 pnpm-workspace.yaml 中的 allowBuilds 就是新命名下的完整实例:逐包列出 true/false 审批结果,例如 esbuild: truesharp: truebetter-sqlite3: false,并带注释说明 simple-git-hooks: false 是为 shellEmulator: true 场景下的已知问题所做的工作区规避(workaround)。旧的四个"构建依赖白名单"设置由此收敛为一张表。

十、package.json 中的包管理器与运行时锁定

package.json 在新模型中只保留两件事:packageManager 字段与 devEngines 声明。文档示例:

{
  "packageManager": "pnpm@10.0.0",
  "devEngines": {
    "packageManager": { "name": "pnpm", "version": ">=11.0.0 <12.0.0", "onFail": "download" },
    "runtime": { "name": "node", "version": "22.x", "onFail": "download" }
  }
}

语义差异:

  • packageManager 要求精确版本(如 pnpm@10.0.0);
  • devEngines.packageManager 支持版本范围,解析出的实际版本会写入 lockfile;
  • 两者的 onFail 都可以通过设置项 pmOnFail / runtimeOnFail 在不改 manifest 的情况下整体覆盖。

AIRI 的根 package.json 声明了 "packageManager": "pnpm@11.24.0",即仓库锁定的 pnpm 主版本正是 11.x——上文所有 v11 行为变化(config 输出 JSON、pnpm 字段弃用、项目级 .npmrc 禁用环境变量展开等)都是该仓库的实际运行前提。技能文档 SKILL.md 也注明本技能"基于 pnpm 10.x 生成,同时覆盖 v11 的行为变化(config 拆分、isolated global packages、allowBuildspmOnFail、global virtual store)",与本仓库 pnpm@11.24.0 的声明相互印证。

十一、关键要点小结

  • 所有 pnpm 设置都放 pnpm-workspace.yaml(camelCase)或全局 config.yaml.npmrc 只保留认证/registry 凭据,并应 gitignore。
  • package.json#pnpm 字段与 npm_config_* 环境变量都不再被读取,环境变量前缀为 pnpm_config_* / PNPM_CONFIG_*
  • 工作区内按包差异化设置用 packageConfigs(Map 或模式匹配数组两种形态),子包级 .npmrc 已取消。
  • 构建脚本审批统一为一张 allowBuilds 映射表(AIRI 根配置中可看到 30 余条真实审批记录);包管理器严格性统一为 pmOnFail 一个设置。
  • pnpm config get / list 自 v11 起输出 JSON;--location=projectset 的落点从全局切到 pnpm-workspace.yaml
  • 版本集中管理走 catalog / 命名 catalogs(AIRI 使用 catalog:catalog:vitest 引用),配合 overridespatchedDependencies 与供应链设置(minimumReleaseAge 等)构成完整的依赖治理面。
登录后查看全文
热门项目推荐
相关项目推荐