All checks were successful
check / check (push) Successful in 6s
Build and Deploy to Cloudflare Pages / build (push) Successful in 7s
probe / s1-build (push) Successful in 13s
Build and Deploy to Cloudflare Pages / deploy (push) Has been skipped
probe / s2-deploy-dryrun (push) Successful in 10s
Round 3 (07af755) cleared the artifact path and left one failure:
check / check success 8s
Build and Deploy .../ build success 8s <- green
Build and Deploy .../ deploy skipped <- if: guard
probe / r1-wrangler-only failure 7s
probe / r2a-upload-proven success 12s
probe / r2b-download-proven success 2s
probe / r3a-upload-node20 success 8s
probe / r3b-download-node20 success 2s
r2a/r2b and r3a/r3b upload and download the real site tarball across the two
job containers, so the artifact round trip is sound. r1 does nothing but
install wrangler and invoke it, and it fails.
Reproduced locally in the pinned node image, which is faster than another CI
round:
$ docker run --rm node@sha256:8f693eaa... sh -c \
'npm install -g wrangler@4.120.0; wrangler --version'
install exit=0 (with EBADENGINE warnings)
Wrangler requires at least Node.js v22.0.0. You are using v20.20.2.
version exit=1
npm treats engines as a warning on an explicit version, so the install step
would have passed and the deploy step would have failed -- a second break,
independent of the artifact one, in the same job nobody could run.
The instructive part is what the unpinned command it replaced was doing:
$ docker run --rm node@sha256:8f693eaa... sh -c \
'npm install -g wrangler; wrangler --version'
`-- wrangler@4.86.0
4.86.0
npm resolves a bare name to the newest version whose engines the running node
satisfies, so `npm install -g wrangler` on node 20 has been installing 4.86.0,
not the 4.120.0 that `latest` points at. Pinning 4.120.0 was therefore not
"pin the version we are already getting", it was an unnoticed major-ish bump
onto a node the container does not have.
So this pins wrangler 4.86.0 (engines: node >= 20.3.0, published 2026-04-28),
which is exactly the version that has been deploying this site, verified to
install and run on the pinned node 20 digest. The node image digest is left
alone. Bumping the container to node 22 to keep 4.120.0 is the alternative,
but that changes the deploy runtime for no benefit this issue asks for.
Round 4 replaces the probe jobs with a single end-to-end rehearsal: the build
job as written, then the deploy job as written with `wrangler pages deploy
--help` in place of the publish call.
116 lines
5.4 KiB
YAML
116 lines
5.4 KiB
YAML
name: Build and Deploy to Cloudflare Pages
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
# TEMPORARY: development-only trigger so the build job actually
|
|
# executes under act_runner before this reaches main. Removed in
|
|
# the final commit.
|
|
- pin-deploy-refs-observable
|
|
|
|
jobs:
|
|
build:
|
|
runs-on: ubuntu-latest
|
|
container:
|
|
# Same digest the Dockerfile pins: one pinned base image and the
|
|
# same dependency list (script/bootstrap) for both the check build
|
|
# and the deploy build. The one extra thing this job needs on top
|
|
# of the Dockerfile is the Actions runner's own prerequisites --
|
|
# see the first step.
|
|
# alpine 3.21, 2026-02-28
|
|
image: alpine@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709
|
|
defaults:
|
|
run:
|
|
# The default step shell is bash; this image has only busybox
|
|
# sh, so say so explicitly rather than rely on a fallback.
|
|
shell: sh
|
|
steps:
|
|
# This image is bare busybox+musl. act_runner executes JavaScript
|
|
# actions (checkout, upload-artifact) with `node` *inside* the job
|
|
# container and does not inject one, so node has to exist before
|
|
# the first `uses:` step -- script/bootstrap runs too late. git is
|
|
# needed for checkout's `submodules: recursive` (without it
|
|
# checkout degrades to a tarball download that cannot do
|
|
# submodules). An inline `run:` needs only a shell, so this step
|
|
# works on the bare image. These apk packages resolve at run time
|
|
# and are not hash-pinned; that gap is repo-wide (script/bootstrap
|
|
# has it too) and is tracked in #19.
|
|
- name: Install runner prerequisites
|
|
run: apk add --no-cache nodejs git tar
|
|
|
|
- name: Checkout
|
|
# actions/checkout v4.2.2, 2026-02-28
|
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
|
with:
|
|
submodules: recursive
|
|
|
|
- name: Install build dependencies
|
|
run: script/bootstrap
|
|
|
|
- name: Build site
|
|
run: script/test
|
|
|
|
- name: Archive site
|
|
run: tar -czf site.tar.gz public
|
|
|
|
# v4 does not work on this Gitea Actions instance -- it is what
|
|
# broke the deploy in run 25. Measured on this branch: a job
|
|
# identical to this one but ending in upload-artifact v4 fails,
|
|
# while the same job without that step passes. So this stays on
|
|
# the v3 line, pinned, using the node20 build of it rather than
|
|
# here; tracked separately. This is the exact commit the mutable
|
|
# `@v3` used to resolve to, i.e. the code that was deploying this
|
|
# site before this issue -- now pinned instead of floating.
|
|
- name: Upload artifact
|
|
# actions/upload-artifact v3.2.1, 2026-08-09
|
|
uses: actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5
|
|
with:
|
|
name: site
|
|
path: site.tar.gz
|
|
|
|
deploy:
|
|
runs-on: ubuntu-latest
|
|
needs: build
|
|
# Publishing guard. This job spends CLOUDFLARE_API_TOKEN and creates a
|
|
# real Cloudflare Pages deployment, so it must never run off main --
|
|
# not even if a branch is added to the push trigger above, deliberately
|
|
# or by accident. Costs one line; the build job stays exercisable from
|
|
# a branch without this job touching anything external.
|
|
if: github.ref_name == 'main'
|
|
container:
|
|
# node 20.20.2-bookworm, 2026-08-09
|
|
image: node@sha256:8f693eaa7e0a8e71560c9a82b55fd54c2ae920a2ba5d2cde28bac7d1c01c9ba5
|
|
steps:
|
|
# Must match the upload-artifact major above -- v4 artifacts and
|
|
# v3 artifacts are different protocols and do not interoperate.
|
|
# Like the upload above, this is the exact commit `@v3` used to
|
|
# resolve to.
|
|
- name: Download artifact
|
|
# actions/download-artifact v3.0.2, 2026-08-09
|
|
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a
|
|
with:
|
|
name: site
|
|
|
|
- name: Extract site
|
|
run: tar -xzf site.tar.gz
|
|
|
|
# 4.86.0, not the 4.120.0 that `latest` points at: wrangler
|
|
# 4.120.0 requires node >= 22 and refuses to start on this
|
|
# container's node 20. Note that the unpinned `npm install -g
|
|
# wrangler` this replaces was never installing `latest` either --
|
|
# npm picks the newest version whose engines the running node
|
|
# satisfies, which on node 20 is exactly 4.86.0. So this pins the
|
|
# version that has actually been deploying this site, rather than
|
|
# silently changing it. Bumping the container to node 22 is the
|
|
# alternative; it is a bigger change and is not what this issue is
|
|
# for.
|
|
- name: Install Wrangler
|
|
# wrangler 4.86.0, 2026-08-09
|
|
run: npm install -g wrangler@4.86.0
|
|
|
|
- name: Deploy to Cloudflare Pages
|
|
run: wrangler pages deploy public --project-name=lora-vegas --branch=${{ github.ref_name }}
|
|
env:
|
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|