The release process (build-release.mjs) creates an immutable artifact containing worker bundles, static assets, and a manifest describing the whole release; the manifest is written last to mark completion. Release IDs use CI pipeline numbers to ensure a monotonic sequence that protects against concurrent promotion and guards against obsolete releases being promoted. A break-glass command named Bonk is defined in .github/workflows/bonk-pr.yml and can be triggered on pull requests to bypass normal workflow gates in emergency situations.
The release build script at scripts/release/build-release.mjs is invoked as node scripts/release/build-release.mjs --out <dir> [--release-id <id>], where --out is required and --release-id is optional (auto-generated when omitted).[1] scripts/release/build-release.mjs produces an immutable release consisting of worker bundles (via wrangler deploy --dry-run --outdir), static assets (Access-mode frontend build), and a manifest.json that describes everything; the manifest is written last because its presence marks the release as complete.[1] The script builds the frontend first because workshop-router's wrangler.jsonc points its assets directory at workshop-frontend/dist, which must exist before the router's dry-run bundle step.[1] Only the Access-mode frontend variant is built (VITE_CF_ACCESS_MODE=true), making it the one asset variant every release carries; the flag is a build-time switch in workshop-frontend/src/useAuth.ts.[1] scripts/release/build-release.mjs uses killProcessTree (defined in scripts/kill-process-tree.ts, tested in scripts/kill-process-tree.test.ts) to cleanly tear down child processes when a concurrent build fails. scripts/release/build-release.mjs uses a mapConcurrent utility (defined in scripts/map-concurrent.ts, tested in scripts/map-concurrent.test.ts) to run worker deploy builds concurrently. Preview environment gatekeepers receive OAuth application credentials automatically at deploy time via env-passthrough into scripts/preview/staging-config.ts, enabling OAuth-dependent flows (e.g. Google, GitHub gatekeeper sign-in) that would otherwise silently fail. scripts/release/manifest-lib.ts validates and normalizes shortName values at build time, enforcing legal install-slug character-set and length constraints so violations are caught before the installer stage.
The release ID uses CI_PIPELINE_IID (per-project monotonic) rather than CI_PIPELINE_ID (instance-global), because run numbers are compared by promote-release.mjs's supersededBy() guard and must form a single monotonic sequence from one publisher.[1] The release commit SHA is read from the CI_COMMIT_SHA environment variable in CI; locally it falls back to git rev-parse HEAD.[1] Worker module blobs in the release output are content-addressed by SHA-256 (<out>/modules/<sha256>), and static asset blobs are content-addressed by Cloudflare hash (<out>/assets/<cfHash>).[1] The build script reads the pinned wrangler version from node_modules/wrangler/package.json and records it in the manifest, so every release artifact knows which wrangler version produced it.[1]
Concurrent promotion runs are not safe against the shared release copy; CI serializes them with a GitLab resource group, and the promote script's supersededBy() newer-release guard skips already-superseded candidates.[2]
Sources