Files
orca/.github/actions/install-node-dependencies/action.yml
T
Neil d05080175b perf(ci): stop duplicating shared Linux download caches (#23578)
* perf(ci): pilot pnpm verification record caching on Linux

* test(ci): review pnpm verification record in mobile cache audit

* perf(ci): share Electron downloads and clean closed PR caches

* test(ci): retain cache ordering checks for restore-only consumers

* perf(ci): limit archive sharing rollout to primed Linux hosts
2026-09-28 01:58:46 -07:00

262 lines
12 KiB
YAML

name: Install Node dependencies
description: Installs the Node toolchain and repository dependencies for CI jobs, with optional Electron archive caching.
inputs:
cache-pnpm-verification:
description: Restore pnpm's policy-checked lockfile verification record on Linux.
required: false
default: 'true'
native-runtime:
description: Native runtime to prepare after the script-free install (none, node, or electron).
required: false
default: none
node-version:
description: Node.js version override; defaults to the version declared in package.json.
required: false
default: ''
cache-dependency-path:
description: Lockfiles for the pnpm download store; include mobile/pnpm-lock.yaml only when the job installs mobile dependencies.
required: false
default: pnpm-lock.yaml
persist-native-cache:
description: Save restored native modules at job end. Set false when a later step overwrites the same path with a different ABI.
required: false
default: 'true'
cache-electron-package:
description: Cache the Electron package archive and export ELECTRON_CACHE for following steps.
required: false
default: 'false'
outputs:
verification-cache-hit:
description: Whether pnpm's verification record was restored.
value: ${{ steps.verification-cache-restore.outputs.cache-hit }}
verification-cache-path:
description: The small pnpm-owned verification record, without registry metadata.
value: ${{ steps.verification-cache.outputs.path }}
verification-cache-key:
description: Exact verification record key, also used by the isolated PR benchmark.
value: ${{ steps.verification-cache.outputs.key }}
node-version:
description: Resolved Node.js version used for the install.
value: ${{ steps.requested-node.outputs.node-version || steps.default-node.outputs.node-version }}
native-cache-scope:
description: Operating-system image scope used by the native module cache.
value: ${{ steps.native-cache-scope.outputs.scope }}
native-cache-hit:
description: Whether the compiled native module cache was restored.
value: ${{ steps.native-cache-restore.outputs.cache-hit || steps.native-cache-restore-only.outputs.cache-hit }}
runs:
using: composite
steps:
# setup-node needs pnpm on PATH to locate and restore its store.
- name: Setup pnpm
uses: pnpm/setup@v2
with:
install: false
# Desktop-only jobs should not miss their download cache when mobile dependencies change.
- name: Setup Node.js
id: default-node
if: inputs.node-version == ''
uses: actions/setup-node@v6
with:
node-version-file: package.json
cache: ${{ github.event_name != 'pull_request' && 'pnpm' || '' }}
cache-dependency-path: ${{ inputs.cache-dependency-path }}
package-manager-cache: false
- name: Setup requested Node.js
id: requested-node
if: inputs.node-version != ''
uses: actions/setup-node@v6
with:
node-version: ${{ inputs.node-version }}
cache: ${{ github.event_name != 'pull_request' && 'pnpm' || '' }}
cache-dependency-path: ${{ inputs.cache-dependency-path }}
package-manager-cache: false
# PR-local stores compete with reusable build caches for the repository quota.
- name: Resolve pnpm download store
id: pnpm-store
if: github.event_name == 'pull_request'
shell: bash
env:
LOCKFILE_HASH: ${{ hashFiles(inputs.cache-dependency-path) }}
run: |
test -n "$LOCKFILE_HASH"
cache_path="$(pnpm store path --silent)"
test -n "$cache_path"
printf 'path=%s\n' "$cache_path" >> "$GITHUB_OUTPUT"
printf 'arch=%s\n' "$(node -p 'require("node:os").arch()')" >> "$GITHUB_OUTPUT"
# Match setup-node's key and path so existing default-branch stores remain reusable.
- name: Restore pnpm download store without saving
if: github.event_name == 'pull_request'
uses: actions/cache/restore@v5
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: node-cache-${{ runner.os }}-${{ steps.pnpm-store.outputs.arch }}-pnpm-${{ hashFiles(inputs.cache-dependency-path) }}
- name: Resolve pnpm verification cache
id: verification-cache
if: runner.os == 'Linux' && inputs.cache-pnpm-verification == 'true'
shell: bash
env:
POLICY_HASH: ${{ hashFiles('pnpm-lock.yaml', 'pnpm-workspace.yaml', '.npmrc') }}
run: |
printf 'path=%s/lockfile-verified.jsonl\n' "$(pnpm cache path)" >> "$GITHUB_OUTPUT"
printf 'key=pnpm-verification-v1-%s-%s-%s-%s\n' "$RUNNER_OS" "$RUNNER_ARCH" "$(pnpm --version)" "$POLICY_HASH" >> "$GITHUB_OUTPUT"
- name: Restore pnpm verification record
id: verification-cache-restore
if: steps.verification-cache.outputs.key != ''
continue-on-error: true
uses: actions/cache/restore@v5
with:
path: ${{ steps.verification-cache.outputs.path }}
key: ${{ steps.verification-cache.outputs.key }}
- name: Validate native runtime
shell: bash
env:
NATIVE_RUNTIME: ${{ inputs.native-runtime }}
run: |
case "$NATIVE_RUNTIME" in
none|node|electron) ;;
*)
echo "::error::native-runtime must be none, node, or electron"
exit 2
;;
esac
- name: Prepare dependency install
shell: bash
run: |
if [ -e node_modules ]; then
ls -ld node_modules
rm -rf node_modules
fi
# Why --frozen-lockfile: re-resolving pulls ~62 MB of registry packuments per job
# (measured) to recompute what the lockfile already pins, and the `git diff` guard
# below fails the run whenever that recomputation would have changed anything. The
# guard stays so a stale lockfile still fails by name rather than by resolver error.
- name: Install dependencies
shell: bash
run: |
pnpm install --frozen-lockfile --ignore-scripts
# Job containers can run composite steps from a source mirror without .git.
if [ "$(git -C "$GITHUB_WORKSPACE" rev-parse --is-inside-work-tree 2>/dev/null)" = true ]; then
git -C "$GITHUB_WORKSPACE" diff --exit-code -- package.json pnpm-lock.yaml pnpm-workspace.yaml
fi
# pnpm checks the cached record's policy and validity; never bypass verification.
- name: Save pnpm verification record on main
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' && steps.verification-cache.outputs.key != '' && steps.verification-cache-restore.outputs.cache-hit != 'true'
continue-on-error: true
uses: actions/cache/save@v5
with:
path: ${{ steps.verification-cache.outputs.path }}
key: ${{ steps.verification-cache.outputs.key }}
- name: Resolve Electron package cache
id: electron-package-cache
if: inputs.native-runtime == 'electron' || inputs.cache-electron-package == 'true'
shell: bash
run: |
set -euo pipefail
case "$RUNNER_OS" in
Linux) cache_root="$HOME/.cache/electron" ;;
macOS) cache_root="$HOME/Library/Caches/electron" ;;
Windows) cache_root="${LOCALAPPDATA:-$HOME/AppData/Local}/electron/Cache" ;;
*)
echo "::error::Unsupported runner OS for Electron cache: $RUNNER_OS"
exit 2
;;
esac
printf 'cache-root=%s\n' "$cache_root" >> "$GITHUB_OUTPUT"
printf 'ELECTRON_CACHE=%s\n' "$cache_root" >> "$GITHUB_ENV"
printf 'version=%s\n' "$(node -p "require('./node_modules/electron/package.json').version")" >> "$GITHUB_OUTPUT"
- name: Cache Electron package archive
if: (github.event_name != 'pull_request' || runner.os != 'Linux') && steps.electron-package-cache.outputs.version != ''
uses: actions/cache@v5
with:
path: ${{ steps.electron-package-cache.outputs.cache-root }}
key: electron-package-${{ runner.os }}-${{ runner.arch }}-${{ steps.electron-package-cache.outputs.version }}
- name: Restore Electron package archive without saving
if: github.event_name == 'pull_request' && runner.os == 'Linux' && steps.electron-package-cache.outputs.version != ''
uses: actions/cache/restore@v5
with:
path: ${{ steps.electron-package-cache.outputs.cache-root }}
key: electron-package-${{ runner.os }}-${{ runner.arch }}-${{ steps.electron-package-cache.outputs.version }}
# Why cached: `--ignore-scripts` leaves node-pty without build/Release, so
# ensure-native-runtime node-gyp-compiles it in every job that asks for a runtime.
# The artifacts are ABI-bound, so the key carries the target runtime, the resolved
# Node version, and the patch whose contents the build has to match.
# Windows extra globs are empty on Linux. No restore-keys: a partial-match key is
# an ABI-mismatched build, and ensure-native-runtime would recompile it anyway.
# Native addons built on a newer Linux image can require glibc symbols
# missing from an older runner/container. ImageOS distinguishes hosted
# Windows/macOS images; /etc/os-release also distinguishes Linux containers.
- name: Resolve native cache scope
id: native-cache-scope
if: inputs.native-runtime != 'none'
shell: bash
run: |
scope="${ImageOS:-$RUNNER_OS}"
if [ -r /etc/os-release ]; then
. /etc/os-release
scope="${ID:-linux}-${VERSION_ID:-unknown}"
fi
echo "scope=$scope" >> "$GITHUB_OUTPUT"
- name: Restore compiled native modules
id: native-cache-restore
if: inputs.native-runtime != 'none' && inputs.persist-native-cache != 'false'
uses: actions/cache@v5
with:
path: |
node_modules/.pnpm/node-pty@*/node_modules/node-pty/build
native/windows-registry/build
node_modules/.pnpm/@vscode+windows-process-tre*/node_modules/@vscode/windows-process-tree/build
key: native-modules-${{ runner.os }}-${{ steps.native-cache-scope.outputs.scope }}-${{ runner.arch }}-${{ inputs.native-runtime }}-node${{ steps.requested-node.outputs.node-version || steps.default-node.outputs.node-version }}-${{ hashFiles('pnpm-lock.yaml', '.github/actions/install-node-dependencies/action.yml', 'config/scripts/ensure-native-runtime.mjs', 'config/scripts/rebuild-native-deps.mjs', 'config/patches/node-pty@1.1.0.patch', 'config/patches/@vscode__windows-process-tree@0.8.0.patch', 'native/windows-registry/src/addon.cc', 'native/windows-registry/binding.gyp', 'native/windows-registry/package.json') }}
- name: Restore compiled native modules without saving
id: native-cache-restore-only
if: inputs.native-runtime != 'none' && inputs.persist-native-cache == 'false'
uses: actions/cache/restore@v5
with:
path: |
node_modules/.pnpm/node-pty@*/node_modules/node-pty/build
native/windows-registry/build
node_modules/.pnpm/@vscode+windows-process-tre*/node_modules/@vscode/windows-process-tree/build
key: native-modules-${{ runner.os }}-${{ steps.native-cache-scope.outputs.scope }}-${{ runner.arch }}-${{ inputs.native-runtime }}-node${{ steps.requested-node.outputs.node-version || steps.default-node.outputs.node-version }}-${{ hashFiles('pnpm-lock.yaml', '.github/actions/install-node-dependencies/action.yml', 'config/scripts/ensure-native-runtime.mjs', 'config/scripts/rebuild-native-deps.mjs', 'config/patches/node-pty@1.1.0.patch', 'config/patches/@vscode__windows-process-tree@0.8.0.patch', 'native/windows-registry/src/addon.cc', 'native/windows-registry/binding.gyp', 'native/windows-registry/package.json') }}
# pnpm's bundled gyp_main.py is not executable on fresh Linux runners.
- name: Use external node-gyp
if: runner.os == 'Linux' && inputs.native-runtime != 'none'
shell: bash
env:
NATIVE_RUNTIME: ${{ inputs.native-runtime }}
NATIVE_CACHE_HIT: ${{ steps.native-cache-restore.outputs.cache-hit || steps.native-cache-restore-only.outputs.cache-hit }}
run: |
# A cache hit can contain unusable addons; probe before skipping the rebuild toolchain.
if [ "$NATIVE_RUNTIME" = node ] && [ "$NATIVE_CACHE_HIT" = true ] &&
node config/scripts/ensure-native-runtime.mjs --check-only; then
exit 0
fi
npm install -g node-gyp@11.5.0
echo "npm_config_node_gyp=$(npm root -g)/node-gyp/bin/node-gyp.js" >> "$GITHUB_ENV"
- name: Prepare native runtime
if: inputs.native-runtime != 'none'
shell: bash
env:
NATIVE_RUNTIME: ${{ inputs.native-runtime }}
run: node config/scripts/ensure-native-runtime.mjs --runtime="$NATIVE_RUNTIME"