Skip slower Windows root package-store restores in CI (#24885)

* Skip slower Windows root package-store restores in CI

* Update reviewed mobile dependency-store cache expression
This commit is contained in:
Neil
2026-10-02 15:24:30 -07:00
committed by GitHub
parent 1aa0860f7e
commit add1c55590
7 changed files with 160 additions and 37 deletions
@@ -2,6 +2,10 @@ name: Install Node dependencies
description: Installs the Node toolchain and repository dependencies for CI jobs, with optional Electron archive caching.
inputs:
cache-pnpm-store:
description: Restore or save the pnpm download store; verification and native caches are independent.
required: false
default: 'true'
cache-pnpm-verification:
description: Restore pnpm's policy-checked lockfile verification record.
required: false
@@ -69,7 +73,7 @@ runs:
uses: actions/setup-node@v6
with:
node-version-file: package.json
cache: ${{ github.event_name != 'pull_request' && 'pnpm' || '' }}
cache: ${{ github.event_name != 'pull_request' && inputs.cache-pnpm-store != 'false' && 'pnpm' || '' }}
cache-dependency-path: ${{ inputs.cache-dependency-path }}
package-manager-cache: false
@@ -79,7 +83,7 @@ runs:
uses: actions/setup-node@v6
with:
node-version: ${{ inputs.node-version }}
cache: ${{ github.event_name != 'pull_request' && 'pnpm' || '' }}
cache: ${{ github.event_name != 'pull_request' && inputs.cache-pnpm-store != 'false' && 'pnpm' || '' }}
cache-dependency-path: ${{ inputs.cache-dependency-path }}
package-manager-cache: false
@@ -87,8 +91,10 @@ runs:
- name: Resolve pnpm download store
id: pnpm-store
if: >-
github.event_name == 'pull_request' &&
(runner.os != 'Windows' || runner.arch != 'X64' || !contains(inputs.cache-dependency-path, 'mobile/pnpm-lock.yaml'))
github.event_name == 'pull_request' && inputs.cache-pnpm-store != 'false' &&
(runner.os != 'Windows' ||
!(runner.arch == 'X64' && contains(inputs.cache-dependency-path, 'mobile/pnpm-lock.yaml')) &&
!((runner.arch == 'X64' || runner.arch == 'ARM64') && inputs.cache-dependency-path == 'pnpm-lock.yaml'))
shell: bash
env:
LOCKFILE_HASH: ${{ hashFiles(inputs.cache-dependency-path) }}
@@ -100,11 +106,13 @@ runs:
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.
# Hosted Windows x64 mixed installs cost less than restoring their root/mobile store.
# Measured Windows installs cost less than unpacking their download store.
- name: Restore pnpm download store without saving
if: >-
github.event_name == 'pull_request' &&
(runner.os != 'Windows' || runner.arch != 'X64' || !contains(inputs.cache-dependency-path, 'mobile/pnpm-lock.yaml'))
github.event_name == 'pull_request' && inputs.cache-pnpm-store != 'false' &&
(runner.os != 'Windows' ||
!(runner.arch == 'X64' && contains(inputs.cache-dependency-path, 'mobile/pnpm-lock.yaml')) &&
!((runner.arch == 'X64' || runner.arch == 'ARM64') && inputs.cache-dependency-path == 'pnpm-lock.yaml'))
uses: actions/cache/restore@v5
with:
path: ${{ steps.pnpm-store.outputs.path }}
+1
View File
@@ -154,6 +154,7 @@ jobs:
- uses: ./.github/actions/install-node-dependencies
with:
native-runtime: ${{ runner.os == 'Windows' && 'node' || 'none' }}
cache-pnpm-store: ${{ runner.os != 'Windows' }}
# Linux release slots come from the floor and Alpine lanes; this slot serves local tests.
- uses: ./.github/actions/prepare-orcad-prebuilds
id: orcad-prebuild
+1
View File
@@ -109,6 +109,7 @@ jobs:
id: dependencies
with:
native-runtime: node
cache-pnpm-store: 'false'
# The deploy materializes rung A from this template; only this runner's slot exists here.
# The process-tree addon carries the launcher that starts the relay outside sshd's job; the
# orcad slot and the relay both stage it, and a standard-user host has no other launch route.
@@ -11,7 +11,9 @@ describe('CI dependency download caches', () => {
it('scopes desktop stores to the root lockfile and lets mixed installs opt in', () => {
expect(action.inputs['cache-dependency-path'].default).toBe('pnpm-lock.yaml')
for (const step of action.runs.steps.filter((step) => step.uses === 'actions/setup-node@v6')) {
expect(step.with.cache).toBe("${{ github.event_name != 'pull_request' && 'pnpm' || '' }}")
expect(step.with.cache).toBe(
"${{ github.event_name != 'pull_request' && inputs.cache-pnpm-store != 'false' && 'pnpm' || '' }}"
)
expect(step.with['cache-dependency-path']).toBe('${{ inputs.cache-dependency-path }}')
expect(step.with['package-manager-cache']).toBe(false)
}
@@ -30,13 +32,13 @@ describe('CI dependency download caches', () => {
])
})
it('restores PR stores except measured Windows mixed installs, without a post-job save', () => {
it('restores PR stores except measured Windows installs, without a post-job save', () => {
const resolve = action.runs.steps.find((step) => step.id === 'pnpm-store')
const restore = action.runs.steps.find(
(step) => step.name === 'Restore pnpm download store without saving'
)
expect(resolve.if).toBe(
"github.event_name == 'pull_request' && (runner.os != 'Windows' || runner.arch != 'X64' || !contains(inputs.cache-dependency-path, 'mobile/pnpm-lock.yaml'))"
"github.event_name == 'pull_request' && inputs.cache-pnpm-store != 'false' && (runner.os != 'Windows' || !(runner.arch == 'X64' && contains(inputs.cache-dependency-path, 'mobile/pnpm-lock.yaml')) && !((runner.arch == 'X64' || runner.arch == 'ARM64') && inputs.cache-dependency-path == 'pnpm-lock.yaml'))"
)
expect(restore.if).toBe(resolve.if)
expect(restore.uses).toBe('actions/cache/restore@v5')
@@ -69,36 +71,111 @@ describe('CI dependency download caches', () => {
['Windows x64 mixed PR', 'pull_request', 'Windows', 'X64', true, false, ''],
['Windows ARM64 mixed PR', 'pull_request', 'Windows', 'ARM64', true, true, ''],
['Windows x86 mixed PR', 'pull_request', 'Windows', 'X86', true, true, ''],
['Windows x64 root-only PR', 'pull_request', 'Windows', 'X64', false, true, ''],
['Windows x64 root-only PR', 'pull_request', 'Windows', 'X64', false, false, ''],
['Windows ARM64 root-only PR', 'pull_request', 'Windows', 'ARM64', false, false, ''],
['Windows x86 root-only PR', 'pull_request', 'Windows', 'X86', false, true, ''],
['Windows x64 custom PR', 'pull_request', 'Windows', 'X64', 'cloud/pnpm-lock.yaml', true, ''],
[
'Windows ARM64 custom PR',
'pull_request',
'Windows',
'ARM64',
'cloud/pnpm-lock.yaml',
true,
''
],
['Windows ARM64 root-only push', 'push', 'Windows', 'ARM64', false, false, 'pnpm'],
[
'Windows ARM64 root-only manual run',
'workflow_dispatch',
'Windows',
'ARM64',
false,
false,
'pnpm'
],
['Explicit Linux PR opt-out', 'pull_request', 'Linux', 'X64', false, false, '', 'false'],
['Explicit Windows push opt-out', 'push', 'Windows', 'ARM64', false, false, '', 'false'],
[
'Explicit Windows manual opt-out',
'workflow_dispatch',
'Windows',
'X64',
false,
false,
'',
'false'
],
[
'Explicit custom-store opt-out',
'pull_request',
'Windows',
'X64',
'cloud/pnpm-lock.yaml',
false,
'',
'false'
],
['Windows x64 root-only push', 'push', 'Windows', 'X64', false, false, 'pnpm'],
['Linux x64 mixed PR', 'pull_request', 'Linux', 'X64', true, true, ''],
['Linux ARM64 mixed PR', 'pull_request', 'Linux', 'ARM64', true, true, ''],
['macOS ARM64 mixed PR', 'pull_request', 'macOS', 'ARM64', true, true, ''],
['Windows x64 mixed push', 'push', 'Windows', 'X64', true, false, 'pnpm'],
['Windows x64 mixed manual run', 'workflow_dispatch', 'Windows', 'X64', true, false, 'pnpm']
])('%s keeps its scoped store policy', (_name, event, os, arch, mixed, restore, cache) => {
const context = {
github: { event_name: event },
runner: { os, arch },
inputs: {
'cache-dependency-path': mixed ? 'pnpm-lock.yaml\nmobile/pnpm-lock.yaml' : 'pnpm-lock.yaml'
},
contains: (value, search) => value.toLowerCase().includes(search.toLowerCase())
}
const evaluate = (expression) =>
runInNewContext(
expression.replaceAll('inputs.cache-dependency-path', 'inputs["cache-dependency-path"]'),
context
)
for (const step of action.runs.steps.filter(
(step) =>
step.id === 'pnpm-store' || step.name === 'Restore pnpm download store without saving'
)) {
expect(evaluate(step.if)).toBe(restore)
}
for (const step of action.runs.steps.filter((step) => step.uses === 'actions/setup-node@v6')) {
expect(evaluate(step.with.cache.slice(3, -2))).toBe(cache)
expect(step.with['package-manager-cache']).toBe(false)
])(
'%s keeps its scoped store policy',
(_name, event, os, arch, mixed, restore, cache, storeCache = 'true') => {
const context = {
github: { event_name: event },
runner: { os, arch },
inputs: {
'cache-pnpm-store': storeCache,
'cache-dependency-path':
typeof mixed === 'string'
? mixed
: mixed
? 'pnpm-lock.yaml\nmobile/pnpm-lock.yaml'
: 'pnpm-lock.yaml'
},
contains: (value, search) => value.toLowerCase().includes(search.toLowerCase())
}
const evaluate = (expression) =>
runInNewContext(
expression
.replaceAll('inputs.cache-dependency-path', 'inputs["cache-dependency-path"]')
.replaceAll('inputs.cache-pnpm-store', 'inputs["cache-pnpm-store"]'),
context
)
for (const step of action.runs.steps.filter(
(step) =>
step.id === 'pnpm-store' || step.name === 'Restore pnpm download store without saving'
)) {
expect(evaluate(step.if)).toBe(restore)
}
for (const step of action.runs.steps.filter(
(step) => step.uses === 'actions/setup-node@v6'
)) {
expect(evaluate(step.with.cache.slice(3, -2))).toBe(cache)
expect(step.with['package-manager-cache']).toBe(false)
}
}
)
it('opts Windows server consumers out while preserving the main warmer store writer', () => {
const installer = './.github/actions/install-node-dependencies'
const persistence = workflow('node-server-tests').jobs.persistence.steps.find(
(step) => step.uses === installer
)
const ssh = workflow('ssh-windows-hosts').jobs.hosts.steps.find(
(step) => step.uses === installer
)
const warmer = workflow('ci-cache-warmup').jobs['warm-windows'].steps.find(
(step) => step.uses === installer
)
expect(action.inputs['cache-pnpm-store'].default).toBe('true')
expect(persistence.with['cache-pnpm-store']).toBe("${{ runner.os != 'Windows' }}")
expect(ssh.with['cache-pnpm-store']).toBe('false')
expect(warmer.with['cache-pnpm-store']).toBeUndefined()
})
it('restores Windows packaging downloads from the release cache without a PR upload', () => {
@@ -122,7 +122,7 @@ const REVIEWED_COMPUTED_PATHS = [
'${{ steps.pnpm-store.outputs.path }}',
// Only pnpm's lockfile-verified.jsonl record, never Metro transforms.
'${{ steps.verification-cache.outputs.path }}',
"${{ github.event_name != 'pull_request' && 'pnpm' || '' }} store"
"${{ github.event_name != 'pull_request' && inputs.cache-pnpm-store != 'false' && 'pnpm' || '' }} store"
]
/** Every step a workflow runs, descending into the repository's own composite actions. */
@@ -289,7 +289,8 @@ describe('PR workflow parallelism', () => {
expect(steps[pnpmIndex].uses).toBe('pnpm/setup@v2')
expect(steps[pnpmIndex].with.version).toBeUndefined()
expect(steps[pnpmIndex].with.install).toBe(false)
const saveOutsidePrs = "${{ github.event_name != 'pull_request' && 'pnpm' || '' }}"
const saveOutsidePrs =
"${{ github.event_name != 'pull_request' && inputs.cache-pnpm-store != 'false' && 'pnpm' || '' }}"
expect(steps[nodeIndex].with.cache).toBe(saveOutsidePrs)
expect(steps[nodeIndex].if).toBe("inputs.node-version == ''")
expect(steps[requestedNodeIndex].if).toBe("inputs.node-version != ''")
@@ -304,7 +305,7 @@ describe('PR workflow parallelism', () => {
)
expect(steps[restoreIndex].uses).toBe('actions/cache/restore@v5')
expect(steps[restoreIndex].if).toBe(
"github.event_name == 'pull_request' && (runner.os != 'Windows' || runner.arch != 'X64' || !contains(inputs.cache-dependency-path, 'mobile/pnpm-lock.yaml'))"
"github.event_name == 'pull_request' && inputs.cache-pnpm-store != 'false' && (runner.os != 'Windows' || !(runner.arch == 'X64' && contains(inputs.cache-dependency-path, 'mobile/pnpm-lock.yaml')) && !((runner.arch == 'X64' || runner.arch == 'ARM64') && inputs.cache-dependency-path == 'pnpm-lock.yaml'))"
)
})
+35
View File
@@ -76,6 +76,41 @@ request reuse only following an exact prepared native-cache hit; manual SSH and
all release builders retain fresh compilation. Subsequent staging checks still
run. Hosted validation and the reuse interval remain to be measured.
## Windows root download stores: registry installs finish sooner
Three paired samples on each Windows architecture compared the existing exact
main download-store restore with a fresh registry install. Each treatment used a
fresh dependency tree, store and pnpm metadata, with registry-first ordering in
sample 2. Both restored the same policy-checked verification record before timing.
All six pairs used Node 24.21.0 and pnpm 12.8.1; manifest digests and installed
lockfile digests matched, and both retained frozen, script-free installation.
| Runner | Cached totals (seconds) | Registry totals (seconds) | Paired median saving |
| ------------- | --------------------------- | --------------------------- | -------------------- |
| Windows x64 | 26.820 / 28.885 / 27.751 | 13.644 / 14.126 / 12.908 | 14.759 seconds |
| Windows ARM64 | 216.540 / 288.492 / 189.342 | 119.856 / 238.562 / 115.611 | 73.731 seconds |
The x64 samples are the three successful Windows 2022 jobs in
[run 37064549378](https://github.com/stablyai/orca/actions/runs/37064549378).
Its ARM cleanup guard rejected pnpm's setup-owned store path before measurement;
those incomplete ARM jobs are excluded. The corrected
[ARM-only run](https://github.com/stablyai/orca/actions/runs/37065220916) passed all
three samples. Earlier rejected measurements also stopped before installation
because an optional config file was absent; none count toward these timings.
Intervals include actual store lookup/restore, inter-step overhead and root
installation. Checkout, toolchain setup, tree/store reset, verification-record
restoration and native preparation are excluded. ARM variation is substantial;
these samples do not measure whole-workflow, queue or billing savings.
Root-only Windows x64/ARM64 PR installs now skip the download-store restore.
The existing x64 mixed-install exception remains. An explicit store opt-out also
lets Windows headless persistence and SSH jobs avoid the archive on main or
manual runs. Frozen installs, verification records, native caches and every
qualification check remain. Other lockfile sets and platforms keep their
existing policy. Default non-PR writers, including the warmer, still seed stores
for direct setup-node consumers and workflows that run package scripts.
## October 1 Windows and dependency cache follow-up
[PR #24355](https://github.com/stablyai/orca/pull/24355) merged at `197ea3a3`.