Skip to content

Commit b30bd2a

Browse files
authored
ci: pre-warm Namespace cache for unit/component-view test shards (#30098)
## Summary Introduces a \`prepare-ci-js-deps\` job and a \`setup-ci-js-deps\` composite action to eliminate redundant \`yarn install\` + \`yarn setup:github-ci --node\` across 13 CI shards (10 unit-test + 2 component-view + 1 merge job). ### What changed **New job \`prepare-ci-js-deps\` (\`ci.yml\`)** Runs before \`unit-tests\`, \`component-view-tests\`, and \`merge-unit-and-component-view-tests\` and installs dependencies once on behalf of all consumers. - **Namespace runners**: mounts the persistent cache volume and runs install only when the volume is cold (yarn.lock changed in the pool). Consumers restore from the same volume — install is skipped entirely on a warm cache. - **Non-Namespace runners** (current default for all PRs): runs install, then packs \`node_modules\` + generated files into a gzipped tarball (\`ci-js-deps.tar.gz\`) and uploads it as a workflow-run artifact. Consumers download and extract the tarball instead of running their own install. > **TEMP**: The tarball artifact path is a temporary fallback while Namespace is on trial. Once Namespace becomes the default runner, the pack/upload steps in the producer and the download/extract steps in each consumer can be deleted. The \`setup-ci-js-deps\` composite action requires no changes. **New composite action \`.github/actions/setup-ci-js-deps\`** Shared setup sequence used by the producer and all three consumer jobs: 1. Mount Namespace cache (Namespace only) 2. \`actions/setup-node\` 3. File-based freshness check (\`node_modules\` dir + \`termsOfUseContent.ts\`) 4. \`yarn install --immutable\` + \`yarn setup:github-ci --node\` — only if step 3 finds files missing On non-Namespace consumers, step 3 finds the tarball already extracted, so step 4 is skipped. **Consumer jobs (\`unit-tests\`, \`component-view-tests\`, \`merge-unit-and-component-view-tests\`)** Each job's setup block is replaced with: - TEMP: download + extract \`ci-js-deps.tar.gz\` (non-Namespace only; tar preserves permissions so no separate restore step is needed) - Call to \`setup-ci-js-deps\` (which skips install when files are already present) **\`check-all-jobs-pass\`** \`prepare-ci-js-deps\` added to the gate. ### Not changed - \`setup-node-modules.yml\` — untouched - E2E jobs — untouched (Phase 2 scope) ## Estimated impact | Lane | Shards | Before | After (non-Namespace) | After (Namespace) | |---|---|---|---|---| | \`unit-tests\` | 10 | ~2 min install each | ~30s download+extract | ~10s cache hit | | \`component-view-tests\` | 2 | ~2 min install each | ~30s download+extract | ~10s cache hit | | \`merge-unit-and-component-view-tests\` | 1 | ~2 min install | ~30s download+extract | ~10s cache hit | | **Saved per run** | | | **~22 runner-min** | **~26 runner-min** | ## Removal guide (when Namespace passes trial) Search for \`# TEMP\` in \`ci.yml\` — there are exactly 4 clearly marked blocks: 1. \`Pack CI JS deps\` + \`Upload CI JS deps artifact\` in \`prepare-ci-js-deps\` 2. \`Download CI JS deps artifact\` + \`Extract CI JS deps\` in each of the 3 consumer jobs Delete those blocks. No changes needed to \`setup-ci-js-deps/action.yml\`. ## Manual testing steps \`\`\`gherkin Given a pull request runs CI on a non-Namespace runner (ubuntu-latest) When prepare-ci-js-deps completes Then a ci-js-deps.tar.gz artifact is uploaded And each unit-tests and component-view-tests shard downloads and extracts it And all shards skip yarn install and pass Given a pull request runs CI on a Namespace runner When prepare-ci-js-deps completes on a warm cache volume Then all consumer shards mount the cache volume and skip yarn install And all shards pass Given the Namespace cache volume is cold (yarn.lock changed) When prepare-ci-js-deps runs Then it installs dependencies and warms the volume And all consumer shards hit the warm volume and skip install \`\`\` ## References - ADR: \`docs/ci-dependency-setup-reuse-adr.md\` - Related action: \`.github/actions/restore-node-modules-permissions\` <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes CI job dependencies and dependency-caching behavior for unit/component-view shards; misconfiguration could cause flaky installs or missing artifacts, but impact is limited to CI execution. > > **Overview** > **Reduces redundant JS dependency setup across CI test shards.** Adds a new composite action `setup-ci-js-deps` that configures Namespace cache, sets up Node, and conditionally runs `yarn install --immutable` + `yarn setup:github-ci --node` based on workspace freshness. > > Introduces a `prepare-ci-js-deps` job that runs once before `unit-tests`, `component-view-tests`, and `merge-unit-and-component-view-tests` to warm Namespace shared cache; for non-Namespace runners it packs and uploads a short-lived `ci-js-deps` artifact that consumer shards download/extract to skip their own installs. > > Updates test/merge jobs to depend on `prepare-ci-js-deps`, replaces their inline dependency setup with the new action + temporary artifact restore steps, tightens `if` conditions to require successful `get_requirements`, and adds `prepare-ci-js-deps` to the `check-all-jobs-pass` gate. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit d9e8501. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent b90a311 commit b30bd2a

2 files changed

Lines changed: 130 additions & 83 deletions

File tree

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
name: 'Setup CI JS dependencies'
2+
description: >
3+
Sets up node_modules and project build outputs for CI jobs.
4+
On Namespace runners, mounts the shared cache volume then runs
5+
yarn install --immutable to sync with the current yarn.lock.
6+
On non-Namespace runners, skips install when node_modules is
7+
already present from a same-run artifact; otherwise installs from scratch.
8+
9+
inputs:
10+
runner_provider:
11+
description: 'Runner provider (`namespace` or any GitHub-hosted value).'
12+
required: false
13+
default: 'current'
14+
15+
runs:
16+
using: 'composite'
17+
steps:
18+
- name: Configure Namespace cache
19+
if: ${{ inputs.runner_provider == 'namespace' }}
20+
uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1
21+
with:
22+
path: |
23+
~/.cache/yarn
24+
.metamask
25+
node_modules
26+
.yarn/cache
27+
28+
- uses: actions/setup-node@v6
29+
with:
30+
node-version-file: '.nvmrc'
31+
cache: ${{ inputs.runner_provider != 'namespace' && 'yarn' || '' }}
32+
33+
# Namespace: always run install so the shared volume stays in sync with yarn.lock.
34+
# Non-Namespace: skip install when node_modules was extracted from a same-run
35+
# artifact; run install when starting from a clean workspace.
36+
- name: Determine if install is needed
37+
id: check-deps
38+
shell: bash
39+
run: |
40+
if [ "${{ inputs.runner_provider }}" != "namespace" ] && \
41+
[ -d node_modules ] && \
42+
[ -f app/util/termsOfUse/termsOfUseContent.ts ]; then
43+
echo "needs-install=false" >> "$GITHUB_OUTPUT"
44+
else
45+
echo "needs-install=true" >> "$GITHUB_OUTPUT"
46+
fi
47+
48+
- name: Install Yarn dependencies with retry
49+
if: ${{ steps.check-deps.outputs.needs-install == 'true' }}
50+
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # v3.0.2
51+
with:
52+
timeout_minutes: 10
53+
max_attempts: 3
54+
retry_wait_seconds: 30
55+
command: yarn install --immutable
56+
57+
- name: Run project setup
58+
if: ${{ steps.check-deps.outputs.needs-install == 'true' }}
59+
shell: bash
60+
run: yarn setup:github-ci --node

.github/workflows/ci.yml

Lines changed: 70 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -600,12 +600,41 @@ jobs:
600600
run: ${{ steps.download-actionlint.outputs.executable }} -color -config-file .github/actionlint.yaml
601601
shell: bash
602602

603+
# Warms the Namespace cache volume before test shards run. On non-Namespace
604+
# runners uploads a ci-js-deps artifact so consumers can skip their own install.
605+
prepare-ci-js-deps:
606+
name: Prepare CI JS dependencies
607+
runs-on: ${{ inputs.runner_provider == 'namespace' && 'namespace-profile-metamask-ci-linux' || 'ubuntu-latest' }}
608+
if: ${{ needs.get_requirements.outputs.skip_everything != 'true' }}
609+
needs:
610+
- get_requirements
611+
steps:
612+
- uses: actions/checkout@v6
613+
- uses: ./.github/actions/setup-ci-js-deps
614+
with:
615+
runner_provider: ${{ inputs.runner_provider }}
616+
# TEMP: artifact fallback for non-Namespace runners.
617+
# Remove these two steps once Namespace passes the trial and becomes the default.
618+
- name: Pack CI JS deps
619+
if: ${{ inputs.runner_provider != 'namespace' }}
620+
run: tar -czf ci-js-deps.tar.gz node_modules app/util/termsOfUse/termsOfUseContent.ts
621+
- name: Upload CI JS deps artifact
622+
if: ${{ inputs.runner_provider != 'namespace' }}
623+
uses: actions/upload-artifact@v4
624+
with:
625+
name: ci-js-deps
626+
path: ci-js-deps.tar.gz
627+
retention-days: 1
628+
compression-level: 0
629+
if-no-files-found: error
630+
603631
unit-tests:
604632
name: Unit tests (${{ matrix.shard }})
605633
runs-on: ${{ inputs.runner_provider == 'namespace' && 'namespace-profile-metamask-ci-linux' || 'ubuntu-latest' }}
606-
if: ${{ needs.get_requirements.outputs.skip_everything != 'true' }}
634+
if: ${{ !cancelled() && needs.get_requirements.result == 'success' && needs.get_requirements.outputs.skip_everything != 'true' }}
607635
needs:
608636
- get_requirements
637+
- prepare-ci-js-deps
609638
strategy:
610639
matrix:
611640
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
@@ -614,28 +643,20 @@ jobs:
614643
if: ${{ inputs.runner_provider == 'namespace' }}
615644
- uses: actions/checkout@v6
616645
if: ${{ inputs.runner_provider != 'namespace' }}
617-
- name: Configure Namespace cache
618-
if: ${{ inputs.runner_provider == 'namespace' }}
619-
uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1
620-
with:
621-
path: |
622-
~/.cache/yarn
623-
.metamask
624-
node_modules
625-
.yarn/cache
626-
- uses: actions/setup-node@v6
646+
# TEMP: artifact fallback for non-Namespace runners.
647+
# Remove these two steps once Namespace passes the trial and becomes the default.
648+
- name: Download CI JS deps artifact
649+
if: ${{ inputs.runner_provider != 'namespace' && needs.prepare-ci-js-deps.result == 'success' }}
650+
uses: actions/download-artifact@v4
627651
with:
628-
node-version-file: '.nvmrc'
629-
cache: ${{ inputs.runner_provider != 'namespace' && 'yarn' || '' }}
630-
- name: Install Yarn dependencies with retry
631-
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2
652+
name: ci-js-deps
653+
path: .
654+
- name: Extract CI JS deps
655+
if: ${{ inputs.runner_provider != 'namespace' && needs.prepare-ci-js-deps.result == 'success' }}
656+
run: tar -xzf ci-js-deps.tar.gz && rm ci-js-deps.tar.gz
657+
- uses: ./.github/actions/setup-ci-js-deps
632658
with:
633-
timeout_minutes: 10
634-
max_attempts: 3
635-
retry_wait_seconds: 30
636-
command: yarn install --immutable
637-
- name: Clean state and following up dependencies installation
638-
run: yarn setup:github-ci --node
659+
runner_provider: ${{ inputs.runner_provider }}
639660
- name: Prepare results directory
640661
run: mkdir -p tests/results
641662
# The "10" in this command is the total number of shards. It must be kept
@@ -681,45 +702,27 @@ jobs:
681702
# threshold calculation is accurate.
682703
merge-unit-and-component-view-tests:
683704
runs-on: ${{ inputs.runner_provider == 'namespace' && 'namespace-profile-metamask-ci-linux' || 'ubuntu-latest' }}
684-
needs: [unit-tests, component-view-tests]
705+
needs: [prepare-ci-js-deps, unit-tests, component-view-tests]
685706
if: ${{ !cancelled() && github.event_name != 'merge_group' }}
686707
steps:
687708
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
688709
if: ${{ inputs.runner_provider == 'namespace' }}
689710
- uses: actions/checkout@v6
690711
if: ${{ inputs.runner_provider != 'namespace' }}
691-
- name: Configure Namespace cache
692-
if: ${{ inputs.runner_provider == 'namespace' }}
693-
uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1
694-
with:
695-
path: |
696-
~/.cache/yarn
697-
.metamask
698-
node_modules
699-
.yarn/cache
700-
- name: Restore node_modules cache
701-
if: ${{ inputs.runner_provider != 'namespace' }}
702-
id: cache-node-modules
703-
uses: actions/cache@v4
704-
with:
705-
path: |
706-
node_modules
707-
.yarn/install-state.gz
708-
key: ${{ runner.os }}-node-modules-${{ hashFiles('yarn.lock') }}
709-
- uses: actions/setup-node@v6
712+
# TEMP: artifact fallback for non-Namespace runners.
713+
# Remove these two steps once Namespace passes the trial and becomes the default.
714+
- name: Download CI JS deps artifact
715+
if: ${{ inputs.runner_provider != 'namespace' && needs.prepare-ci-js-deps.result == 'success' }}
716+
uses: actions/download-artifact@v4
710717
with:
711-
node-version-file: '.nvmrc'
712-
cache: ${{ inputs.runner_provider != 'namespace' && 'yarn' || '' }}
713-
- name: Install Yarn dependencies with retry
714-
if: ${{ inputs.runner_provider == 'namespace' || steps.cache-node-modules.outputs.cache-hit != 'true' }}
715-
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2
718+
name: ci-js-deps
719+
path: .
720+
- name: Extract CI JS deps
721+
if: ${{ inputs.runner_provider != 'namespace' && needs.prepare-ci-js-deps.result == 'success' }}
722+
run: tar -xzf ci-js-deps.tar.gz && rm ci-js-deps.tar.gz
723+
- uses: ./.github/actions/setup-ci-js-deps
716724
with:
717-
timeout_minutes: 10
718-
max_attempts: 3
719-
retry_wait_seconds: 30
720-
command: yarn install --immutable
721-
- name: Clean state and following up dependencies installation
722-
run: yarn setup:github-ci --node
725+
runner_provider: ${{ inputs.runner_provider }}
723726
- name: Download coverage shards (Namespace)
724727
if: ${{ inputs.runner_provider == 'namespace' }}
725728
uses: namespace-actions/download-artifact@7cbad919e4b0e09f17e9d6311a444ff002992b5b # v2.0.1
@@ -879,9 +882,10 @@ jobs:
879882
component-view-tests:
880883
name: Component view tests
881884
runs-on: ${{ inputs.runner_provider == 'namespace' && 'namespace-profile-metamask-ci-linux' || 'ubuntu-latest' }}
882-
if: ${{ needs.get_requirements.outputs.skip_everything != 'true' }}
885+
if: ${{ !cancelled() && needs.get_requirements.result == 'success' && needs.get_requirements.outputs.skip_everything != 'true' }}
883886
needs:
884887
- get_requirements
888+
- prepare-ci-js-deps
885889
strategy:
886890
matrix:
887891
shard: [1, 2]
@@ -890,38 +894,20 @@ jobs:
890894
if: ${{ inputs.runner_provider == 'namespace' }}
891895
- uses: actions/checkout@v6
892896
if: ${{ inputs.runner_provider != 'namespace' }}
893-
- name: Configure Namespace cache
894-
if: ${{ inputs.runner_provider == 'namespace' }}
895-
uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1
896-
with:
897-
path: |
898-
~/.cache/yarn
899-
.metamask
900-
node_modules
901-
.yarn/cache
902-
- name: Restore node_modules cache
903-
if: ${{ inputs.runner_provider != 'namespace' }}
904-
id: cache-node-modules
905-
uses: actions/cache@v4
906-
with:
907-
path: |
908-
node_modules
909-
.yarn/install-state.gz
910-
key: ${{ runner.os }}-node-modules-${{ hashFiles('yarn.lock') }}
911-
- uses: actions/setup-node@v6
897+
# TEMP: artifact fallback for non-Namespace runners.
898+
# Remove these two steps once Namespace passes the trial and becomes the default.
899+
- name: Download CI JS deps artifact
900+
if: ${{ inputs.runner_provider != 'namespace' && needs.prepare-ci-js-deps.result == 'success' }}
901+
uses: actions/download-artifact@v4
912902
with:
913-
node-version-file: '.nvmrc'
914-
cache: ${{ inputs.runner_provider != 'namespace' && 'yarn' || '' }}
915-
- name: Install Yarn dependencies with retry
916-
if: ${{ inputs.runner_provider == 'namespace' || steps.cache-node-modules.outputs.cache-hit != 'true' }}
917-
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2
903+
name: ci-js-deps
904+
path: .
905+
- name: Extract CI JS deps
906+
if: ${{ inputs.runner_provider != 'namespace' && needs.prepare-ci-js-deps.result == 'success' }}
907+
run: tar -xzf ci-js-deps.tar.gz && rm ci-js-deps.tar.gz
908+
- uses: ./.github/actions/setup-ci-js-deps
918909
with:
919-
timeout_minutes: 10
920-
max_attempts: 3
921-
retry_wait_seconds: 30
922-
command: yarn install --immutable
923-
- name: Clean state and following up dependencies installation
924-
run: yarn setup:github-ci --node
910+
runner_provider: ${{ inputs.runner_provider }}
925911
- name: Prepare results directory
926912
run: mkdir -p tests/results
927913
- run: |
@@ -1288,6 +1274,7 @@ jobs:
12881274
- unit-tests
12891275
- component-view-tests
12901276
- check-workflows
1277+
- prepare-ci-js-deps
12911278
- js-bundle-size-check
12921279
- sonar-cloud-quality-gate-status
12931280
- build-android-apks

0 commit comments

Comments
 (0)