Provision CI pnpm via pnpm/action-setup
经由 pnpm/action-setup 提供 CI 的 pnpm
Outside `landlock-run.yml`, each workflow that installed pnpm hand-provisioned it with `corepack enable`, and five of them further repeated a hand-rolled cache setup — `pnpm store path --silent >> $GITHUB_OUTPUT`, then `actions/cache@v4` keyed on `pnpm-lock.yaml`: `e2e.yml`, `docs-pages.yml`, `pi-ai-provider-e2e.yml`, `build-exe-for-python-sdk.yml`, and the node-compat, serial-linux, and benchmark jobs of `ci.yml`. T
English
Problem
Outside landlock-run.yml, each workflow that installed pnpm hand-provisioned it with corepack enable, and five of them further repeated a hand-rolled cache setup — pnpm store path --silent >> $GITHUB_OUTPUT, then actions/cache@v4 keyed on pnpm-lock.yaml: e2e.yml, docs-pages.yml, pi-ai-provider-e2e.yml, build-exe-for-python-sdk.yml, and the node-compat, serial-linux, and benchmark jobs of ci.yml. The maintained equivalent — pnpm/action-setup@v4 (reads packageManager from package.json) plus actions/setup-node with cache: pnpm — was already proven in-repo in landlock-run.yml, and corepack's removal from newer Node distributions made every corepack enable a known future break.
Decision
pnpm/action-setup@v4 is the only pnpm provisioning mechanism in CI: no workflow runs corepack enable. The root dev dependency on @yarnpkg/cli-dist separately supplies the modern Yarn CLI exercised by the generated-project e2e; package-manager coverage therefore does not inherit the runner image's Yarn Classic. Caching remains per-job policy on top of pnpm provisioning, in three deliberate shapes:
- Symmetric cache (restore and save):
actions/setup-nodewithcache: pnpm—e2e.yml,docs-pages.yml,pi-ai-provider-e2e.yml,build-exe-for-python-sdk.yml, the node-compat job ofci.yml, and the two benchmark jobs ofci-master.yml. The larger-runner benchmark keeps its store cache Linux-only through a conditionalcache:input; the consolidated benchmark caches on both platforms. - Restore-only caching (hand-rolled
actions/cachesteps): the three enterprise-runner PR jobs and the Wine-based required Windows job restore without saving, keeping cache compression/upload off their latency-sensitive paths — an asymmetrysetup-node's cache cannot express. Each configures a store outside the action's replaceable install directory and resolves that path. No master job produces these hosted caches, so these restores hit matching archived entries until they evict. The enterprise jobs skip restore during self-hosted failover because that VM's persistent store is already warm. - Cache-less or persistent (no store-cache action): the independent native Windows job, native serial-windows and serial-macos, plus
sandbox.ymlinstall from a cold or runner-local store. Extracting the many-file pnpm store costs more than a clean Windows install; the self-hosted standby and failover jobs instead reuse their VM's persistent pnpm store without transferring a hosted cache archive.
Alternatives considered
- Keep the hand-rolled steps. They worked, but they were drifting copies of setup boilerplate, and the corepack dependency was a known future break.
- Convert the enterprise jobs' caching to
cache: pnpm. Rejected: the restore-only asymmetry is a documented latency decision inci.yml's comments; erasing it to unify tooling inverts the priority. - Convert serial-linux's store cache. Rejected during implementation: the original proposal counted serial-linux among the symmetric setups, but its cache step is the producer half of the enterprise jobs' restore-only pairing — moving it to
setup-node's key format is the enterprise conversion by another route. - Stop at the cache-bearing workflows and leave the other
corepack enablesites. Rejected: provisioning and caching are separable concerns, and leaving corepack in the cache-less jobs kept the future break and two provisioning idioms for no benefit. - Rely on the runner image's Yarn. Rejected: the hosted image exposes Yarn 1.22 after Corepack is removed, while the generated-project e2e requires Yarn 2 or newer. A locked root dev dependency makes that coverage independent of runner image contents.
- A composite action wrapping action-setup + setup-node. Rejected for now: the remaining per-job variation (node-version matrices, per-platform conditional caching, the restore-only pairing) is deliberate policy, not boilerplate — a wrapper would grow mirroring inputs or flatten a real asymmetry, and the two-line pair is already near the floor.
Consequences
- The corepack dependency is gone from CI entirely; pnpm arrives via the pnpm team's official action everywhere, and the version pin stays single-sourced in
package.json'spackageManagerfield. - The generated-project e2e runs the root-pinned Yarn 4 CLI instead of inheriting or silently skipping the runner image's Yarn version.
- The cache-key format changed once for converted lanes; one cold run repopulated it, after which hit rates match the old steps. The built-in key spans platform, arch, and the lockfile hash but not the Node version, so the node-compat matrix legs share one store entry — safe, because the pnpm store is Node-version-independent.
setup-node's built-in pnpm cache restores by exact key only, with norestore-keysprefix fallback: apnpm-lock.yamlchange starts a converted lane from a cold store instead of seeding from the previous entry.pnpm/action-setupdeletes its install directory on every run and places the default store beneath the resultingPNPM_HOME. Linux jobs that need hosted cache restores or self-hosted persistence therefore setPNPM_CONFIG_STORE_DIRto$HOME/.local/share/pnpm/store, outside the action directory; the restore-only jobs resolve that stable path and exact key.
中文
问题
除 landlock-run.yml 外,每个安装 pnpm 的工作流都曾用 corepack enable 手工提供 pnpm,其中五个还各自重复着一套手写(hand-rolled)的缓存设置——pnpm store path --silent >> $GITHUB_OUTPUT、再加上以 pnpm-lock.yaml 为缓存键的 actions/cache@v4:e2e.yml、docs-pages.yml、pi-ai-provider-e2e.yml、build-exe-for-python-sdk.yml,以及 ci.yml 的 node-compat、serial-linux 与 benchmark 作业。与之等价、由官方维护的做法——pnpm/action-setup@v4(从 package.json 读取 packageManager)加带 cache: pnpm 的 actions/setup-node——当时已在仓库内的 landlock-run.yml 中得到验证,而 corepack 被从较新 Node 发行版中移除,使每一处 corepack enable 都成了已知的未来失效点。
决策
pnpm/action-setup@v4 是 CI 中提供 pnpm 的唯一机制:没有任何工作流运行 corepack enable。根目录的 @yarnpkg/cli-dist 开发依赖另行提供 generated-project e2e 所运行的现代 Yarn CLI(命令行界面);因此,用于包管理器覆盖率的 Yarn 不会沿用 runner 镜像里的 Yarn Classic。缓存仍是叠加在 pnpm 提供机制上的按作业策略,保留三种有意采用的形态:
- 对称缓存(既恢复也保存):带
cache: pnpm的actions/setup-node——e2e.yml、docs-pages.yml、pi-ai-provider-e2e.yml、build-exe-for-python-sdk.yml、ci.yml的 node-compat 作业,以及ci-master.yml的两个 benchmark 作业。larger-runner benchmark 通过条件化的cache:输入让 store 缓存仅限 Linux;consolidated benchmark 在两个平台上都启用缓存。 - 只恢复不上传(手写的
actions/cache步骤):企业 runner 上的三个 PR(Pull Request)作业和基于 Wine 的必需 Windows 作业只恢复不保存,把缓存压缩/上传挡在它们的延迟敏感路径之外——这种不对称是setup-node的缓存无法表达的。每个作业都在 action 可替换的安装目录之外配置 store,并解析该路径。没有任何 master 作业生产这些 hosted 缓存,这些恢复步骤只能命中仍有归档的旧条目,直至其被逐出;企业作业在自托管故障切换期间跳过恢复,因为该 VM 的持久 store 已经预热。 - 无缓存或持久化(不使用 store 缓存 action):独立的原生 Windows 作业、原生 serial-windows 和 serial-macos,以及
sandbox.yml均从冷 store 或 runner 本地 store 安装。解压含有大量文件的 pnpm store,成本高于在 Windows 上进行一次全新安装;自托管热备与故障切换作业则复用其 VM 的持久 pnpm store,不传输托管缓存归档。
曾考虑的替代方案
- 保留手写步骤。 它们能用,但那是会各自漂移的设置样板副本,而且对 corepack 的依赖是已知的未来失效点。
- 把企业作业的缓存也转换成
cache: pnpm。 否决:只恢复不上传的不对称是ci.yml注释中有记录的延迟决策;为统一工具而抹掉它,属于颠倒优先级。 - 转换 serial-linux 的 store 缓存。 实现期间否决:原提案曾把 serial-linux 计入对称设置,但其缓存步骤是企业作业只恢复不上传配对中的生产者一端——把它改成
setup-node的键格式,等于换条路径做了企业作业的转换。 - 只转换带缓存的工作流,留下其余出现
corepack enable的位置。 否决:提供 pnpm 与缓存是可分离的关注点,在无缓存作业里留下 corepack 只会保留未来失效点和两套并存的提供方式,毫无收益。 - 依赖 runner 镜像自带的 Yarn。 否决:Corepack 移除后,托管镜像提供的是 Yarn 1.22,而 generated-project e2e 要求 Yarn 2 或更高版本。锁定版本的根开发依赖让该项覆盖率不再受 runner 镜像内容影响。
- 用一个组合 action 包装 action-setup + setup-node。 暂不采纳:剩余的按作业差异(node 版本矩阵、按平台的条件缓存、只恢复不上传配对)是刻意采用的策略而非样板——包装层要么不得不增加与这些差异一一对应的输入,要么抹平一处真实的不对称,而两行的组合已接近下限。
后果
- corepack 依赖已从 CI 中彻底消失;pnpm 在所有工作流中都经由 pnpm 团队的官方 action 提供,版本锁定继续单一来源于
package.json的packageManager字段。 - generated-project e2e 运行根目录锁定的 Yarn 4 CLI,既不再沿用 runner 镜像中的 Yarn 版本,也不会因此悄然跳过。
- 已转换泳道的缓存键格式变更了一次;各跑一次冷运行重建缓存后,命中率与旧步骤持平。内建缓存键涵盖平台、架构与锁文件哈希,但不含 Node 版本,因此 node-compat 的各个矩阵任务共享同一条 store 缓存记录——这是安全的,因为 pnpm store 与 Node 版本无关。
setup-node内建的 pnpm 缓存只按精确键恢复,没有restore-keys前缀回退:pnpm-lock.yaml一旦变更,已转换泳道会从冷 store 起步,而不是利用上一条缓存记录预填充。pnpm/action-setup每次运行都会删除其安装目录,并把默认 store 放在由此产生的PNPM_HOME下。因此,需要 hosted 缓存恢复或自托管持久化的 Linux 作业会把PNPM_CONFIG_STORE_DIR设为$HOME/.local/share/pnpm/store,置于 action 目录之外;只恢复不上传的作业会解析这一稳定路径及精确键。