AIRI 单仓的 pnpm 实践手册:从 pnpm-workspace.yaml 配置模型到 Catalogs、Patches 与供应链安全
2026/9/6 21:45:53 网站建设 项目流程

AIRI 单仓的 pnpm 实践手册:从 pnpm-workspace.yaml 配置模型到 Catalogs、Patches 与供应链安全

【免费下载链接】airi💖🧸 Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-sama's altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi

本文以 AIRI 仓库中的技能文档 .agents/skills/pnpm/SKILL.md 为主体,系统讲解 pnpm(以 10.x/11.x 为基准)的命令体系、配置模型与高级特性,并结合 AIRI 自身的 pnpm-workspace.yaml、package.json 与 patches/ 目录,展示这套方案在一个真实大型 Vue/TS 单仓(monorepo)中的落地形态。读完后,你将能够独立配置 pnpm 工作区、用 Catalogs 统一版本、管理第三方补丁,并理解 pnpm 11 的供应链安全机制如何保障安装过程的可信性。

1. 文档定位:一份面向 Agent 的 pnpm 技能地图

SKILL.md 的 frontmatter 说明了它的用途:当需要在 pnpm 项目中执行 pnpm 命令、配置pnpm-workspace.yaml工作区,或管理 catalogs、patches、overrides、config dependencies 与全局 virtual store 时使用该技能。文档基于 pnpm 10.x 生成(生成日期 2026-06-22,作者 Anthony Fu),同时覆盖 v11 的行为变更:配置拆分、隔离式全局包、allowBuildspmOnFail与全局 virtual store。

技能文档把 pnpm 知识组织为四大板块,每块都指向一份参考文档:

板块覆盖内容参考文档
CoreCLI 命令、配置模型、工作区、内容寻址存储core-cli、core-config、core-workspaces、core-store
FeaturesCatalogs、Overrides、Patches、Aliases、Hooks、Peer Dependencies、Config Dependencies、全局虚拟仓库、供应链安全features-catalogs、features-patches、features-supply-chain-security 等
Best PracticesCI/CD 搭建、从 npm/Yarn 迁移、性能优化best-practices-ci、best-practices-migration、best-practices-performance

AIRI 根目录 package.json 中"packageManager": "pnpm@11.24.0"表明仓库实际运行在 pnpm 11 上,因此下文的 v11 行为(尤其是配置拆分与构建脚本审批)是该仓库的真实约束条件。

2. 配置模型:pnpm-workspace.yaml 是唯一主配置

技能文档开篇就强调了当前 pnpm 最重要的配置概念——配置两分法

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

三条关键变化(见 core-config):

  1. package.jsonpnpm字段不再被读取——所有设置移入pnpm-workspace.yaml
  2. .npmrc仅用于认证/仓库凭据,其余配置(含以前写在.npmrc的 kebab-case 项)一律进 YAML,且键名改为 camelCase;
  3. npm_config_*环境变量不再被读取,改用pnpm_config_*(或PNPM_CONFIG_*),例如pnpm_config_save_exact=true pnpm add foo

工作区内的按包配置也不再使用子项目.npmrc,而是通过根目录的packageConfigs

# pnpm-workspace.yaml packageConfigs: project-1: saveExact: true project-2: savePrefix: '~'

旧的构建脚本审批配置(onlyBuiltDependenciesneverBuiltDependencies等)统一收敛为一个allowBuilds映射;包管理器严格性收敛为pmOnFail: download | ignore | warn | error;运行时固定改用package.jsondevEngines.runtime

AIRI 的真实配置:逐段拆解

AIRI 的 pnpm-workspace.yaml 是该配置模型的完整实例,下面逐段对照:

工作区成员(packages)——覆盖全部九个顶层目录,并显式排除构建产物:

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

根 package.json 中还保留了一个workspaces数组(内容与上述 glob 一致),这是给 npm 生态工具看的冗余声明,pnpm 本身只认pnpm-workspace.yaml

Catalog 行为与供应链参数

catalogMode: prefer # pnpm add 时优先使用默认 catalog 中的版本 minimumReleaseAge: 4320 # 发布 72 小时内的新版本默认不安装(默认值 1440 分钟/1 天) minimumReleaseAgeExclude: # 项目自有 scope 豁免,允许即时安装 - '@moeru/*' - '@proj-airi/*' - '@vishot/*' - '@xsai/*' # ... 等 shellEmulator: true # 用 Node 模拟 shell 执行生命周期脚本,规避 Windows/沙箱问题

minimumReleaseAge的取值4320分钟(3 天)高于 v11 默认值1440(1 天),说明 AIRI 对新发布的依赖采取了更保守的窗口;minimumReleaseAgeExclude精确豁免了项目自有 scope(@proj-airi@moeru等),保证内部包的迭代不受发布年龄限制。

Overrides:强制版本与 npm 别名。这是 AIRI 依赖治理的核心手段之一:

overrides: '@types/hast': 'catalog:' # 覆盖为 catalog 版本,版本只在一处维护 array-flatten: npm:@nolyfill/array-flatten@^1.0.44 # 用 npm: 别名替换为 nolyfill 版本 axios: npm:feaxios@^0.0.23 # 全局替换 axios 为兼容封装 feaxios 'eslint-plugin-sonarjs>typescript': 'catalog:' # 仅针对传递依赖路径定向覆盖 hono: 4.13.3 # 精确锁定 onnxruntime-web: npm:onnxruntime-web@^1.27.0 # 其余为若干 @nolyfill/* 替换(isarray、safe-buffer、side-channel 等)

这里体现了技能文档中两类特性:npm:包别名(features-aliases)用于在不改依赖方代码的前提下整体替换实现(axios → feaxiosnode-pty → @lydell/node-pty亦见于 catalog 定义),以及catalog:协议在overrides中的合法性——让被覆盖项与默认 catalog 保持同一版本来源。

packageExtensions:修补有缺陷的包清单。当上游包没有正确声明 peer/optional 依赖时,用packageExtensions在本地“扩展”其 manifest:

packageExtensions: '@formkit/auto-animate': peerDependencies: vue: '*' '@pixiv/three-vrm-core': peerDependencies: '@types/three': '*' 'vitepress': peerDependencies: vite: '*' vue: '*' # ... 共 11 处扩展

这避免了pnpm peers check(见 core-cli)报出缺失 peer 告警,也无需peerDependencyRules.ignoreMissing粗暴忽略。

3. Catalogs:单仓版本治理的“单一事实来源”

features-catalogs 定义了 Catalogs 的完整语义:在pnpm-workspace.yaml中定义版本,各包package.json中以catalog:引用。

  • 默认 catalog 写在catalog:键下,catalog:catalog:default的简写;
  • 命名 catalog 写在catalogs:键下,引用语法为catalog:<name>
  • catalog:协议可用于dependencies/devDependencies/peerDependencies/optionalDependencies,也可用于pnpm-workspace.yamloverrides,甚至 CLI:pnpm add react@catalog:pnx shx@catalog:
  • 行为由catalogMode控制(v10.12+):manual(默认,不自动纳入)、preferpnpm add时若版本匹配则写入catalog:)、strict(只允许 catalog 版本);
  • 发布时catalog:会被替换为真实版本(如"catalog:""^18.2.0"),对外发布透明。

AIRI 的 catalog 规模

AIRI 的默认 catalog 收录了400 余项依赖(pnpm-workspace.yaml 第 44 行起),覆盖 Vue 全家桶(vue ^3.5.41pinia ^4.0.3vue-router ^5.2.0)、Three.js 生态(three ^0.185.1@pixiv/three-vrm ^3.5.5)、Electron 工具链(electron ^43.4.1electron-builder ^26.15.3)、语音管线(kokoro-jsonnxruntime-web@huggingface/transformers)、以及 Minecraft 集成(mineflayer ^4.37.1及全套 prismarine 包)。同时定义了两个命名 catalog:

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

根 package.json 正是消费方:devDependencies 中"vitest": "catalog:vitest""@vitest/browser-playwright": "catalog:vitest",其余工具链依赖统一"catalog:"。配合catalogMode: prefer,日后pnpm add时会自动倾向写入 catalog 引用而非散落的版本字面量——升级依赖只需改一处,且各包package.json基本不动,显著减少合并冲突。这正是参考文档列出的四大收益:单一事实来源、全仓一致、一处升级、更少冲突。

文档还给出了一条从 overrides 迁移到 catalogs 的自动化命令:pnpx codemod pnpm/catalog

4. 核心命令体系:安装、脚本、过滤与补丁

以下命令清单完整继承自 core-cli,并结合 AIRI 根package.json中实际脚本(pnpm -rF @proj-airi/stage-web devturbo run build -F="..."taze -w -r -I && pnpm prune && pnpm dedupe)给出验证。

安装类

pnpm install # 安装全部依赖(别名 pnpm i) pnpm add <pkg> [-D|-O|-E] # dev/optional/精确版本 pnpm remove <pkg> # 别名 rm / uninstall / un pnpm update [--latest|-i] pnpm install --frozen-lockfile # lockfile 将被修改时直接失败(CI 中自动启用) pnpm ci # = pnpm clean + install --frozen-lockfile pnpm clean [--lockfile] # 清理全工作区 node_modules(别名 purge)

v11 注意:tarball 与 lockfile 校验和不匹配是硬错误(ERR_PNPM_TARBALL_INTEGRITY),需人工核验新字节后才可pnpm install --update-checksums;CI 中由更新大版本 pnpm 写出的 lockfile 也会直接失败。

脚本执行

pnpm run <script> # 或简写 pnpm <script> pnpm run build -- --watch # -- 之后的参数透传 pnpm run --if-present build pnpm exec <cmd> # 执行本地二进制,如 pnpm exec eslint .

隐藏脚本(.开头)不可直接执行;与内置命令同名的脚本(cleansetupdeployrebuild)优先执行package.json脚本,强制用内置命令时写作pnpm pm clean。一次性运行无需安装的工具:

pnx create-vite my-app # pnx == pnpm dlx == pnpx,支持 catalog: 协议 pnx --package=@scope/tool tool --help

v11 中dlx/pnx默认使用全局 virtual store,并遵守minimumReleaseAgetrustPolicy等供应链设置。

工作区过滤(AIRI 日常脚本的骨架)

pnpm -r run <script> # 所有包含该脚本的包(--recursive) pnpm --filter <pattern> run <script> # -F 简写 pnpm --filter "./packages/**" run build # glob 目录 pnpm --filter "@myorg/*" run lint # 名称前缀 pnpm -r --parallel run dev # 并行 pnpm --filter "...@scope/app" build # 包及其依赖 pnpm --filter "@scope/core..." test # 包及其依赖者 pnpm --filter "...[origin/main]" build # 相对某 git ref 有变更的包

AIRI 根脚本大量使用这种组合,例如"dev:apps": "pnpm -rF=\"./apps/*\" run --parallel dev""typecheck": "pnpm -rF=\"./packages/*\" -F=\"./apps/*\" -F=\"./server/**\" -F=\"./docs\" --parallel typecheck"。工作区级安装/发布命令:

pnpm --filter @myorg/app add lodash # 向指定包添加依赖 pnpm publish -r --no-git-checks # CI 发布(跳过 git 工作树检查)

发布时workspace:协议按变体转换为真实版本(workspace:*→ 实际版本,workspace:^^x.y.zworkspace:~~x.y.z,见 core-workspaces)。

补丁(Patches):修改第三方包并固化到仓库

features-patches 定义的三步流程:

pnpm patch <pkg>@<version> # 1. 生成可编辑副本并输出临时路径 # 2. 在临时目录中修改源码 pnpm patch-commit <path> # 3. 生成 patches/<pkg>.patch 并写入配置 pnpm patch-remove <pkg>@<version>

提交后配置记录在pnpm-workspace.yamlpatchedDependencies(不再是package.json#pnpm)。不带版本号的键(express:)会补丁所有已安装版本;v11 移除了ignorePatchFailures,补丁应用失败必定抛错,可用allowUnusedPatches: true容忍“已声明但未应用”的补丁。

AIRI 的 5 个真实补丁(pnpm-workspace.yaml 第 38-43 行):

patchedDependencies: mineflayer-pathfinder: patches/mineflayer-pathfinder.patch pixi-live2d-display: patches/pixi-live2d-display.patch sponsorkit@17.1.0: patches/sponsorkit@17.1.0.patch tab-election@4.6.2: patches/tab-election@4.6.2.patch uiohook-napi@1.5.5: patches/uiohook-napi@1.5.5.patch

可以看到两种键名风格的混用:mineflayer-pathfinderpixi-live2d-display不带版本号(补丁所有版本),而sponsorkit@17.1.0uiohook-napi@1.5.5等精确锁定版本。补丁文件本体就在仓库 patches/ 目录下(如 patches/sponsorkit@17.1.0.patch),全部以 unified diff 格式提交入库,任何开发者pnpm install后即自动获得一致的第三方行为——这正是补丁“可复现、可审查”的要点。

5. 供应链安全:v11 默认拦截的攻击面

features-supply-chain-security 描述了 pnpm 默认拦截的几类攻击向量,AIRI 的配置全部落在这一框架内:

构建脚本审批(allowBuilds)

默认情况下 pnpm不执行任何依赖的生命周期脚本(preinstall/install/postinstall),必须显式审批。审批集中在一个allowBuilds映射(替换了被移除的onlyBuiltDependencies等五个旧配置):

allowBuilds: esbuild: true core-js: false 'nx@21.6.4 || 21.6.5': true # 支持版本选择器

未列出的包视为“未审查”,默认被拦截;strictDepBuilds(默认true)使未审查构建导致安装非零退出(ERR_PNPM_IGNORED_BUILDS)。审批命令:

pnpm approve-builds # 交互式 pnpm approve-builds --all # 全批 pnpm approve-builds esbuild fsevents !core-js # ! 表示拒绝 pnpm add --allow-build=esbuild my-bundler # 添加时顺带审批

AIRI 的 pnpm-workspace.yaml 第 455-488 行给出了一个 30+ 条目的审批清单:electronesbuildsharpcanvasffmpeg-static等为true(这些包的 postinstall 下载/编译二进制资源是必需的);@ax-llm/ax@prisma/clientbetter-sqlite3false;最典型的一条:

simple-git-hooks: false # [workaround] With `shellEmulator: true`, simple-git-hooks install may fail when no .git dir exists.

注释直接解释了拒绝原因——在shellEmulator: true下无.git目录时该脚本会失败,这是审批机制与根脚本"postinstall": "pnpm exec simple-git-hooks && ..."(改用 exec 手动触发)配合工作的结果。文档同时警告逃生舱dangerouslyAllowAllBuilds: true会放行一切构建脚本,应避免使用。

最小发布年龄、信任策略与来源封锁

minimumReleaseAge: 4320 # 分钟;v11 默认 1440(1 天) minimumReleaseAgeExclude: [...] # 豁免列表 trustPolicy: no-downgrade # 包信任级别降级(如失去 provenance)即失败 blockExoticSubdeps: true # 默认;仅直接依赖可用 git/直连 tarball 等“奇特来源”

minimumReleaseAge覆盖全部依赖(含传递依赖),恶意发布通常在一小时内被下架,延迟窗口能有效规避;minimumReleaseAgeStrict控制“无满足年龄的版本时”是失败还是回退旧版。

Lockfile 完整性

自 v11 起,下载 tarball 的哈希与pnpm-lock.yaml不一致即为硬错误(ERR_PNPM_TARBALL_INTEGRITY),--forcepnpm update均不能绕过——这是保护已提交 lockfile 不被投毒注册表/代理篡改的最后一道防线。内容寻址 store、全局 virtual store 与元数据缓存都属于 pnpm 的信任域,只应在相互信任的机器/任务间共享。

6. Store、Node Linker 与全局虚拟仓库

core-store 说明了 pnpm 的存储原理:所有包按内容哈希存入全局内容寻址 store,项目node_modules通过硬链接/符号链接引用,同一版本跨项目只存一份。核心命令与布局:

pnpm store path # 查看 store 位置 pnpm store prune # GC 无引用包(含全局 virtual store 链接) pnpm store status # 校验完整性 pnpm store add <pkg>
project/ └── node_modules/ ├── .pnpm/ # 虚拟仓库:硬链接到全局 store │ └── lodash@4.17.21/node_modules/lodash/ ├── lodash -> .pnpm/lodash@4.17.21/node_modules/lodash └── express -> .pnpm/express@4.18.2/node_modules/express

nodeLinker三模式(pnpm-workspace.yaml中设置):

  • isolated(默认):符号链接虚拟仓库,严格依赖解析、无幽灵依赖——AIRI 采用默认值;
  • hoisted:npm 式扁平node_modules,供不支持符号链接的工具兼容;
  • pnp:无node_modules(需另设symlink: false)。

网络盘/Docker 等硬链接失效场景可用packageImportMethod: copy(默认auto按 clone → hardlink → copy 顺序尝试)。v11.7+ 的frozenStore: true允许对只读 store(Nix store、只读挂载、OCI 层)执行pnpm install --frozen-store --offline --frozen-lockfile

全局虚拟仓库(features-global-virtual-store):enableGlobalVirtualStore: true时项目不再各自维护node_modules/.pnpm,其node_modules只是指向<store-path>/links/中按依赖图哈希共享目录的符号链接。v11 中它已是pnpm dlx/全局安装的默认,项目安装仍为 opt-in,特别适合 git worktree 多 Agent 并行开发场景。

7. CI/CD:冻结 lockfile、store 缓存与镜像构建

best-practices-ci 的要点:CI 环境下 pnpm 自动进入 frozen-lockfile 模式,v11 起对“由更新大版本写入的 lockfile”直接失败(不回写),因此 CI 的 pnpm 大版本需与 lockfile 生成者保持一致。

GitHub Actions 标准写法:

- uses: pnpm/action-setup@v4 with: run_install: false - name: Get pnpm store directory shell: bash run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - uses: actions/cache@v4 with: path: ${{ env.STORE_PATH }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - run: pnpm install --frozen-lockfile

Docker 多阶段构建中,v11 的全局 bin 位于$PNPM_HOME/bin(需ENV PATH="$PNPM_HOME/bin:$PATH");官方镜像ghcr.io/pnpm/pnpm:<version>可搭配pnpm runtime set node <ver> -g自行管理 Node。单仓只构建变更包的策略:pnpm --filter "...[origin/main]" build

版本固定通过 package.json 的packageManager: "pnpm@11.24.0"(Corepack 消费)完成;范围式固定用devEngines.packageManager(解析版本存于 lockfile),外部版本管理器(asdf/mise/Volta)场景可设pmOnFail: ignore

另外从仓库结构看,AIRI 还有 flake.nix 与 nix/pnpm-deps-hash.txt(配套 nix/update-pnpm-deps-hash.sh),推测用于 Nix 构建链中固定 pnpm 依赖树,与上文 frozen store / frozen lockfile 的复现思路一脉相承;由于未逐行核对该 Nix 实现,此处不作进一步断言。

8. 关键要点速查

  1. 配置三分:所有 pnpm 设置进pnpm-workspace.yaml(camelCase);.npmrc只管认证;package.json#pnpmnpm_config_*已废弃。
  2. Catalogs 是单仓版本治理主入口:AIRI 用 400+ 条默认 catalog + 2 个命名 catalog(vitestxsai)+catalogMode: prefer,实现“一处改版本、全仓生效、package.json 零冲突”。
  3. Overrides 与 npm: 别名做依赖替换axios → feaxios、多包@nolyfill/*替换、传递依赖路径定向覆盖('pkg>a': 'catalog:')。
  4. 补丁入库即复现patchedDependencies+patches/*.patch使第三方修复对所有人确定生效;v11 中补丁失败必抛错。
  5. v11 安全默认allowBuilds白名单(AIRI 30+ 条审批)、minimumReleaseAge: 4320、lockfile 完整性硬校验、blockExoticSubdeps
  6. CI 三件套pnpm ci/--frozen-lockfile、按 lockfile 哈希缓存 store、packageManager精确固定大版本。
  7. 存储即信任域:store、全局 virtual store、元数据缓存只在与受信环境间共享;硬链接不可用时回退packageImportMethod: copy

对照 .agents/skills/pnpm/SKILL.md 的三板块表格,本文已覆盖 Core(CLI/配置/工作区/Store)、Features(Catalogs/Overrides/Patches/供应链安全)与 Best Practices(CI/CD)中与 AIRI 实际配置强相关的部分;其余参考文档(Aliases、Hooks、Peer Dependencies、Config Dependencies、Migration、Performance 等)可按表格中的仓库相对路径继续深入。

【免费下载链接】airi💖🧸 Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-sama's altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询