From cb3a1302eed107fcb9b783afaa624b8d1a148d76 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 26 Jul 2026 22:55:05 +0800 Subject: [PATCH 1/4] ci: adopt pnpm/action-setup for symmetric pnpm store caching Convert the hand-rolled corepack + store-path + actions/cache pnpm setups to pnpm/action-setup@v4 + setup-node cache: pnpm (the landlock-run.yml pattern) in e2e.yml, docs-pages.yml, pi-ai-provider-e2e.yml, build-exe-for-python-sdk.yml, and ci.yml's node-compat and benchmark jobs. The enterprise restore-only PR jobs and the Windows job keep their documented asymmetries; serial-linux keeps its hand-rolled store cache because it produces the exact key those restore-only jobs consume, and only swaps corepack for the action. Implements and moves the CI-caching Agent Note from PR #667 to implemented/, recording the serial-linux producer exception and the exact-key (no restore-keys prefix) behavior of setup-node's cache. --- ...n-setup-for-symmetric-ci-caching.i18n.yaml | 4 +- ...m-action-setup-for-symmetric-ci-caching.md | 32 ++++++++ ...ction-setup-for-symmetric-ci-caching.zh.md | 32 ++++++++ ...m-action-setup-for-symmetric-ci-caching.md | 31 -------- ...ction-setup-for-symmetric-ci-caching.zh.md | 31 -------- .../workflows/build-exe-for-python-sdk.yml | 20 ++--- .github/workflows/ci.yml | 76 ++++++------------- .github/workflows/docs-pages.yml | 17 +---- .github/workflows/e2e.yml | 17 +---- .github/workflows/pi-ai-provider-e2e.yml | 17 +---- 10 files changed, 102 insertions(+), 175 deletions(-) rename .agents/notes/{proposed => implemented}/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.i18n.yaml (59%) create mode 100644 .agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md create mode 100644 .agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md delete mode 100644 .agents/notes/proposed/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md delete mode 100644 .agents/notes/proposed/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md diff --git a/.agents/notes/proposed/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.i18n.yaml b/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.i18n.yaml similarity index 59% rename from .agents/notes/proposed/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.i18n.yaml rename to .agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.i18n.yaml index 0a31f7a857..23f0580c07 100644 --- a/.agents/notes/proposed/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.i18n.yaml +++ b/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md: 63e3f45ab2340ee2b732da286117e25be45bed08 -2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md: 2348e07d58f7f0ed39a1759cc30133c8e15dbc4a +2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md: 0cc0a09dfe1019d80b08599a245ce4bb044d81c5 +2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md: 477f8eed61579eb9c4f18701a39307daf098407d diff --git a/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md b/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md new file mode 100644 index 0000000000..0cc0a09dfe --- /dev/null +++ b/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md @@ -0,0 +1,32 @@ +# Agent Note: Use pnpm/action-setup for symmetric CI pnpm caching + +Status: implemented + +English | [中文](2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md) + +## Problem + +Five workflows repeated a hand-rolled three-step pnpm setup — `corepack enable`, `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` (~40–60 YAML lines total). 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 also insulates against corepack's removal from newer Node distributions. + +## Decision + +The symmetric-cache setups use `pnpm/action-setup@v4` followed by `actions/setup-node` with `cache: pnpm`, the `landlock-run.yml` pattern: `e2e.yml`, `docs-pages.yml`, `pi-ai-provider-e2e.yml`, `build-exe-for-python-sdk.yml`, and the node-compat and two benchmark jobs of `ci.yml`. The larger-runner benchmark keeps its store cache Linux-only through a conditional `cache:` input, mirroring the required Windows job's deliberate skip; the consolidated benchmark caches on both platforms as before. + +Explicitly NOT converted: + +- the three enterprise-runner PR jobs in `ci.yml` — they deliberately use `actions/cache/restore` only, keeping cache compression/upload off the paid latency-critical path, an asymmetry `setup-node`'s cache cannot express; +- the Windows job, which deliberately skips the store cache; +- the store-cache step of `ci.yml`'s serial-linux job — the job swaps `corepack enable` for `pnpm/action-setup@v4`, but its `pnpm store path` + `actions/cache@v4` steps stay hand-rolled because the master-push serial-linux run is the save side that populates the exact key and path the enterprise restore-only jobs consume; converting the producer to `setup-node`'s own key format would silently starve their restores. + +## Alternatives considered + +- **Keep the hand-rolled steps.** They work, but they are five drifting copies of setup boilerplate, and the corepack dependency is a known future break. +- **Convert everything including the enterprise jobs.** Rejected: the restore-only asymmetry is a documented latency decision in `ci.yml`'s comments; erasing it to unify tooling inverts the priority. +- **Convert serial-linux's store cache too.** Rejected during implementation: the 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. + +## Consequences + +- The corepack dependency is gone from every converted workflow; pnpm arrives via the pnpm team's official action, already trusted in-repo (`landlock-run.yml`). +- The cache-key format changed once; one cold run per converted lane repopulates 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 no `restore-keys` prefix fallback: a `pnpm-lock.yaml` change starts a converted lane from a cold store instead of seeding from the previous entry. +- About 75 net lines of workflow YAML removed; the enterprise-runner PR jobs and the Windows job are byte-identical, and serial-linux keeps producing the key they restore. diff --git a/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md b/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md new file mode 100644 index 0000000000..477f8eed61 --- /dev/null +++ b/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md @@ -0,0 +1,32 @@ +# Agent Note: 用 pnpm/action-setup 实现对称的 CI pnpm 缓存 + +Status: implemented + +[English](2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md) | 中文 + +## 问题 + +五个工作流曾重复着同一套手写(hand-rolled)的三步 pnpm 设置——`corepack enable`、`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 作业(合计约 40–60 行 YAML)。与之等价、由官方维护的做法——`pnpm/action-setup@v4`(从 package.json 读取 `packageManager`)加带 `cache: pnpm` 的 `actions/setup-node`——当时已在仓库内的 `landlock-run.yml` 中得到验证,同时还能隔绝 corepack 被从较新 Node 发行版中移除的影响。 + +## 决策 + +各对称缓存设置现采用 `pnpm/action-setup@v4` 后接带 `cache: pnpm` 的 `actions/setup-node`,即 `landlock-run.yml` 的模式:`e2e.yml`、`docs-pages.yml`、`pi-ai-provider-e2e.yml`、`build-exe-for-python-sdk.yml`,以及 `ci.yml` 的 node-compat 与两个 benchmark 作业。larger-runner benchmark 通过条件化的 `cache:` 输入让 store 缓存仅限 Linux,与必需 Windows 作业刻意跳过缓存的做法保持一致;consolidated benchmark 与之前一样在两个平台上都启用缓存。 + +以下明确未做转换: + +- `ci.yml` 中运行在企业 runner 上的三个 PR(Pull Request)作业——它们刻意只用 `actions/cache/restore`,把缓存压缩/上传挡在付费且延迟敏感的关键路径之外,这种不对称是 `setup-node` 的缓存无法表达的; +- Windows 作业,它刻意跳过 store 缓存; +- `ci.yml` 中 serial-linux 作业的 store 缓存步骤——该作业把 `corepack enable` 换成了 `pnpm/action-setup@v4`,但其 `pnpm store path` + `actions/cache@v4` 步骤仍保持手写,因为 master 推送触发的 serial-linux 运行正是写入侧,负责填充企业只恢复不上传(restore-only)作业所消费的那个精确缓存键与路径;把生产者改成 `setup-node` 自有的键格式,会悄然断供它们的恢复。 + +## 曾考虑的替代方案 + +- **保留手写步骤。** 它们能用,但那是五份会各自漂移的设置样板副本,而且对 corepack 的依赖是已知的未来失效点。 +- **连企业作业在内全部转换。** 否决:只恢复不上传的不对称是 `ci.yml` 注释中有记录的延迟决策;为统一工具而抹掉它,属于颠倒优先级。 +- **连 serial-linux 的 store 缓存也转换。** 实现期间否决:提案曾把 serial-linux 计入对称设置,但其缓存步骤是企业作业只恢复不上传配对中的生产者一半——把它改成 `setup-node` 的键格式,等于换条路径做了企业作业的转换。 + +## 后果 + +- 每个已转换的工作流都不再依赖 corepack;pnpm 经由 pnpm 团队的官方 action 提供,该 action 已在仓库内获得信任(`landlock-run.yml`)。 +- 缓存键格式变更了一次;每条已转换泳道各跑一次冷运行以重建缓存,此后命中率与旧步骤持平。内建缓存键涵盖平台、架构与锁文件哈希,但不含 Node 版本,因此 node-compat 矩阵的各条腿共享同一条 store 缓存记录——这是安全的,因为 pnpm store 与 Node 版本无关。 +- `setup-node` 内建的 pnpm 缓存只按精确键恢复,没有 `restore-keys` 前缀回退:`pnpm-lock.yaml` 一旦变更,已转换泳道会从冷 store 起步,而不是从上一条缓存记录播种。 +- 净删除约 75 行工作流 YAML;企业 runner 上的 PR 作业与 Windows 作业逐字节未动,serial-linux 继续生产它们所恢复的缓存键。 diff --git a/.agents/notes/proposed/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md b/.agents/notes/proposed/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md deleted file mode 100644 index 63e3f45ab2..0000000000 --- a/.agents/notes/proposed/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md +++ /dev/null @@ -1,31 +0,0 @@ -# Agent Note: Use pnpm/action-setup for symmetric CI pnpm caching - -Status: proposed - -English | [中文](2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md) - -## Problem - -Five workflows repeat a hand-rolled three-step pnpm setup — `corepack enable`, `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` (~40–60 YAML lines total). The maintained equivalent — `pnpm/action-setup@v4` (reads `packageManager` from package.json) plus `actions/setup-node` with `cache: pnpm` — is already proven in-repo in `landlock-run.yml`, and also insulates against corepack's removal from newer Node distributions. - -## Proposal - -Convert the symmetric-cache workflows to `pnpm/action-setup@v4` + `setup-node` `cache: pnpm`. Explicitly do NOT convert: - -- the three enterprise-runner PR jobs in `ci.yml` — they deliberately use `actions/cache/restore` only, keeping cache compression/upload off the paid latency-critical path, an asymmetry `setup-node`'s cache cannot express; -- the Windows job, which deliberately skips the store cache. - -## Alternatives considered - -- **Keep the hand-rolled steps.** They work, but they are five drifting copies of setup boilerplate, and the corepack dependency is a known future break. -- **Convert everything including the enterprise jobs.** Rejected: the restore-only asymmetry is a documented latency decision in `ci.yml`'s comments; erasing it to unify tooling inverts the priority. - -## Acceptance criteria - -- The five symmetric workflows set up pnpm via the actions; one cold run per lane repopulates the new cache-key format, after which cache hit rates match the old steps. -- The enterprise-runner PR jobs and the Windows job are untouched. - -## Risks - -- Cache-key format changes once (one cold run per lane). -- A third-party action in more workflows; it is already trusted in-repo (`landlock-run.yml`) and is the pnpm team's official action. diff --git a/.agents/notes/proposed/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md b/.agents/notes/proposed/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md deleted file mode 100644 index 2348e07d58..0000000000 --- a/.agents/notes/proposed/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md +++ /dev/null @@ -1,31 +0,0 @@ -# Agent Note: 用 pnpm/action-setup 实现对称的 CI pnpm 缓存 - -Status: proposed - -[English](2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md) | 中文 - -## 问题 - -五个工作流重复着同一套手写(hand-rolled)的三步 pnpm 设置——`corepack enable`、`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 作业(合计约 40–60 行 YAML)。与之等价、由官方维护的做法——`pnpm/action-setup@v4`(从 package.json 读取 `packageManager`)加带 `cache: pnpm` 的 `actions/setup-node`——已在仓库内的 `landlock-run.yml` 中得到验证,同时还能隔绝 corepack 被从较新 Node 发行版中移除的影响。 - -## 提案 - -将各对称缓存工作流改为 `pnpm/action-setup@v4` + `setup-node` `cache: pnpm`。以下明确不做转换: - -- `ci.yml` 中运行在企业 runner 上的三个 PR(Pull Request)作业——它们刻意只用 `actions/cache/restore`,把缓存压缩/上传挡在付费且延迟敏感的关键路径之外,这种不对称是 `setup-node` 的缓存无法表达的; -- Windows 作业,它刻意跳过 store 缓存。 - -## 曾考虑的替代方案 - -- **保留手写步骤。** 它们能用,但那是五份会各自漂移的设置样板副本,而且对 corepack 的依赖是已知的未来失效点。 -- **连企业作业在内全部转换。** 否决:只恢复不上传(restore-only)的不对称是 `ci.yml` 注释中有记录的延迟决策;为统一工具而抹掉它,属于颠倒优先级。 - -## 验收标准 - -- 五个对称工作流经由上述 action 完成 pnpm 设置;每条泳道各跑一次冷运行以重建新的缓存键格式,此后缓存命中率与旧步骤持平。 -- 企业 runner 上的 PR 作业与 Windows 作业保持原样不动。 - -## 风险 - -- 缓存键格式变更一次(每条泳道各一次冷运行)。 -- 更多工作流引入一个第三方 action;它已在仓库内获得信任(`landlock-run.yml`),且是 pnpm 团队的官方 action。 diff --git a/.github/workflows/build-exe-for-python-sdk.yml b/.github/workflows/build-exe-for-python-sdk.yml index 48c96e4d20..b119a548b8 100644 --- a/.github/workflows/build-exe-for-python-sdk.yml +++ b/.github/workflows/build-exe-for-python-sdk.yml @@ -124,9 +124,14 @@ jobs: steps: - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4 + + # setup-node's built-in pnpm store cache keys on platform AND arch, so + # the Linux architectures sharing runner.os stay on separate caches. - uses: actions/setup-node@v6 with: node-version: 24 + cache: pnpm - uses: actions/setup-python@v6 with: @@ -135,21 +140,6 @@ jobs: - name: Install Python build tooling run: python -m pip install uv==0.11.23 - - name: Enable corepack (pnpm) - run: corepack enable - - - name: Resolve pnpm store path - id: pnpm-store - run: echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT" - - # Linux architectures share runner.os, so the cache key includes arch. - - uses: actions/cache@v4 - with: - path: ${{ steps.pnpm-store.outputs.path }} - key: ${{ runner.os }}-${{ runner.arch }}-node-24-pnpm-${{ hashFiles('pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-${{ runner.arch }}-node-24-pnpm- - # Cache pkg's target Node binary; lockfile changes roll the # exact key while the restore prefix can seed its replacement. - uses: actions/cache@v4 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2eceefa114..272bfd4453 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -236,22 +236,12 @@ jobs: steps: - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v6 with: node-version: ${{ matrix.node }} - - - name: Enable corepack and resolve pnpm store path - id: pnpm-store - run: | - corepack enable - echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT" - - - uses: actions/cache@v4 - with: - path: ${{ steps.pnpm-store.outputs.path }} - key: ${{ runner.os }}-node-${{ matrix.node }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-node-${{ matrix.node }}-pnpm- + cache: pnpm - name: Install (immutable) run: pnpm install --frozen-lockfile @@ -324,17 +314,22 @@ jobs: steps: - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v6 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} - - name: Enable corepack and resolve pnpm store path + - name: Resolve pnpm store path id: pnpm-store - run: | - corepack enable - echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT" + run: echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT" # Master refreshes the caches that pull requests restore without saving. + # The store cache stays a hand-rolled actions/cache step rather than + # setup-node's `cache: pnpm`: the enterprise pull-request jobs above + # restore exactly this key and path, and setup-node's built-in cache + # uses its own key format — converting this producer would silently + # starve their documented restore-only optimization. - uses: actions/cache@v4 with: path: ${{ steps.pnpm-store.outputs.path }} @@ -498,9 +493,14 @@ jobs: steps: - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4 + + # The Windows lanes deliberately skip the store cache like the required + # windows job; an empty cache input disables setup-node's caching. - uses: actions/setup-node@v6 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} + cache: ${{ matrix.platform == 'linux' && 'pnpm' || '' }} - name: Report runner capacity run: >- @@ -508,22 +508,6 @@ jobs: console.log(JSON.stringify({ arch: process.arch, cpus: os.cpus().length, memoryGiB: Math.round(os.totalmem() / 2 ** 30) }))" - - name: Enable corepack (pnpm) - run: corepack enable - - - name: Resolve pnpm store path - if: matrix.platform == 'linux' - id: pnpm-store - run: echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT" - - - uses: actions/cache@v4 - if: matrix.platform == 'linux' - with: - path: ${{ steps.pnpm-store.outputs.path }} - key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm- - - name: Install (immutable) run: pnpm install --frozen-lockfile @@ -598,9 +582,14 @@ jobs: steps: - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4 + + # Unlike the larger-runner suite, both platforms cache the store here: + # the consolidated topology measures cache mechanics as workload. - uses: actions/setup-node@v6 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} + cache: pnpm - name: Report runner capacity run: >- @@ -608,27 +597,6 @@ jobs: console.log(JSON.stringify({ arch: process.arch, cpus: os.cpus().length, memoryGiB: Math.round(os.totalmem() / 2 ** 30) }))" - - name: Enable corepack (pnpm) - run: corepack enable - - - name: Resolve pnpm store path (Linux) - if: matrix.platform == 'linux' - id: pnpm-store-linux - run: echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT" - - - name: Resolve pnpm store path (Windows) - if: matrix.platform == 'windows' - id: pnpm-store-windows - shell: pwsh - run: '"path=$(pnpm store path --silent)" >> $env:GITHUB_OUTPUT' - - - uses: actions/cache@v4 - with: - path: ${{ steps.pnpm-store-linux.outputs.path || steps.pnpm-store-windows.outputs.path }} - key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm- - - uses: actions/cache@v4 if: matrix.platform == 'linux' with: diff --git a/.github/workflows/docs-pages.yml b/.github/workflows/docs-pages.yml index e1ad20997d..281e931c50 100644 --- a/.github/workflows/docs-pages.yml +++ b/.github/workflows/docs-pages.yml @@ -33,23 +33,12 @@ jobs: steps: - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v6 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} - - - name: Enable corepack (pnpm) - run: corepack enable - - - name: Resolve pnpm store path - id: pnpm-store - run: echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT" - - - uses: actions/cache@v4 - with: - path: ${{ steps.pnpm-store.outputs.path }} - key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm- + cache: pnpm - name: Install (immutable) run: pnpm install --frozen-lockfile diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 3d1bba6c17..c445034a8c 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -61,23 +61,12 @@ jobs: steps: - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v6 with: node-version: 24 - - - name: Enable corepack (pnpm) - run: corepack enable - - - name: Resolve pnpm store path - id: pnpm-store - run: echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT" - - - uses: actions/cache@v4 - with: - path: ${{ steps.pnpm-store.outputs.path }} - key: ${{ runner.os }}-node-24-pnpm-${{ hashFiles('pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-node-24-pnpm- + cache: pnpm - name: Install (immutable) run: pnpm install --frozen-lockfile diff --git a/.github/workflows/pi-ai-provider-e2e.yml b/.github/workflows/pi-ai-provider-e2e.yml index d198abf5b5..1306754d4c 100644 --- a/.github/workflows/pi-ai-provider-e2e.yml +++ b/.github/workflows/pi-ai-provider-e2e.yml @@ -27,23 +27,12 @@ jobs: steps: - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v6 with: node-version: 24 - - - name: Enable corepack (pnpm) - run: corepack enable - - - name: Resolve pnpm store path - id: pnpm-store - run: echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT" - - - uses: actions/cache@v4 - with: - path: ${{ steps.pnpm-store.outputs.path }} - key: ${{ runner.os }}-node-24-pnpm-${{ hashFiles('pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-node-24-pnpm- + cache: pnpm - name: Install (immutable) run: pnpm install --frozen-lockfile From 073c698c72940adaa6a04427232117c32edb8827 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Mon, 27 Jul 2026 13:15:09 +0800 Subject: [PATCH 2/4] =?UTF-8?q?ci:=20finish=20the=20corepack=20sweep=20?= =?UTF-8?q?=E2=80=94=20pnpm/action-setup=20everywhere?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review follow-up on the symmetric-cache conversion: provisioning and caching are separable concerns, so the remaining 8 corepack enable sites (the three enterprise restore-only PR jobs, the required Windows job, serial-macos/serial-windows, the cache-less ci.yml jobs, and sandbox.yml) now provision pnpm via pnpm/action-setup@v4 too — only the provisioning line moves; every deliberate cache shape (enterprise restore-only pairing, serial-linux producer, Windows cache-less) is byte-preserved. corepack is gone from CI entirely. Agent Note retitled to the decision's real shape (provisioning policy, with caching as per-job policy on top), EN+ZH, pair re-recorded; composite-action and stop-at-cache-workflows alternatives recorded. --- ...n-setup-for-symmetric-ci-caching.i18n.yaml | 4 +- ...m-action-setup-for-symmetric-ci-caching.md | 28 ++++++------- ...ction-setup-for-symmetric-ci-caching.zh.md | 28 ++++++------- .github/workflows/ci.yml | 41 +++++++++---------- .github/workflows/sandbox.yml | 5 +-- 5 files changed, 52 insertions(+), 54 deletions(-) diff --git a/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.i18n.yaml b/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.i18n.yaml index 23f0580c07..0b452fcf81 100644 --- a/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.i18n.yaml +++ b/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md: 0cc0a09dfe1019d80b08599a245ce4bb044d81c5 -2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md: 477f8eed61579eb9c4f18701a39307daf098407d +2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md: d734f6be65affb15f7e56c08b0b6d268002a44d8 +2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md: 77ea0aa1cc7443ca7cd9e89b652a9c908bab64e5 diff --git a/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md b/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md index 0cc0a09dfe..d734f6be65 100644 --- a/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md +++ b/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md @@ -1,4 +1,4 @@ -# Agent Note: Use pnpm/action-setup for symmetric CI pnpm caching +# Agent Note: Provision CI pnpm via pnpm/action-setup Status: implemented @@ -6,27 +6,27 @@ English | [中文](2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md) ## Problem -Five workflows repeated a hand-rolled three-step pnpm setup — `corepack enable`, `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` (~40–60 YAML lines total). 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 also insulates against corepack's removal from newer Node distributions. +Every workflow hand-provisioned pnpm 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` (~40–60 YAML lines of drifting copies). 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 -The symmetric-cache setups use `pnpm/action-setup@v4` followed by `actions/setup-node` with `cache: pnpm`, the `landlock-run.yml` pattern: `e2e.yml`, `docs-pages.yml`, `pi-ai-provider-e2e.yml`, `build-exe-for-python-sdk.yml`, and the node-compat and two benchmark jobs of `ci.yml`. The larger-runner benchmark keeps its store cache Linux-only through a conditional `cache:` input, mirroring the required Windows job's deliberate skip; the consolidated benchmark caches on both platforms as before. +`pnpm/action-setup@v4` is the only pnpm provisioning mechanism in CI: no workflow runs `corepack enable`. Caching remains per-job policy on top of it, in three deliberate shapes: -Explicitly NOT converted: - -- the three enterprise-runner PR jobs in `ci.yml` — they deliberately use `actions/cache/restore` only, keeping cache compression/upload off the paid latency-critical path, an asymmetry `setup-node`'s cache cannot express; -- the Windows job, which deliberately skips the store cache; -- the store-cache step of `ci.yml`'s serial-linux job — the job swaps `corepack enable` for `pnpm/action-setup@v4`, but its `pnpm store path` + `actions/cache@v4` steps stay hand-rolled because the master-push serial-linux run is the save side that populates the exact key and path the enterprise restore-only jobs consume; converting the producer to `setup-node`'s own key format would silently starve their restores. +- **Symmetric cache** (restore and save): `actions/setup-node` with `cache: pnpm` — `e2e.yml`, `docs-pages.yml`, `pi-ai-provider-e2e.yml`, `build-exe-for-python-sdk.yml`, and the node-compat and two benchmark jobs of `ci.yml`. The larger-runner benchmark keeps its store cache Linux-only through a conditional `cache:` input; the consolidated benchmark caches on both platforms. +- **Restore-only / producer pairing** (hand-rolled `actions/cache` steps, unchanged): the three enterprise-runner PR jobs restore without saving, keeping cache compression/upload off the paid latency-critical path — an asymmetry `setup-node`'s cache cannot express — and the master-push serial-linux job keeps its `pnpm store path` + `actions/cache@v4` save side, because it populates the exact key and path those restore-only jobs consume; converting the producer to `setup-node`'s key format would silently starve their restores. +- **Cache-less** (no store cache at all): the required Windows job and serial-windows (many-file store extraction is slower than a clean install there), serial-macos, sandbox.yml, and the coverage/consumers enterprise jobs that already restore via the shared enterprise key. ## Alternatives considered -- **Keep the hand-rolled steps.** They work, but they are five drifting copies of setup boilerplate, and the corepack dependency is a known future break. -- **Convert everything including the enterprise jobs.** Rejected: the restore-only asymmetry is a documented latency decision in `ci.yml`'s comments; erasing it to unify tooling inverts the priority. -- **Convert serial-linux's store cache too.** Rejected during implementation: the 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. +- **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 in `ci.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 enable` sites.** Rejected on review follow-up: 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. +- **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 every converted workflow; pnpm arrives via the pnpm team's official action, already trusted in-repo (`landlock-run.yml`). -- The cache-key format changed once; one cold run per converted lane repopulates 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. +- 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`'s `packageManager` field. +- 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 no `restore-keys` prefix fallback: a `pnpm-lock.yaml` change starts a converted lane from a cold store instead of seeding from the previous entry. -- About 75 net lines of workflow YAML removed; the enterprise-runner PR jobs and the Windows job are byte-identical, and serial-linux keeps producing the key they restore. +- About 75 net lines of workflow YAML removed. The enterprise-runner PR jobs' and Windows jobs' cache behavior is unchanged (only their provisioning line moved to the action), and serial-linux keeps producing the key the restore-only jobs consume. diff --git a/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md b/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md index 477f8eed61..77ea0aa1cc 100644 --- a/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md +++ b/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md @@ -1,4 +1,4 @@ -# Agent Note: 用 pnpm/action-setup 实现对称的 CI pnpm 缓存 +# Agent Note: 经由 pnpm/action-setup 提供 CI 的 pnpm Status: implemented @@ -6,27 +6,27 @@ Status: implemented ## 问题 -五个工作流曾重复着同一套手写(hand-rolled)的三步 pnpm 设置——`corepack enable`、`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 作业(合计约 40–60 行 YAML)。与之等价、由官方维护的做法——`pnpm/action-setup@v4`(从 package.json 读取 `packageManager`)加带 `cache: pnpm` 的 `actions/setup-node`——当时已在仓库内的 `landlock-run.yml` 中得到验证,同时还能隔绝 corepack 被从较新 Node 发行版中移除的影响。 +每个工作流都曾用 `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 作业(约 40–60 行各自漂移的 YAML 副本)。与之等价、由官方维护的做法——`pnpm/action-setup@v4`(从 package.json 读取 `packageManager`)加带 `cache: pnpm` 的 `actions/setup-node`——当时已在仓库内的 `landlock-run.yml` 中得到验证,而 corepack 被从较新 Node 发行版中移除,使每一处 `corepack enable` 都成了已知的未来失效点。 ## 决策 -各对称缓存设置现采用 `pnpm/action-setup@v4` 后接带 `cache: pnpm` 的 `actions/setup-node`,即 `landlock-run.yml` 的模式:`e2e.yml`、`docs-pages.yml`、`pi-ai-provider-e2e.yml`、`build-exe-for-python-sdk.yml`,以及 `ci.yml` 的 node-compat 与两个 benchmark 作业。larger-runner benchmark 通过条件化的 `cache:` 输入让 store 缓存仅限 Linux,与必需 Windows 作业刻意跳过缓存的做法保持一致;consolidated benchmark 与之前一样在两个平台上都启用缓存。 +`pnpm/action-setup@v4` 是 CI 中提供 pnpm 的唯一机制:没有任何工作流运行 `corepack enable`。缓存仍是叠加其上的按作业政策,保持三种刻意的形态: -以下明确未做转换: - -- `ci.yml` 中运行在企业 runner 上的三个 PR(Pull Request)作业——它们刻意只用 `actions/cache/restore`,把缓存压缩/上传挡在付费且延迟敏感的关键路径之外,这种不对称是 `setup-node` 的缓存无法表达的; -- Windows 作业,它刻意跳过 store 缓存; -- `ci.yml` 中 serial-linux 作业的 store 缓存步骤——该作业把 `corepack enable` 换成了 `pnpm/action-setup@v4`,但其 `pnpm store path` + `actions/cache@v4` 步骤仍保持手写,因为 master 推送触发的 serial-linux 运行正是写入侧,负责填充企业只恢复不上传(restore-only)作业所消费的那个精确缓存键与路径;把生产者改成 `setup-node` 自有的键格式,会悄然断供它们的恢复。 +- **对称缓存**(既恢复也保存):带 `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 与两个 benchmark 作业。larger-runner benchmark 通过条件化的 `cache:` 输入让 store 缓存仅限 Linux;consolidated benchmark 在两个平台上都启用缓存。 +- **只恢复不上传/生产者配对**(手写的 `actions/cache` 步骤,保持不变):企业 runner 上的三个 PR(Pull Request)作业只恢复不保存,把缓存压缩/上传挡在付费且延迟敏感的关键路径之外——这种不对称是 `setup-node` 的缓存无法表达的;master 推送触发的 serial-linux 作业保留其 `pnpm store path` + `actions/cache@v4` 写入侧,因为它负责填充那些只恢复不上传(restore-only)作业所消费的精确缓存键与路径;把生产者改成 `setup-node` 的键格式,会悄然断供它们的恢复。 +- **无缓存**(完全不设 store 缓存):必需的 Windows 作业与 serial-windows(在那里解压海量小文件的 store 缓存比干净安装更慢)、serial-macos、sandbox.yml,以及本就经共享企业键恢复的 coverage/consumers 企业作业。 ## 曾考虑的替代方案 -- **保留手写步骤。** 它们能用,但那是五份会各自漂移的设置样板副本,而且对 corepack 的依赖是已知的未来失效点。 -- **连企业作业在内全部转换。** 否决:只恢复不上传的不对称是 `ci.yml` 注释中有记录的延迟决策;为统一工具而抹掉它,属于颠倒优先级。 -- **连 serial-linux 的 store 缓存也转换。** 实现期间否决:提案曾把 serial-linux 计入对称设置,但其缓存步骤是企业作业只恢复不上传配对中的生产者一半——把它改成 `setup-node` 的键格式,等于换条路径做了企业作业的转换。 +- **保留手写步骤。** 它们能用,但那是会各自漂移的设置样板副本,而且对 corepack 的依赖是已知的未来失效点。 +- **把企业作业的缓存也转换成 `cache: pnpm`。** 否决:只恢复不上传的不对称是 `ci.yml` 注释中有记录的延迟决策;为统一工具而抹掉它,属于颠倒优先级。 +- **转换 serial-linux 的 store 缓存。** 实现期间否决:原提案曾把 serial-linux 计入对称设置,但其缓存步骤是企业作业只恢复不上传配对中的生产者一半——把它改成 `setup-node` 的键格式,等于换条路径做了企业作业的转换。 +- **只转换带缓存的工作流,留下其余 `corepack enable` 站点。** 评审跟进时否决:提供 pnpm 与缓存是可分离的关注点,在无缓存作业里留下 corepack 只会保留未来失效点和两套并存的提供方式,毫无收益。 +- **用一个组合 action 包装 action-setup + setup-node。** 暂不采纳:剩余的按作业差异(node 版本矩阵、按平台的条件缓存、只恢复不上传配对)是刻意的政策而非样板——包装层要么长出镜像这些差异的输入,要么抹平一处真实的不对称,而两行的组合已接近下限。 ## 后果 -- 每个已转换的工作流都不再依赖 corepack;pnpm 经由 pnpm 团队的官方 action 提供,该 action 已在仓库内获得信任(`landlock-run.yml`)。 -- 缓存键格式变更了一次;每条已转换泳道各跑一次冷运行以重建缓存,此后命中率与旧步骤持平。内建缓存键涵盖平台、架构与锁文件哈希,但不含 Node 版本,因此 node-compat 矩阵的各条腿共享同一条 store 缓存记录——这是安全的,因为 pnpm store 与 Node 版本无关。 +- corepack 依赖已从 CI 中彻底消失;pnpm 在所有工作流中都经由 pnpm 团队的官方 action 提供,版本锁定继续单一来源于 `package.json` 的 `packageManager` 字段。 +- 已转换泳道的缓存键格式变更了一次;各跑一次冷运行重建缓存后,命中率与旧步骤持平。内建缓存键涵盖平台、架构与锁文件哈希,但不含 Node 版本,因此 node-compat 矩阵的各条腿共享同一条 store 缓存记录——这是安全的,因为 pnpm store 与 Node 版本无关。 - `setup-node` 内建的 pnpm 缓存只按精确键恢复,没有 `restore-keys` 前缀回退:`pnpm-lock.yaml` 一旦变更,已转换泳道会从冷 store 起步,而不是从上一条缓存记录播种。 -- 净删除约 75 行工作流 YAML;企业 runner 上的 PR 作业与 Windows 作业逐字节未动,serial-linux 继续生产它们所恢复的缓存键。 +- 净删除约 75 行工作流 YAML。企业 runner 上的 PR 作业与 Windows 作业的缓存行为未变(只有提供 pnpm 的那一行改用了 action),serial-linux 继续生产只恢复不上传作业所消费的缓存键。 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ee12e9768..6813b75c56 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,14 +52,14 @@ jobs: restore-keys: | ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm- + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v6 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} - - name: Enable corepack and install dependencies - run: | - corepack enable - pnpm install --frozen-lockfile + - name: Install (immutable) + run: pnpm install --frozen-lockfile - name: Run static gates env: @@ -98,13 +98,14 @@ jobs: restore-keys: | ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm- + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v6 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} - - name: Enable corepack, install dependencies, and prepare bubblewrap + - name: Install dependencies and prepare bubblewrap run: | - corepack enable pnpm install --frozen-lockfile & install_pid=$! bash scripts/prepare-ci-bubblewrap.sh & @@ -158,13 +159,14 @@ jobs: restore-keys: | ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full- + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v6 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} - - name: Enable corepack, install dependencies, and prepare bubblewrap + - name: Install dependencies and prepare bubblewrap run: | - corepack enable pnpm install --frozen-lockfile & install_pid=$! bash scripts/prepare-ci-bubblewrap.sh & @@ -291,17 +293,17 @@ jobs: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /t REG_DWORD /f /v "AllowDevelopmentWithoutDevLicense" /d "1" + - uses: pnpm/action-setup@v4 + + # Extracting the many-file pnpm store cache is slower than a clean install, + # and saving it adds more latency after gates, so this job runs cache-less. - uses: actions/setup-node@v6 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} - # Extracting the many-file pnpm store cache is slower than a clean install, - # and saving it adds more latency after gates. - - name: Enable corepack and install (immutable) + - name: Install (immutable) shell: pwsh - run: | - corepack enable - pnpm install --frozen-lockfile + run: pnpm install --frozen-lockfile - name: Run blocking and observational Windows gates concurrently shell: pwsh @@ -374,13 +376,12 @@ jobs: steps: - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v6 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} - - name: Enable corepack (pnpm) - run: corepack enable - - name: Install (immutable) run: pnpm install --frozen-lockfile @@ -406,14 +407,12 @@ jobs: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /t REG_DWORD /f /v "AllowDevelopmentWithoutDevLicense" /d "1" + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v6 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} - - name: Enable corepack (pnpm) - shell: pwsh - run: corepack enable - # Master refreshes the small cache that pull requests restore without # putting package-store extraction back on the Windows critical path. - uses: actions/cache@v4 diff --git a/.github/workflows/sandbox.yml b/.github/workflows/sandbox.yml index 51dfe06f1c..0d14914bfd 100644 --- a/.github/workflows/sandbox.yml +++ b/.github/workflows/sandbox.yml @@ -53,13 +53,12 @@ jobs: steps: - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v6 with: node-version: 24 - - name: Enable corepack (pnpm) - run: corepack enable - - name: Install (immutable) run: pnpm install --frozen-lockfile From 1665ab9884bee46c144c6aa5fbd9e115e497e08a Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Mon, 27 Jul 2026 18:39:20 +0800 Subject: [PATCH 3/4] ci: preserve stores across pnpm action setup Keep restore-only and self-hosted stores outside pnpm/action-setup's replaceable install directory, resolving the configured path for every paired cache. Pin Yarn 4 for the generated-project e2e so runner images without Corepack do not fall back to Yarn Classic, and update the bilingual decision record. --- ...n-setup-for-symmetric-ci-caching.i18n.yaml | 6 +- ...m-action-setup-for-symmetric-ci-caching.md | 12 ++- ...ction-setup-for-symmetric-ci-caching.zh.md | 12 ++- .github/workflows/ci.yml | 99 +++++++++++++------ knip.json | 1 + package.json | 3 +- pnpm-lock.yaml | 10 ++ 7 files changed, 99 insertions(+), 44 deletions(-) diff --git a/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.i18n.yaml b/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.i18n.yaml index 0b452fcf81..7ad4d7bee7 100644 --- a/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.i18n.yaml +++ b/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write -2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md: d734f6be65affb15f7e56c08b0b6d268002a44d8 -2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md: 77ea0aa1cc7443ca7cd9e89b652a9c908bab64e5 +# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md +2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md: 006360cedfd4d1e2c2b67ede98062a375e316f47 +2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md: d511e7a2b89788fe8addd2cc47634742c7a87fe4 diff --git a/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md b/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md index d734f6be65..006360cedf 100644 --- a/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md +++ b/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md @@ -6,15 +6,15 @@ English | [中文](2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md) ## Problem -Every workflow hand-provisioned pnpm 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` (~40–60 YAML lines of drifting copies). 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. +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`. Caching remains per-job policy on top of it, in three deliberate shapes: +`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-node` with `cache: pnpm` — `e2e.yml`, `docs-pages.yml`, `pi-ai-provider-e2e.yml`, `build-exe-for-python-sdk.yml`, and the node-compat and two benchmark jobs of `ci.yml`. The larger-runner benchmark keeps its store cache Linux-only through a conditional `cache:` input; the consolidated benchmark caches on both platforms. -- **Restore-only / producer pairing** (hand-rolled `actions/cache` steps, unchanged): the three enterprise-runner PR jobs restore without saving, keeping cache compression/upload off the paid latency-critical path — an asymmetry `setup-node`'s cache cannot express — and the master-push serial-linux job keeps its `pnpm store path` + `actions/cache@v4` save side, because it populates the exact key and path those restore-only jobs consume; converting the producer to `setup-node`'s key format would silently starve their restores. -- **Cache-less** (no store cache at all): the required Windows job and serial-windows (many-file store extraction is slower than a clean install there), serial-macos, sandbox.yml, and the coverage/consumers enterprise jobs that already restore via the shared enterprise key. +- **Restore-only / producer pairing** (hand-rolled `actions/cache` steps): the three enterprise-runner PR jobs and the Wine-based pull-request Windows job restore without saving, keeping cache compression/upload off their latency-sensitive paths — an asymmetry `setup-node`'s cache cannot express. Each configures a store outside the action's replaceable install directory and resolves that path, matching the master-push serial-linux producer's path and exact key; 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): native serial-windows and serial-macos plus `sandbox.yml` install from a cold or runner-local store. The self-hosted standby and failover jobs reuse their VM's persistent pnpm store without transferring a hosted cache archive. ## Alternatives considered @@ -22,11 +22,13 @@ Every workflow hand-provisioned pnpm with `corepack enable`, and five of them fu - **Convert the enterprise jobs' caching to `cache: pnpm`.** Rejected: the restore-only asymmetry is a documented latency decision in `ci.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 enable` sites.** Rejected on review follow-up: 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`'s `packageManager` field. +- 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 no `restore-keys` prefix fallback: a `pnpm-lock.yaml` change starts a converted lane from a cold store instead of seeding from the previous entry. -- About 75 net lines of workflow YAML removed. The enterprise-runner PR jobs' and Windows jobs' cache behavior is unchanged (only their provisioning line moved to the action), and serial-linux keeps producing the key the restore-only jobs consume. +- `pnpm/action-setup` deletes its install directory on every run and places the default store beneath the resulting `PNPM_HOME`. Linux jobs that need cache pairing or self-hosted persistence therefore set `PNPM_CONFIG_STORE_DIR` to `$HOME/.local/share/pnpm/store`, outside the action directory; the restore-only jobs and serial-linux resolve and share that stable path and exact key. diff --git a/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md b/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md index 77ea0aa1cc..d511e7a2b8 100644 --- a/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md +++ b/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md @@ -6,15 +6,15 @@ Status: implemented ## 问题 -每个工作流都曾用 `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 作业(约 40–60 行各自漂移的 YAML 副本)。与之等价、由官方维护的做法——`pnpm/action-setup@v4`(从 package.json 读取 `packageManager`)加带 `cache: pnpm` 的 `actions/setup-node`——当时已在仓库内的 `landlock-run.yml` 中得到验证,而 corepack 被从较新 Node 发行版中移除,使每一处 `corepack enable` 都成了已知的未来失效点。 +除 `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`。缓存仍是叠加其上的按作业政策,保持三种刻意的形态: +`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 与两个 benchmark 作业。larger-runner benchmark 通过条件化的 `cache:` 输入让 store 缓存仅限 Linux;consolidated benchmark 在两个平台上都启用缓存。 -- **只恢复不上传/生产者配对**(手写的 `actions/cache` 步骤,保持不变):企业 runner 上的三个 PR(Pull Request)作业只恢复不保存,把缓存压缩/上传挡在付费且延迟敏感的关键路径之外——这种不对称是 `setup-node` 的缓存无法表达的;master 推送触发的 serial-linux 作业保留其 `pnpm store path` + `actions/cache@v4` 写入侧,因为它负责填充那些只恢复不上传(restore-only)作业所消费的精确缓存键与路径;把生产者改成 `setup-node` 的键格式,会悄然断供它们的恢复。 -- **无缓存**(完全不设 store 缓存):必需的 Windows 作业与 serial-windows(在那里解压海量小文件的 store 缓存比干净安装更慢)、serial-macos、sandbox.yml,以及本就经共享企业键恢复的 coverage/consumers 企业作业。 +- **只恢复不上传/生产者配对**(手写的 `actions/cache` 步骤):企业 runner 上的三个 PR(Pull Request)作业与基于 Wine 的拉取请求 Windows 作业只恢复不保存,把缓存压缩/上传挡在它们的延迟敏感路径之外——这种不对称是 `setup-node` 的缓存无法表达的。每个作业都在 action 可替换的安装目录之外配置 store,并解析该路径,从而与 master 推送触发的 serial-linux 生产者所用的路径和精确键匹配;企业作业在自托管故障切换期间跳过恢复,因为该 VM 的持久 store 已能直接提供热安装。 +- **无缓存或持久化**(不使用 store 缓存 action):原生 serial-windows 和 serial-macos 加上 `sandbox.yml` 从冷 store 或 runner 本地 store 安装。自托管热备与故障切换作业复用其 VM 的持久 pnpm store,不传输托管缓存归档。 ## 曾考虑的替代方案 @@ -22,11 +22,13 @@ Status: implemented - **把企业作业的缓存也转换成 `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 起步,而不是从上一条缓存记录播种。 -- 净删除约 75 行工作流 YAML。企业 runner 上的 PR 作业与 Windows 作业的缓存行为未变(只有提供 pnpm 的那一行改用了 action),serial-linux 继续生产只恢复不上传作业所消费的缓存键。 +- `pnpm/action-setup` 每次运行都会删除其安装目录,并把默认 store 放在由此产生的 `PNPM_HOME` 下。因此,需要缓存配对或自托管持久化的 Linux 作业会把 `PNPM_CONFIG_STORE_DIR` 设为 `$HOME/.local/share/pnpm/store`,置于 action 目录之外;只恢复不上传的作业与 serial-linux 会解析并共享这一稳定路径及精确键。 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 349f4ef88c..615aaf8fc1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,23 +58,31 @@ jobs: fetch-depth: 0 persist-credentials: false + - uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v6 + with: + node-version: ${{ env.PRIMARY_NODE_VERSION }} + + - name: Configure pnpm store path + id: pnpm-store + run: | + store_root="$HOME/.local/share/pnpm/store" + echo "PNPM_CONFIG_STORE_DIR=$store_root" >> "$GITHUB_ENV" + store_path=$(PNPM_CONFIG_STORE_DIR="$store_root" pnpm store path --silent) + echo "path=$store_path" >> "$GITHUB_OUTPUT" + # Pull requests consume the default-branch cache but do not put cache # compression and upload on the paid latency-critical path. Skipped # under failover — see the coverage lane's identical rationale. - uses: actions/cache/restore@v4 if: vars.DSH_CI_FAILOVER != 'selfhosted' || github.event.pull_request.user.login == 'dependabot[bot]' with: - path: /home/runner/.local/share/pnpm/store/v11 + path: ${{ steps.pnpm-store.outputs.path }} key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm- - - uses: pnpm/action-setup@v4 - - - uses: actions/setup-node@v6 - with: - node-version: ${{ env.PRIMARY_NODE_VERSION }} - - name: Install (immutable) run: pnpm install --frozen-lockfile @@ -117,23 +125,31 @@ jobs: with: persist-credentials: false - # Skipped under failover: the self-hosted VM's persistent pnpm store - # serves warm installs directly, and this hosted-path restore would - # spend ~52 s pulling ~180 MB into a path pnpm never reads there. - - uses: actions/cache/restore@v4 - if: vars.DSH_CI_FAILOVER != 'selfhosted' || github.event.pull_request.user.login == 'dependabot[bot]' - with: - path: /home/runner/.local/share/pnpm/store/v11 - key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm- - - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v6 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} + - name: Configure pnpm store path + id: pnpm-store + run: | + store_root="$HOME/.local/share/pnpm/store" + echo "PNPM_CONFIG_STORE_DIR=$store_root" >> "$GITHUB_ENV" + store_path=$(PNPM_CONFIG_STORE_DIR="$store_root" pnpm store path --silent) + echo "path=$store_path" >> "$GITHUB_OUTPUT" + + # Skipped under failover: the self-hosted VM's persistent pnpm store + # already serves warm installs, while restoring the hosted archive + # would spend ~52 s pulling ~180 MB into that populated store. + - uses: actions/cache/restore@v4 + if: vars.DSH_CI_FAILOVER != 'selfhosted' || github.event.pull_request.user.login == 'dependabot[bot]' + with: + path: ${{ steps.pnpm-store.outputs.path }} + key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm- + - name: Install dependencies and prepare bubblewrap run: | pnpm install --frozen-lockfile & @@ -180,15 +196,6 @@ jobs: - name: Restore built tree run: tar -xzf "$RUNNER_TEMP/node-24-built-tree.tar.gz" - # Skipped under failover — see the coverage lane's identical rationale. - - uses: actions/cache/restore@v4 - if: vars.DSH_CI_FAILOVER != 'selfhosted' || github.event.pull_request.user.login == 'dependabot[bot]' - with: - path: /home/runner/.local/share/pnpm/store/v11 - key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm- - - uses: actions/cache/restore@v4 with: path: .cache/eslint @@ -202,6 +209,23 @@ jobs: with: node-version: ${{ env.PRIMARY_NODE_VERSION }} + - name: Configure pnpm store path + id: pnpm-store + run: | + store_root="$HOME/.local/share/pnpm/store" + echo "PNPM_CONFIG_STORE_DIR=$store_root" >> "$GITHUB_ENV" + store_path=$(PNPM_CONFIG_STORE_DIR="$store_root" pnpm store path --silent) + echo "path=$store_path" >> "$GITHUB_OUTPUT" + + # Skipped under failover — see the coverage lane's identical rationale. + - uses: actions/cache/restore@v4 + if: vars.DSH_CI_FAILOVER != 'selfhosted' || github.event.pull_request.user.login == 'dependabot[bot]' + with: + path: ${{ steps.pnpm-store.outputs.path }} + key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm- + - name: Install dependencies and prepare bubblewrap run: | pnpm install --frozen-lockfile & @@ -340,9 +364,17 @@ jobs: with: node-version: ${{ env.PRIMARY_NODE_VERSION }} + - name: Configure pnpm store path + id: pnpm-store + run: | + store_root="$HOME/.local/share/pnpm/store" + echo "PNPM_CONFIG_STORE_DIR=$store_root" >> "$GITHUB_ENV" + store_path=$(PNPM_CONFIG_STORE_DIR="$store_root" pnpm store path --silent) + echo "path=$store_path" >> "$GITHUB_OUTPUT" + - uses: actions/cache/restore@v4 with: - path: /home/runner/.local/share/pnpm/store/v11 + path: ${{ steps.pnpm-store.outputs.path }} key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm- @@ -548,9 +580,13 @@ jobs: with: node-version: ${{ env.PRIMARY_NODE_VERSION }} - - name: Resolve pnpm store path + - name: Configure pnpm store path id: pnpm-store - run: echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT" + run: | + store_root="$HOME/.local/share/pnpm/store" + echo "PNPM_CONFIG_STORE_DIR=$store_root" >> "$GITHUB_ENV" + store_path=$(PNPM_CONFIG_STORE_DIR="$store_root" pnpm store path --silent) + echo "path=$store_path" >> "$GITHUB_OUTPUT" # Master refreshes the caches that pull requests restore without saving. # The store cache stays a hand-rolled actions/cache step rather than @@ -621,6 +657,9 @@ jobs: with: node-version: ${{ env.PRIMARY_NODE_VERSION }} + - name: Configure persistent pnpm store + run: echo "PNPM_CONFIG_STORE_DIR=$HOME/.local/share/pnpm/store" >> "$GITHUB_ENV" + - name: Install (immutable) run: pnpm install --frozen-lockfile diff --git a/knip.json b/knip.json index 110abb3a2b..7ab7d47e5f 100644 --- a/knip.json +++ b/knip.json @@ -13,6 +13,7 @@ "python/sdk-runtime" ], "ignoreDependencies": [ + "@yarnpkg/cli-dist", "lightningcss" ], "workspaces": { diff --git a/package.json b/package.json index 3aa3d6998a..27f192466c 100644 --- a/package.json +++ b/package.json @@ -113,9 +113,10 @@ "@types/mdast": "^4.0.4", "@types/node": "^22.20.0", "@vitest/coverage-v8": "^4.1.8", + "@yarnpkg/cli-dist": "4.17.1", "eslint": "^10.4.1", - "execa": "^10.0.0", "eslint-plugin-sonarjs": "^4.1.0", + "execa": "^10.0.0", "fast-check": "^4.8.0", "js-yaml": "^4.2.0", "jscpd": "^5.0.12", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f82de24032..a24a701f61 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: '@vitest/coverage-v8': specifier: ^4.1.8 version: 4.1.8(vitest@4.1.8) + '@yarnpkg/cli-dist': + specifier: 4.17.1 + version: 4.17.1 eslint: specifier: ^10.4.1 version: 10.5.0(jiti@2.7.0) @@ -7468,6 +7471,11 @@ packages: '@xterm/headless@5.5.0': resolution: {integrity: sha512-5xXB7kdQlFBP82ViMJTwwEc3gKCLGKR/eoxQm4zge7GPBl86tCdI0IdPJjoKd8mUSFXz5V7i/25sfsEkP4j46g==} + '@yarnpkg/cli-dist@4.17.1': + resolution: {integrity: sha512-2tiSQuJNl/L3QwTdrq6lKWDpkcnp9MGvCT/rIldHcbu3SWfnLdmehvt3eulX1hT7FFt1Gjfq3CesF+kvhFip6g==} + engines: {node: '>=18.12.0'} + hasBin: true + accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} @@ -12364,6 +12372,8 @@ snapshots: '@xterm/headless@5.5.0': {} + '@yarnpkg/cli-dist@4.17.1': {} + accepts@2.0.0: dependencies: mime-types: 3.0.2 From 961bde64737c7030c7a8351260e7cc589f342f37 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Mon, 27 Jul 2026 18:48:11 +0800 Subject: [PATCH 4/4] test(sdk): keep fresh Yarn installs mutable in CI The live-link E2E intentionally creates a project without a lockfile. Prevent ambient CI=true from making Yarn 4's first install immutable, while preserving the CI environment for every other behavior under test. --- packages/sdk/create-sdk/tests/link-workspace.e2e.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/sdk/create-sdk/tests/link-workspace.e2e.ts b/packages/sdk/create-sdk/tests/link-workspace.e2e.ts index 0fefefdffa..7d1944614d 100644 --- a/packages/sdk/create-sdk/tests/link-workspace.e2e.ts +++ b/packages/sdk/create-sdk/tests/link-workspace.e2e.ts @@ -90,6 +90,8 @@ describe.skipIf(!existsSync(builtScripts))('live-linked generated projects', () XDG_DATA_HOME: join(cacheRoot, 'data'), npm_config_cache: join(cacheRoot, 'npm'), ...pnpmStore === undefined ? {} : { pnpm_config_store_dir: pnpmStore }, + // A generated project has no lockfile yet; ambient CI must not make its first Yarn install immutable. + ...name === 'yarn' ? { YARN_ENABLE_IMMUTABLE_INSTALLS: 'false' } : {}, } await execFileAsync(name, manager.installCommand(), { cwd: root,