5.3 KiB
Agent Note: Offline pnpm prefetch scripts
Status: implemented
English | 中文
Problem
A source checkout cannot boot dsh web until a supported Node runtime, pnpm install, and pnpm run build have materialized node_modules, package lib/ output, and apps/web/dist. Install reaches the npm registry (and Corepack reaches it for pnpm@11.7.0); the Node distro comes from nodejs.org/dist. Node's fetch client cannot use socks5h:// proxies, so a machine whose only ambient proxy is SOCKS fails pnpm install even while curl through the same proxy succeeds. An air-gapped follow-up install therefore needs a same-OS prefetch of the official Node archive, the lockfile store, and a Corepack home that does not call the registry.
Decision
scripts/offline/download-deps (POSIX twin .sh) runs while nodejs.org and the registry are reachable. It downloads the official Node zip/tar for the current OS and CPU from DSH_NODE_DIST_BASE (default https://nodejs.org/dist), verifies SHASUMS256.txt, unpacks into ignored .offline-cache/node/runtime, and writes .offline-cache/node/runtime.json. The Node version is DSH_NODE_VERSION when set, otherwise the running Node when it satisfies engines.node (^22.19.0 || >=24.0.0), otherwise the newest v24.x from index.json that publishes this platform's archive. It then sets COREPACK_HOME to .offline-cache/corepack, corepack prepares the root package.json packageManager pin using that Node, and pnpm fetch --frozen-lockfile --store-dir .offline-store. scripts/offline/install-deps (POSIX twin .sh) unpacks the cached Node if needed, prepends it to PATH, sets COREPACK_ENABLE_NETWORK=0, and runs pnpm install --offline --frozen-lockfile against that store. Optional -Build / --build runs npm run build:lib then pnpm --dir apps/web run build, avoiding the nested pnpm --filter invocation inside npm run build:web that can pick a different Corepack pnpm than packageManager.
DSH_NPM_HTTP_PROXY, when set, is an HTTP overlay applied only around corepack prepare and pnpm fetch, then the process proxy variables are restored so a caller SOCKS session is unchanged. curl uses the inherited HTTP_PROXY / HTTPS_PROXY / ALL_PROXY (SOCKS included) and falls back to DSH_NPM_HTTP_PROXY only when none of those are set. A socks5 or socks5h value for Node/pnpm (from DSH_NPM_HTTP_PROXY, or from the inherited proxies when no HTTP overlay is set) fails before Corepack runs, because that scheme is not a supported pnpm transport.
The scripts prefetch Node and the lockfile for the host OS and CPU they run on. They do not download LLM weights, Playwright browsers, or a local inference server. Copy the checkout including .offline-store and .offline-cache to the offline machine.
Alternatives considered
Tell contributors to copy a finished node_modules tree and skip a second install. A copied isolated pnpm layout is host-specific and breaks when the store path or package content-address links do not match; pnpm install --offline against a fetched store is the documented pnpm air-gap path.
Check supportedArchitectures into the download script so one store serves win32 and linux. Rejected for the default: optional native packages (esbuild, koffi, node-pty) must match the install host, and a mixed store still needs a same-OS install to unpack the right binaries. Cross-OS prefetch remains an explicit local pnpm fetch config, not the script default.
Add root package.json scripts that call these files. Nested pnpm under npm run is what already mismatches Corepack versions on build:web; the wrappers stay invoked as files so they control COREPACK_HOME and --store-dir.
Overwrite the caller's HTTP_PROXY for the whole download script. Rejected: .\download-deps.ps1 runs in the current PowerShell session, so a session-wide overwrite would replace a working SOCKS proxy for later commands. curl can use SOCKS; only Node/pnpm need the HTTP overlay, and only for those child processes.
Ship the Node MSI or pkg installer. Rejected: those installers need administrator rights and a global install; the official zip/tar is relocatable, and install only unpacks it and prepends PATH.
Hardcode one Node version in the script. Rejected: a pinned patch goes stale against engines.node and CI (22.19, 24, 26). Matching the running Node keeps optional native packages aligned with the prefetch host; index.json is the fallback when Node is not on PATH.
Vendor the pnpm store in git. The store is hundreds of megabytes of registry tarballs; .gitignore keeps .offline-store/ and .offline-cache/ untracked.
Consequences
Air-gap preparation is a two-command, same-OS pair and does not change the online pnpm install path. The offline machine does not need a preinstalled Node: install unpacks the cached official distro. dsh plugin … add of a registry or git spec still needs a network on the machine that runs it. Local chat still needs a separately installed OpenAI-compatible server; these scripts do not substitute for that.