|
| 1 | +# Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. |
| 2 | +# |
| 3 | +# setup-workspace -- the one setup sequence every job that installs the |
| 4 | +# workspace runs after its checkout: Node from .nvmrc, the pinned pnpm, the |
| 5 | +# pnpm store restored from cache, `pnpm install --frozen-lockfile`, and the |
| 6 | +# store saved back on `push` and `schedule` runs only. |
| 7 | +# |
| 8 | +# Why one place. The same five steps were copied by hand into about forty |
| 9 | +# jobs, each carrying its own `node-version` literal and its own copy of the |
| 10 | +# store cache key, so a change to any of them was a forty-site edit and a |
| 11 | +# site that missed it drifted silently. `scripts/check-node-version.mjs` |
| 12 | +# reads `.github/actions/**` as well as the workflows, so the Node pin here is |
| 13 | +# audited like any other. |
| 14 | +# |
| 15 | +# The store is RESTORED on every run and SAVED only on `push` and `schedule`. |
| 16 | +# A pull request or merge-queue run that touches the lockfile used to post-save |
| 17 | +# a multi-GB store entry under its own key into the same 10 GB Actions cache |
| 18 | +# pool that holds `main`'s Turbo seeds, and those entries evicted the seeds. |
| 19 | +# `main`'s push and scheduled runs keep the store warm for everyone: a cache |
| 20 | +# entry saved on the default branch is restorable from every pull request and |
| 21 | +# queue branch, while one saved on a pull request is restorable only from that |
| 22 | +# pull request. ⛔ No `pull_request` or `merge_group` run saves the store. |
| 23 | +# |
| 24 | +# The save is a main-phase step right after the install, not `actions/cache`'s |
| 25 | +# post-job save: the store changes only during the install, and a composite |
| 26 | +# action has no post phase of its own. It is `continue-on-error` -- a store |
| 27 | +# that failed to upload says nothing about the code under test. |
| 28 | +# |
| 29 | +# Pnpm is acquired through `.github/actions/setup-pnpm` (the cached Corepack |
| 30 | +# store), called as a step here rather than inlined, because three callers |
| 31 | +# still use it directly and keep their own steps: |
| 32 | +# - ci.yml's five ui jobs (`ui-deps`, `type-check-ui`, `test-ui`, `lint-ui`, |
| 33 | +# `build-e2e-ui`), whose steps pins under `ui/scripts/__tests__/` read by |
| 34 | +# name and by text; |
| 35 | +# - governed-surface-guard.yml, whose toolchain steps are each gated and |
| 36 | +# each degrade on their own, pinned one by one by |
| 37 | +# `scripts/pm/check-governed-queue-guard.mjs --self-test`; |
| 38 | +# - release.yml's `release-integrity`, which installs the version commit's |
| 39 | +# tree, not the checkout. |
| 40 | +# |
| 41 | +# Inputs: |
| 42 | +# install 'false' stops after pnpm is on PATH: no store, no install. |
| 43 | +# For jobs that run pnpm but install nothing. |
| 44 | +# install-args appended to `pnpm install --frozen-lockfile`. |
| 45 | +# save-store 'false' never saves the store, on any event. For jobs that |
| 46 | +# deliberately restore only. |
| 47 | + |
| 48 | +name: Setup workspace |
| 49 | +description: >- |
| 50 | + Set up Node from .nvmrc and the repo-pinned pnpm, restore the pnpm store, |
| 51 | + run `pnpm install --frozen-lockfile`, and save the store on push and |
| 52 | + schedule runs only. |
| 53 | +
|
| 54 | +inputs: |
| 55 | + install: |
| 56 | + description: "'false' sets up Node and pnpm only, with no store cache and no install." |
| 57 | + required: false |
| 58 | + default: 'true' |
| 59 | + install-args: |
| 60 | + description: Extra arguments appended to `pnpm install --frozen-lockfile`. |
| 61 | + required: false |
| 62 | + default: '' |
| 63 | + save-store: |
| 64 | + description: "'false' never saves the pnpm store. Otherwise it is saved on push and schedule runs, on a miss of its exact key." |
| 65 | + required: false |
| 66 | + default: 'true' |
| 67 | + |
| 68 | +outputs: |
| 69 | + store-path: |
| 70 | + description: The pnpm store directory (empty when `install` is 'false'). |
| 71 | + value: ${{ steps.store.outputs.path }} |
| 72 | + store-cache-hit: |
| 73 | + description: "'true' when the store was restored from its exact key." |
| 74 | + value: ${{ steps.restore.outputs.cache-hit }} |
| 75 | + |
| 76 | +runs: |
| 77 | + using: composite |
| 78 | + steps: |
| 79 | + - name: Setup Node.js |
| 80 | + uses: actions/setup-node@v7 |
| 81 | + with: |
| 82 | + node-version-file: .nvmrc |
| 83 | + # pnpm is not on PATH yet, and a setup-node that caches for the |
| 84 | + # `packageManager` it reads shells out to it and dies here |
| 85 | + # (partof-closing-keyword-guard.yml carries the receipt). The store is |
| 86 | + # cached by the steps below instead. |
| 87 | + package-manager-cache: false |
| 88 | + |
| 89 | + - name: Setup pnpm |
| 90 | + uses: ./.github/actions/setup-pnpm |
| 91 | + |
| 92 | + - name: Get pnpm store directory |
| 93 | + id: store |
| 94 | + if: inputs.install != 'false' |
| 95 | + shell: bash |
| 96 | + run: | |
| 97 | + path="$(pnpm store path --silent)" |
| 98 | + echo "path=$path" >> "$GITHUB_OUTPUT" |
| 99 | + echo "STORE_PATH=$path" >> "$GITHUB_ENV" |
| 100 | +
|
| 101 | + # The one definition of the store cache key. `-v3-` and the lockfile hash |
| 102 | + # are unchanged from the hand-copied steps, so the entries `main` already |
| 103 | + # holds stay valid. |
| 104 | + - name: Restore pnpm store |
| 105 | + id: restore |
| 106 | + if: inputs.install != 'false' |
| 107 | + uses: actions/cache/restore@v6 |
| 108 | + with: |
| 109 | + path: ${{ steps.store.outputs.path }} |
| 110 | + key: ${{ runner.os }}-pnpm-store-v3-${{ hashFiles('**/pnpm-lock.yaml') }} |
| 111 | + restore-keys: | |
| 112 | + ${{ runner.os }}-pnpm-store-v3- |
| 113 | +
|
| 114 | + - name: Install dependencies |
| 115 | + if: inputs.install != 'false' |
| 116 | + shell: bash |
| 117 | + env: |
| 118 | + INSTALL_ARGS: ${{ inputs.install-args }} |
| 119 | + run: | |
| 120 | + # shellcheck disable=SC2086 # INSTALL_ARGS is a word list on purpose. |
| 121 | + pnpm install --frozen-lockfile $INSTALL_ARGS |
| 122 | +
|
| 123 | + - name: Save pnpm store |
| 124 | + if: >- |
| 125 | + inputs.install != 'false' |
| 126 | + && inputs.save-store != 'false' |
| 127 | + && (github.event_name == 'push' || github.event_name == 'schedule') |
| 128 | + && steps.restore.outputs.cache-hit != 'true' |
| 129 | + continue-on-error: true |
| 130 | + uses: actions/cache/save@v6 |
| 131 | + with: |
| 132 | + path: ${{ steps.store.outputs.path }} |
| 133 | + key: ${{ runner.os }}-pnpm-store-v3-${{ hashFiles('**/pnpm-lock.yaml') }} |
0 commit comments