Skip to content

Commit 60aba2c

Browse files
committed
fix: decouple pnpm store cache saving from node-setup and implement dedicated caching workflow
1 parent 8994cc9 commit 60aba2c

3 files changed

Lines changed: 88 additions & 48 deletions

File tree

.github/actions/node-setup/action.yml

Lines changed: 2 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@ description: General setup of node
33
inputs:
44
node-version:
55
default: 26
6-
save-cache:
7-
description: Whether to save the pnpm store cache on main (still restored either way)
8-
default: 'true'
96

107
runs:
118
using: composite
@@ -26,9 +23,9 @@ runs:
2623

2724
# Restore-keys fall back to the closest prior store even when the lockfile
2825
# hash doesn't match exactly, so PRs reuse main's store instead of
29-
# installing from scratch.
26+
# installing from scratch. Saving/pruning the cache is done separately by
27+
# the prime-pnpm-cache workflow on push to main, not on every job here.
3028
- name: Restore pnpm store
31-
id: pnpm-store-restore
3229
uses: actions/cache/restore@v6
3330
with:
3431
path: ${{ steps.pnpm-store.outputs.path }}
@@ -40,44 +37,3 @@ runs:
4037
shell: bash
4138
run: pnpm install --frozen-lockfile
4239

43-
# Cache keys are immutable, so every lockfile change saves a brand-new
44-
# store on top of the previous one and old package versions never get
45-
# dropped. Pruning here keeps the saved cache scoped to what this
46-
# lockfile actually needs instead of growing unbounded.
47-
- name: Prune pnpm store
48-
shell: bash
49-
run: pnpm store prune
50-
51-
# Only main writes new cache entries — PRs restore-and-reuse but never
52-
# save, so open PRs stop each spawning their own throwaway ~700MB entry.
53-
# cache-hit == exact key match already restored, so skip the save outright
54-
# instead of letting every concurrent main job queue up on the same save lock.
55-
- name: Save pnpm store
56-
if: github.ref == 'refs/heads/main' && inputs.save-cache == 'true' && steps.pnpm-store-restore.outputs.cache-hit != 'true'
57-
continue-on-error: true
58-
uses: actions/cache/save@v6
59-
with:
60-
path: ${{ steps.pnpm-store.outputs.path }}
61-
key: pnpm-store-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
62-
63-
# Cache keys are immutable, so every save creates a brand-new entry
64-
# instead of replacing the previous one — old pnpm-store entries for main
65-
# never get evicted by anything but GitHub's 10GB LRU quota. Delete them
66-
# only after the new save succeeds, keeping a couple of recent fallbacks
67-
# so an in-flight workflow always finds a cache to restore from.
68-
- name: Delete stale pnpm store caches
69-
if: github.ref == 'refs/heads/main' && inputs.save-cache == 'true'
70-
continue-on-error: true
71-
shell: bash
72-
env:
73-
GH_TOKEN: ${{ github.token }}
74-
run: |
75-
currentKey="pnpm-store-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}"
76-
cacheIds=$(gh cache list --repo "${{ github.repository }}" --ref refs/heads/main \
77-
--key "pnpm-store-${{ runner.os }}-" --limit 100 \
78-
--json id,key,createdAt \
79-
--jq "sort_by(.createdAt) | reverse | .[2:] | .[] | select(.key != \"$currentKey\") | .id")
80-
for id in $cacheIds; do
81-
echo "Deleting stale pnpm store cache $id"
82-
gh cache delete "$id" --repo "${{ github.repository }}" || true
83-
done

.github/workflows/ci.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,6 @@ jobs:
125125

126126
- name: Setup node and install deps
127127
uses: ./.github/actions/node-setup
128-
with:
129-
save-cache: 'false'
130128

131129
# allowBuilds disables this by default (pnpm-workspace.yaml) since it's
132130
# only needed here; vsce needs the native signing binary to publish.
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
name: Prime pnpm store cache
2+
3+
# Saving the pnpm store cache used to happen inside every job that called
4+
# node-setup, so any push to main that triggered more than one workflow
5+
# raced to save the same cache key. Do it once, here, decoupled from CI.
6+
on:
7+
push:
8+
branches:
9+
- main
10+
11+
concurrency:
12+
group: ${{ github.workflow }}-${{ github.ref }}
13+
# Only the latest push's lockfile matters for the cache, so a superseded
14+
# run for an older commit is wasted work — cancel it instead of queuing.
15+
cancel-in-progress: true
16+
17+
permissions:
18+
actions: write # Required to delete stale pnpm-store caches
19+
contents: read
20+
21+
jobs:
22+
prime-cache:
23+
name: Save pnpm store cache
24+
runs-on: ubuntu-latest
25+
steps:
26+
- uses: actions/checkout@v7
27+
28+
- name: Setup pnpm
29+
uses: pnpm/action-setup@v6
30+
31+
- name: Setup Node.js
32+
uses: actions/setup-node@v7
33+
with:
34+
node-version: 26
35+
36+
- name: Get pnpm store path
37+
id: pnpm-store
38+
run: echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT"
39+
40+
- name: Check for an existing exact-match cache
41+
id: pnpm-store-restore
42+
uses: actions/cache/restore@v6
43+
with:
44+
path: ${{ steps.pnpm-store.outputs.path }}
45+
key: pnpm-store-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
46+
lookup-only: true
47+
48+
- name: Install dependencies
49+
if: steps.pnpm-store-restore.outputs.cache-hit != 'true'
50+
run: pnpm install --frozen-lockfile
51+
52+
# Cache keys are immutable, so every lockfile change saves a brand-new
53+
# store on top of the previous one and old package versions never get
54+
# dropped. Pruning here keeps the saved cache scoped to what this
55+
# lockfile actually needs instead of growing unbounded.
56+
- name: Prune pnpm store
57+
if: steps.pnpm-store-restore.outputs.cache-hit != 'true'
58+
run: pnpm store prune
59+
60+
- name: Save pnpm store
61+
if: steps.pnpm-store-restore.outputs.cache-hit != 'true'
62+
uses: actions/cache/save@v6
63+
with:
64+
path: ${{ steps.pnpm-store.outputs.path }}
65+
key: pnpm-store-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
66+
67+
# Cache keys are immutable, so every save creates a brand-new entry
68+
# instead of replacing the previous one — old pnpm-store entries for
69+
# main never get evicted by anything but GitHub's 10GB LRU quota.
70+
# Delete them here, keeping a couple of recent fallbacks so an
71+
# in-flight workflow always finds a cache to restore from.
72+
- name: Delete stale pnpm store caches
73+
if: steps.pnpm-store-restore.outputs.cache-hit != 'true'
74+
continue-on-error: true
75+
env:
76+
GH_TOKEN: ${{ github.token }}
77+
run: |
78+
currentKey="pnpm-store-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}"
79+
cacheIds=$(gh cache list --repo "${{ github.repository }}" --ref refs/heads/main \
80+
--key "pnpm-store-${{ runner.os }}-" --limit 100 \
81+
--json id,key,createdAt \
82+
--jq "sort_by(.createdAt) | reverse | .[2:] | .[] | select(.key != \"$currentKey\") | .id")
83+
for id in $cacheIds; do
84+
echo "Deleting stale pnpm store cache $id"
85+
gh cache delete "$id" --repo "${{ github.repository }}" || true
86+
done

0 commit comments

Comments
 (0)