Files
orca/docs/reference/pnpm-install-policy.md
Neil e86cba888b build: reduce native dependency installs to the host platform (#20420)
* Reduce native dependency installs to the host platform

* Remove install policy documentation

* Guard cross-arch packaging and scope release installs to the runner

electron-builder only logs a warning for a missing extraResources source,
so a host-only install silently shipped a foreign-arch slice without its
natives — `pnpm build:mac` on Apple Silicon produced an x64 DMG with no
sherpa-onnx-darwin-x64 and no @parcel/watcher-darwin-x64. The previous
beforePack hook covered only win32.

- Add assertPackagedNativeVariantsInstalled, an arch-aware check over the
  target's sherpa-onnx, @parcel/watcher, and (on Windows) node-gyp addons.
  beforePack now runs it for every platform, with remedies split: another
  architecture comes from install:release, the os:win32 addons need a
  Windows host.
- Drop --os from the release installs. Every packaging job already runs on
  a runner whose OS matches its target, so only the macOS lanes need extra
  breadth, and only on CPU for their x64+arm64 config. Windows and Linux
  packaging return to a plain host-only install.
- Add --frozen-lockfile to install:release so a bare run cannot rewrite
  the lockfile.
- Restore the install policy reference doc and the CONTRIBUTING note, plus
  the rationale comments dropped from the runtime contract test.
- Gate the packaging-closure assertions on whether the Windows addons are
  installed rather than on the host OS, so a cross-arch install exercises
  them off Windows too.
- Make the workflow contract test read `run:` steps as well as retry-action
  commands, and enforce host-only scoping on the non-macOS packaging lanes.
- Remove the unreferenced install measurement script; its numbers live in
  the policy doc.

* Track the install policy doc and index it from AGENTS.md

docs/** is ignored behind a per-file allow-list, so the new reference doc
was only committed via git add -f and future edits would be skipped. Add
it to the allow-list and give it an AGENTS.md entry like every other
tracked reference doc, so the host-only install rule is discoverable
before someone packages a second architecture.

* Route Windows-lane removals through the retrying helper

Adding these four specs to the PR Windows lane pulled them into the
windows-lane-tree-removal-boundary ratchet, which failed on 20 raw
recursive removals. On Windows a bare rmSync races a handle the OS has
not released, throwing EPERM after the assertions already passed and
reporting a green test as a lane failure.

* Adapt the packaging guard to the vendored Windows registry addon

main vendored windows-native-registry as the workspace package
@orca/windows-registry (#20438). A workspace link resolves on every
host, so including it in the installed-Windows-addons checks proved
nothing. @vscode/windows-process-tree is the only os: win32 npm addon
left, so it alone decides whether the win32 resource plan resolves.
2026-09-12 21:25:03 -07:00

5.2 KiB

Native dependency install policy

Ordinary pnpm install installs optional native dependencies for the current OS and CPU only. This applies to local development and root-project CI jobs, including jobs using .github/actions/install-node-dependencies. Mobile and cloud projects with their own workspace configuration are separate.

The one cross-target build in the repo is macOS: pnpm build:mac and the four macOS packaging workflows produce both x64 and arm64 artifacts from an arm64 runner. Before packaging for another architecture, widen the CPU set:

pnpm install:release

This runs pnpm install --frozen-lockfile --cpu=current,x64,arm64. It never widens the OS set: every packaging job runs on a runner whose OS matches its target, so cross-OS installs are never needed. The macOS workflows pass --cpu=current,x64,arm64 directly; Windows and Linux packaging jobs use a plain host-only pnpm install --frozen-lockfile. Keeping current in the list preserves the host's build tools alongside the target resources. An install for another target does not itself cross-compile native addons.

Packaging guard

electron-builder only logs a warning for a missing extraResources source and continues, so without a check a foreign-architecture slice would ship silently broken. beforePack in config/electron-builder.config.cjs therefore calls assertPackagedNativeVariantsInstalled in config/packaged-runtime-node-modules.cjs, which fails the build when the target platform/architecture's native variants are not installed: sherpa-onnx-*, @parcel/watcher-*, and on Windows the node-gyp addon @vscode/windows-process-tree. The error names every missing package and gives the remedy that fits: another architecture's variants come from pnpm install:release, the Windows addon does not (see below).

Windows packaging requires a Windows host. @vscode/windows-process-tree is an os: win32 npm addon, so it is installed only where that matches; @orca/windows-registry is a workspace package that links on every host, but its native binary is still compiled only on Windows. Both are compiled only by the Windows-only rebuild in config/scripts/rebuild-native-deps.mjs (allowBuilds in pnpm-workspace.yaml keeps pnpm itself from running node-gyp for them). The guard checks @vscode/windows-process-tree alone because the workspace link is present everywhere and proves nothing. pnpm install:release does not help on macOS or Linux because it does not widen the OS set.

Tests that inspect installed Windows addons and their packaging closure run on Windows, where those dependencies are required. The PR Windows lane explicitly includes them. Loading the packaging config tolerates absent Windows addons; only beforePack rejects Windows packaging until they are installed. Patch-source assertions, fixture tests, and the isolated real patch-install test continue to run on other hosts.

Existing checkouts

A narrowing incremental install can leave previously installed variants behind. Stop development processes using this checkout, remove this checkout's node_modules, then run pnpm install --frozen-lockfile to obtain a fresh host install. Do not use --force: pnpm 12 documents that it installs optional packages even when their OS/CPU/libc do not match. The shared pnpm download store is separate; this change does not clear it.

Measurement: macOS arm64, pnpm 12.0.0

Measured 2026-09-12 with the same package manifest, lockfile, patch files, and existing download store in two fresh install directories, both with --frozen-lockfile --ignore-scripts --offline. One used the earlier broad policy (--os=current,darwin,linux,win32 --cpu=current,x64,arm64); the other used --os=current --cpu=current. Numbers are logical file bytes in the virtual store, not unique disk usage: pnpm hardlinks and APFS clones may share storage.

Measure Broad install Host install Reduction
Package directories 1,296 1,206 90
Regular files 53,972 52,915 1,057
Logical package bytes 2,485,153,773 1,159,396,649 1,325,757,124 (53.3%)
Logical package GiB 2.31 1.08 1.23
Install wall time, single warm-cache trial 11.98 s 11.25 s 0.73 s
Native family Broad MiB Host MiB
Canvas 235.7 25.8
Sherpa speech 235.2 71.6
oxlint and tsgolint 234.0 32.7
SWC 225.8 24.4
TypeScript 158.4 26.2

Electron downloads and native rebuild outputs are absent from both trials. The single warm-cache timing pair does not establish a speedup, and Linux/Windows results were not measured; do not present the macOS numbers as CI savings.