ci: fix the node 20 warning and pin the protoc plugins (#84)

* ci: replace the archived buf-setup-action with buf-action

buf-setup-action is archived and runs on node20, which the runners now
warn about. buf-action is its supported replacement and runs on node24.
setup_only keeps it an install, since buf lint is its own step.

* ci: bump bun to 1.3.14

* ci: bump setup-chrome to v2.2.0

* ci: move to node 24 and drop the npm oidc workaround

node 22 is in maintenance and ships npm 10, which is why the publish job
had to install npm@latest over it. node 24 is the active lts and bundles
npm 11.17.0, above the 11.5.1 oidc floor, so the extra step goes.

* ci: pin the protoc plugins instead of installing @latest

these generate the committed stubs, so @latest makes codegen depend on
whatever released most recently. pinned to the versions proto/ records:
protoc-gen-go v1.36.11, protoc-gen-go-grpc v1.6.0.

* ci: run the ios leg on macos-26, pinned to a device and a runtime

macos-26 carries no iPhone 16 Pro at all, and on macos-15 that name spanned
iOS 18.5 through 26.2, so the leg could boot a two-major-old runtime. the
pair is now iPhone 17 Pro on iOS 26.2, resolved to a udid before boot, and
an image that drops it fails naming what it does carry.

iPhone 17 Pro is what examples/folio/justfile already defaulted to.

* ci: keep IOS_DEVICE a device name, not the resolved udid

the boot step exported the udid as IOS_DEVICE, and just ios spends that as
xcodebuild's -destination name=, which matches the display name and
rejected it: 'unable to find a device matching { name:6F69910C-... }'.

nothing downstream needed it. install, launch and terminate all address
booted, and sanderling resolves --ios-device against booted simulators
first, so the simulator this step boots is the one they all get.
This commit is contained in:
pj authored and GitHub committed 2026-08-16 22:57:38 +05:30
1 parent 4781d63ee1
commit 93c2d2ba74
5 files changed
+88 -29

No files matched your search

+1 -1
View File
@@ -8,7 +8,7 @@ runs:
# the action cannot move the browser these jobs drive. The alternative it # the action cannot move the browser these jobs drive. The alternative it
# offers is Chrome for Testing latest, which tracks ahead of the channel # offers is Chrome for Testing latest, which tracks ahead of the channel
# users run. # users run.
- uses: browser-actions/setup-chrome@2e1d749697dd1612b833dba4a722266286fbefcd # v2.1.2 - uses: browser-actions/setup-chrome@48ad923757ca74d66703209fe939badbdf80f2f4 # v2.2.0
with: with:
chrome-version: stable chrome-version: stable
+1 -1
View File
@@ -110,7 +110,7 @@ expect_argv "--seed" argv-ios
expect_argv "7" argv-ios expect_argv "7" argv-ios
expect_argv "240" argv-ios expect_argv "240" argv-ios
expect_argv "20m" argv-ios expect_argv "20m" argv-ios
expect_argv "iPhone 16 Pro" argv-ios expect_argv "iPhone 17 Pro" argv-ios
run argv-android android 2 <<TRACE run argv-android android 2 <<TRACE
$on_txn $on_txn
+1 -1
View File
@@ -102,7 +102,7 @@ case "$platform" in
# away from the `simctl uninstall` + `install` path that races FrontBoard # away from the `simctl uninstall` + `install` path that races FrontBoard
# ("app.folio is unknown to FrontBoard"), which needs an app path to reach. # ("app.folio is unknown to FrontBoard"), which needs an app path to reach.
folio_args+=(--platform ios folio_args+=(--platform ios
--ios-device "${IOS_DEVICE:-iPhone 16 Pro}") --ios-device "${IOS_DEVICE:-iPhone 17 Pro}")
;; ;;
web) web)
dist="examples/folio/app/webApp/build/dist/wasmJs/developmentExecutable" dist="examples/folio/app/webApp/build/dist/wasmJs/developmentExecutable"
+77 -26
View File
@@ -51,17 +51,17 @@ jobs:
- name: Set up Android SDK - name: Set up Android SDK
uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1 uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1
- name: Set up Node 22 - name: Set up Node 24
uses: actions/setup-node@v7 uses: actions/setup-node@v7
with: with:
node-version: "22" node-version: "24"
cache: npm cache: npm
cache-dependency-path: pkg/spec/package-lock.json cache-dependency-path: pkg/spec/package-lock.json
- name: Set up bun - name: Set up bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with: with:
bun-version: "1.3.13" bun-version: "1.3.14"
- name: Cache bun store - name: Cache bun store
uses: actions/cache@v6 uses: actions/cache@v6
@@ -74,18 +74,26 @@ jobs:
# The token is what stops this step flaking: without it the action pulls # The token is what stops this step flaking: without it the action pulls
# buf's release tarball from github.com anonymously, on the shared runner # buf's release tarball from github.com anonymously, on the shared runner
# IP's rate limit, and a throttled connection shows up as `socket hang # IP's rate limit, and a throttled connection shows up as `socket hang
# up` after three retries. The version is the action's own default, made # up` after three retries. The version is pinned explicitly so a new
# explicit so a new action release cannot move the buf we build with. # action release cannot move the buf we build with. `setup_only` is what
# keeps this a plain install: left off, the action runs its own lint,
# format and breaking checks, and `buf lint` below is where this repo
# says which rules it wants.
- name: Install buf - name: Install buf
uses: bufbuild/buf-setup-action@a47c93e0b1648d5651a065437926377d060baa99 # v1.50.0 uses: bufbuild/buf-action@8c6a16e16f12ba20b6470afa9c2ba9b5ba8c97c3 # v1.5.0
with: with:
version: "1.50.0" version: "1.72.0"
setup_only: true
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
# Pinned, not @latest: these two write the committed stubs, so a floating
# version is an unreviewed input to generated code. The versions are the
# ones the stubs under proto/ record generating them, so what CI builds
# with and what is checked in stay the same thing.
- name: Install protoc plugins - name: Install protoc plugins
run: | run: |
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.11
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.6.0
echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"
- name: Cache Gradle - name: Cache Gradle
@@ -198,7 +206,7 @@ jobs:
- name: Set up bun - name: Set up bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with: with:
bun-version: "1.3.13" bun-version: "1.3.14"
- name: Build the folio app - name: Build the folio app
uses: ./.github/actions/folio-app uses: ./.github/actions/folio-app
@@ -228,13 +236,14 @@ jobs:
folio-ios: folio-ios:
name: Folio (ios) name: Folio (ios)
runs-on: macos-15 runs-on: macos-26
timeout-minutes: 90 timeout-minutes: 90
env: env:
SEED: "7" SEED: "7"
MAX_STEPS: "240" MAX_STEPS: "240"
DURATION: 20m DURATION: 20m
IOS_DEVICE: iPhone 16 Pro IOS_DEVICE: iPhone 17 Pro
IOS_RUNTIME: iOS 26.2
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v7
@@ -247,7 +256,7 @@ jobs:
- name: Set up bun - name: Set up bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with: with:
bun-version: "1.3.13" bun-version: "1.3.14"
- name: Build the folio app - name: Build the folio app
uses: ./.github/actions/folio-app uses: ./.github/actions/folio-app
@@ -257,10 +266,53 @@ jobs:
- name: Build sanderling - name: Build sanderling
run: make sanderling-ios run: make sanderling-ios
# simctl resolves a device by name alone, and one image carries the same
# phone under several runtimes: iPhone 17 Pro exists here on iOS 26.2,
# 26.4 and 26.5. Booting by name is therefore booting on whichever one
# simctl happens to list first, and a seed only means something against a
# fixed runtime. So the pair is resolved to a UDID here and every step
# after this boots that one simulator. An image that stops carrying the
# pair fails here naming what it does carry, rather than as a
# `bootstatus` error to read backwards from.
#
# The UDID stays in this step. IOS_DEVICE has to keep holding the name,
# because `just ios` spends it as xcodebuild's `-destination name=`, which
# matches on the display name and rejects a UDID. Nothing downstream needs
# it anyway: the install, the launch and the terminate all address
# `booted`, and sanderling resolves --ios-device against booted simulators
# before available ones, so the one booted here is the one they all get.
- name: Boot a simulator - name: Boot a simulator
run: | run: |
xcrun simctl boot "$IOS_DEVICE" || true udid="$(python3 <<'PY'
xcrun simctl bootstatus "$IOS_DEVICE" -b import json, os, subprocess, sys
want_device = os.environ["IOS_DEVICE"]
want_runtime = os.environ["IOS_RUNTIME"]
devices = json.loads(subprocess.run(
["xcrun", "simctl", "list", "devices", "available", "--json"],
capture_output=True, text=True, check=True).stdout)["devices"]
def name_of(runtime):
family, _, version = runtime.rsplit(".", 1)[-1].partition("-")
return "%s %s" % (family, version.replace("-", "."))
for runtime, entries in devices.items():
if name_of(runtime) != want_runtime:
continue
for entry in entries:
if entry["name"] == want_device:
print(entry["udid"])
sys.exit(0)
carried = sorted({"%s on %s" % (e["name"], name_of(r))
for r, es in devices.items() for e in es})
sys.exit("no %r on %r in this image. it carries:\n %s"
% (want_device, want_runtime, "\n ".join(carried) or "no simulators at all"))
PY
)"
echo "booting $IOS_DEVICE on $IOS_RUNTIME ($udid)"
xcrun simctl boot "$udid"
xcrun simctl bootstatus "$udid" -b
- name: Build and install folio - name: Build and install folio
working-directory: examples/folio working-directory: examples/folio
@@ -302,7 +354,7 @@ jobs:
- name: Set up bun - name: Set up bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with: with:
bun-version: "1.3.13" bun-version: "1.3.14"
- name: Set up headless Chrome - name: Set up headless Chrome
uses: ./.github/actions/headless-chrome uses: ./.github/actions/headless-chrome
@@ -359,7 +411,7 @@ jobs:
- name: Set up bun - name: Set up bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with: with:
bun-version: "1.3.13" bun-version: "1.3.14"
- name: Set up headless Chrome - name: Set up headless Chrome
uses: ./.github/actions/headless-chrome uses: ./.github/actions/headless-chrome
@@ -517,21 +569,20 @@ jobs:
# this job needs the git credential afterwards. # this job needs the git credential afterwards.
persist-credentials: false persist-credentials: false
- name: Set up Node 22 # registry-url below writes an `_authToken=${NODE_AUTH_TOKEN}` line into
# .npmrc whether or not a token exists, and an npm older than 11.5.1 reads
# that empty line as "auth is configured" and never asks for an OIDC
# token, so the publish fails needing auth. 24 is the oldest Node whose
# bundled npm clears that floor (11.17.0), which is why it is pinned here
# rather than upgrading npm over the top of an older one.
- name: Set up Node 24
uses: actions/setup-node@v7 uses: actions/setup-node@v7
with: with:
node-version: "22" node-version: "24"
registry-url: "https://registry.npmjs.org" registry-url: "https://registry.npmjs.org"
cache: npm cache: npm
cache-dependency-path: pkg/spec/package-lock.json cache-dependency-path: pkg/spec/package-lock.json
# registry-url above writes an `_authToken=${NODE_AUTH_TOKEN}` line into
# .npmrc whether or not a token exists, and an npm older than 11.5.1 reads
# that empty line as "auth is configured" and never asks for an OIDC
# token, so the publish fails needing auth. Node 22 ships npm 10.
- name: Install an npm that can publish over OIDC
run: npm install -g npm@latest
- name: Install dependencies - name: Install dependencies
working-directory: pkg/spec working-directory: pkg/spec
run: npm ci run: npm ci
+8
View File
@@ -170,6 +170,14 @@ reinstall-and-launch cycles on iOS 26.1, 10 of them reinstalling on top of a
live app, so any fix for it has to be developed on a host that can still show it live app, so any fix for it has to be developed on a host that can still show it
failing. failing.
The leg names a device and a runtime, `iPhone 17 Pro` on `iOS 26.2`, and boots
by the UDID that pair resolves to. Both halves matter: one runner image carries
the same phone under several runtimes, so booting by name alone is booting on
whichever one `simctl` lists first, and a seed that is only calibrated against a
runtime it did not run on says nothing. A runner image that stops carrying the
pair fails the boot step naming what it does carry, which is the cue to pick a
new pair and recalibrate rather than a `bootstatus` error to read backwards.
Only one sanderling run may drive a given simulator at a time. The driver takes Only one sanderling run may drive a given simulator at a time. The driver takes
an advisory lock on the target's UDID and a second run is refused with the lock an advisory lock on the target's UDID and a second run is refused with the lock
path in the message, because two runs interleaving app lifecycle leave the first path in the message, because two runs interleaving app lifecycle leave the first