Skip to content

fix: make the runtime work on Firefox (V8 stack shim + dev Service Wo… #189

fix: make the runtime work on Firefox (V8 stack shim + dev Service Wo…

fix: make the runtime work on Firefox (V8 stack shim + dev Service Wo… #189

Workflow file for this run

name: CI
# Three tiers, cheapest first (mirrors the retired GitLab CI):
#
# gate - offline, Wasm-free guards (toolchain subsystem + llhttp Wasm HTTP
# parser). Runs on every push/PR; seconds; catches silent regressions
# such as esbuild-wasm drift or a broken native->wasm alias table.
# verify - the hermetic offline suite (scripts/verify-node.mjs). Needs the Wasm
# VFS/codec/crypto built for Node; runs on PRs + the default branch.
# pm-gate - the package-manager North Star (real npm/yarn/pnpm/corepack in-VM).
# Network + the Wasm VFS/codec/crypto, so it's manual/scheduled like
# the spikes tier — but it FAILS the run, unlike spikes-net.
# spikes - the network tier: boots real templates and installs from the live
# npm registry in-VM. Slow + registry-dependent, so it's manual/scheduled.
on:
push:
branches: [master]
pull_request:
schedule:
# Nightly network spikes.
- cron: "0 3 * * *"
workflow_dispatch:
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
# --- fast gate: offline, no Wasm, every push/PR ----------------------------
toolchain-gate:
if: github.event_name != 'schedule'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
- run: node scripts/run-spikes.mjs --offline
# The notebook's view, rendered under jsdom. It is `net` only because it
# installs React, a DOM and a bundler off the registry — it runs no
# container, needs no Wasm and touches no network of its own, so it belongs
# in the PR gate and not in spikes-net, which is schedule-only and
# `continue-on-error: true` and therefore gates nothing. That distinction is
# not academic here: the notebook shipped with every cell editor dead, and
# this is the only job that would have gone red for it.
- name: the notebook view mounts
run: node scripts/run-spikes.mjs --net notebook-view
# The static probes. Both are Wasm-free and take under a second, and
# neither was wired into any job before — probe:node-registry shipped as
# the guard against unresolvable builtin ids and had never actually run in
# CI, which is the same "written but never executed" gap the spike table
# had.
- run: npm run probe:node-registry
- run: npm run probe:http-egress
# The browser egress header policy. Pure logic on purpose: Node has no CORS
# and the headless fetchers forward every header, so no spike can see a
# regression here — a stripped Authorization header shows up as a request
# that quietly succeeds anonymously.
- run: npm run probe:egress-headers
- run: npm run probe:terminal-feedback
# The cookie jar's RFC 6265 subset. Pure logic on purpose: a path rule that
# is slightly too generous hands one route's session to another, and neither
# that nor an over-eager expiry shows up as an error anywhere.
- run: npm run probe:cookie-jar
# --- the same gate, on the OLDEST Node package.json admits -----------------
# Every other job pins `node-version: 22`, and `.nvmrc` says `22`. Both resolve
# to the newest 22.x, so before this job existed NO MACHINE ANYWHERE ran the
# regime `engines.node` promises to support — not CI, not a contributor using
# nvm. That is not a hypothetical gap: `npm run dev` shipped broken on
# 22.0-22.17 twice, behind three rounds of green gates, because Node only
# strips TypeScript types unprompted from 22.18 and every runner had it.
#
# Pinned to the exact floor rather than a range, so that raising `engines.node`
# without raising this line leaves an untested version admitted, and lowering
# it without lowering this line is a promise nothing checks. The two are meant
# to be edited together; scripts/spike-install-latency.mjs asserts they match.
#
# Cheap by construction: it is the Wasm-free offline tier, the same one
# `toolchain-gate` runs, so it costs a second checkout and about a minute.
# Without the crates, run-spikes skips the `needsWasm` spikes — which is what
# keeps this job honest about Node versions rather than about OpenSSL: the
# `constants` spike compares the host's bundled OpenSSL and zlib against the
# VM's and legitimately differs on any Node but the newest.
toolchain-floor:
if: github.event_name != 'schedule'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
# Keep in step with `engines.node`.
node-version: "22.15.0"
# Named separately from the tier below so that the failure this job exists
# to catch reads as itself. Thirteen scripts import the studio's templates
# out of a .ts file and two of them run on every `npm run dev`; when that
# broke, the generators failed soft and the only visible symptom was a dev
# server that was quietly slow.
- name: the studio templates must import on the floor, not only on latest
run: |
node --input-type=module -e "
const { loadShippedTemplates } = await import('./scripts/lib/shipped-templates.mjs');
const t = await loadShippedTemplates();
if (!Array.isArray(t) || t.length === 0) throw new Error('loaded no templates');
console.log('loaded ' + t.length + ' templates on Node ' + process.versions.node);
"
- run: node scripts/run-spikes.mjs --offline
# The corpus gate that installs each shipped lock in the VM lives in
# `template-gate`, which is schedule/dispatch only because it needs the Rust
# toolchain and an hour. That leaves merges unguarded against the change most
# likely to poison a lock: an edit to templates.ts, to the generator, or to
# the kernel's alias table. RESOLVING all of them needs none of that — no
# Wasm, no VM, ~35s — and resolution is where both guards run, so a template
# that starts pulling in an aliased native is refused here rather than on the
# deploy that ships it. It is not the in-VM gate and does not replace it; it
# is the part of that gate which is cheap enough to block a merge.
#
# `--strict`, because producing them IS this job's subject. The cost is that a
# registry outage reddens a PR — the same trade `template-gate` already makes
# for the same command, against the alternative of hearing it from a nightly.
lock-resolution-gate:
if: github.event_name != 'schedule'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
- name: every shipped lockfile must still resolve, and pass both guards
run: npm run vendor:locks -- --strict
# SDK packaging + public-API contract guard (offline, no Wasm, no browser).
# Also regression-checks the shipped preview sw.js branding + examples/basic.
sdk-smoke:
if: github.event_name != 'schedule'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
- run: npm run smoke
# --- offline hermetic suite (needs the Rust->Wasm build) -------------------
verify:
if: github.event_name == 'push' || github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown, wasm32-wasip1
- uses: jetli/wasm-pack-action@v0.4.0
with:
version: v0.13.1
- name: Cache cargo
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
packages/*/target
key: cargo-${{ runner.os }}-${{ hashFiles('packages/*/Cargo.lock') }}
- name: Build Wasm crates for Node
run: npm run build:vfs:node && npm run build:codec:node && npm run build:crypto:node && npm run build:wasi-demo
# These are offline but need the Rust/Wasm VFS, so they can't run in the
# Wasm-free toolchain-gate (run-spikes.mjs would silently skip them); run them
# here, after the crates are built above. The `bun` filter is a substring
# match, so it also re-runs the Wasm-free bun-offline spike — cheap, and it
# means a Bun change is never merged on a green run that skipped Bun.
#
# EVERY `needsWasm` offline spike belongs in this list. One that is registered
# but missing here runs nowhere at all: skipped in toolchain-gate for want of
# the crates, and never selected in the only job that has them.
- name: offline spikes that need the Wasm VFS
run: node scripts/run-spikes.mjs --offline dep-cache depcache-shipped seed-swc bun http-binary-body http-response-bytes cookie-session fs-cp fs-errors large-fs-payloads worker-pool node-cli repl stack-traces readline watch child-stdin fs-metadata crypto-jwk zlib-brotli port-liveness constants diag-liveness fatal-errors net-close-order net-blocklist
- run: npm run verify
# --- package-manager gate: the North Star, gated on its own ----------------
# Running the real npm/yarn/pnpm/corepack CLIs in-VM is the headline capability,
# so it gets a job that can actually go red. It is deliberately NOT part of
# spikes-net: that job is `continue-on-error: true` (registry flakiness across
# 40+ template installs would make it noise), which means a broken package
# manager would show up as a green run there.
#
# Not on push/PR either: each PM spike vendors a real CLI and runs a real
# install from the registry, so the eight of them cost far more than the PR
# budget allows. Making this PR-blocking needs the vendor assets cached or
# committed first — see scripts/run-spikes.mjs VENDORS.
pm-gate:
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- uses: jetli/wasm-pack-action@v0.4.0
with:
version: v0.13.1
- name: Cache cargo
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
packages/*/target
key: cargo-${{ runner.os }}-${{ hashFiles('packages/*/Cargo.lock') }}
# All three crates, not just the VFS. A package manager is a crypto and
# compression workload before it is a filesystem one: npm verifies every
# tarball against its sha512 integrity, and only md5/sha1/sha256 have a JS
# fallback, so without the crypto crate `npm install` dies on the first
# dependency with FETCH_ERROR "digest 'sha512' needs the wasm codec". The
# codec crate is just as load-bearing — a registry tarball is a .tgz, and
# without zlib the extract fails with "zlib wasm codec is not available".
- name: Build the Node Wasm crates
run: npm run build:vfs:node && npm run build:codec:node && npm run build:crypto:node
# Provisions its own scratch dirs + studio delivery assets (see VENDORS);
# a provisioning failure is reported as a FAIL, not skipped.
- run: npm run spikes:pm
# --- framework-template gate: the shipped templates, on a job that can go red -
# Same argument as pm-gate, and it has now been paid for. `vite: ^8.0.0` floated
# onto rolldown 1.2.2, which dropped @rolldown/binding-wasm32-wasi from its
# optionalDependencies — so nothing installed the wasm binding any more, and the
# dev server in every Vite 8 template stopped starting. FOUR spikes went red and
# said so for as long as it took anyone to look, which was never: spikes-net is
# `continue-on-error: true`, so its run is green whatever happens inside it.
#
# These six are separable from that tolerance on their own merits. They are fast
# (11-16s each, versus the 40+ installs that made the tier noisy), they install one
# small tree apiece, and a failure here is not registry weather — it is the
# product's headline surface not starting. Registry drift is exactly what it is
# supposed to catch, so tolerating it defeats the job.
#
# Scheduled/dispatch rather than per-push for the reason pm-gate gives: the shared
# harness vendors a real npm CLI, and that provisioning is not cached yet.
template-gate:
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- uses: jetli/wasm-pack-action@v0.4.0
with:
version: v0.13.1
- name: Cache cargo
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
packages/*/target
key: cargo-${{ runner.os }}-${{ hashFiles('packages/*/Cargo.lock') }}
# All three crates, for the reason pm-gate spells out: a real install verifies
# sha512 integrity and extracts .tgz tarballs.
- name: Build the Node Wasm crates
run: npm run build:vfs:node && npm run build:codec:node && npm run build:crypto:node
# `react` was missing from this list until the template most users start from
# turned out to be the one nothing ran. Each of these now boots its server the
# way the studio does — through the manifest's own dev command — rather than by
# calling the vite bin directly, which is the shortcut that let a broken shipped
# path sit behind six green spikes.
#
# Anchored (`name$`) because the filters are a substring match by default, and
# a bare `lit` also selects sqlite and pglite: this step was running two in-VM
# database spikes it does not name, on a job whose failures are supposed to mean
# "a shipped template stopped booting". Nine ran where seven were asked for.
# There is deliberately no `vendor:locks` step here. It used to be one, and
# a step is the wrong place for it: `spikes-net` runs the same lock-reading
# spikes with no such step, so `vendor/locks/` was empty there and
# `template-locks` died in 0.2s on the first missing file while the eight
# template spikes silently installed from RANGES — two jobs exercising
# different code paths for the same template, which is the one thing a
# second job must not do. Provisioning now belongs to the spikes that need
# it (`VENDORS.locks` in scripts/run-spikes.mjs), so every job that selects
# one gets the locks, and a job added tomorrow cannot forget.
# Every id in COVERAGE, installed in the VM from the lock the runner
# provisions for it. This is the gate that made COVERAGE forty templates
# instead of one: a spike per template would have been ~50 near-identical
# files, so this is one spike driven over the list. It installs and checks
# the tree is usable rather than booting a server — the step below is what
# boots servers, for the eight frameworks it names.
- name: every shipped lockfile must install into a usable tree
run: node scripts/run-spikes.mjs --net template-locks$
- name: framework templates must start their dev server
run: node scripts/run-spikes.mjs --net react$ preact$ lit$ solid$ vue$ svelte$ qwik$ ember$
# The two the step above used to pick up by accident, kept blocking on purpose.
# Anchoring `lit` would otherwise have moved them to spikes-net alone, which is
# `continue-on-error: true` — so a broken in-VM database would have stopped being
# able to turn the workflow red. They earn a step here on the same terms as the
# templates: a WASM SQL engine running in the browser is a headline capability,
# and at ~10s for the pair they are cheaper than anything else in this job. Their
# own step, because a failure here means something different from a template that
# will not boot — which was the whole problem with them arriving via `lit`.
- name: the in-VM databases must work
run: node scripts/run-spikes.mjs --net sqlite$ pglite$
# Nest's dev command is a watch supervisor rather than a dev server, so its gate
# is that a SAVE reaches the browser: boot, edit, recompile, restart, new
# response. Separate step because a failure here means something different.
- name: the Nest template must survive a save
run: node scripts/run-spikes.mjs --net nest-watch
# Its own step rather than an extra filter above, because it proves something
# different: that an interactive scaffolder can be ANSWERED, not that a template
# serves. Needs the vendored npm, hence the net tier.
- name: an interactive scaffolder must be answerable
run: node scripts/run-spikes.mjs --net scaffolder
# --- network tier: real template installs from the live registry -----------
# Manual (workflow_dispatch) or scheduled: slow and depends on
# registry.npmjs.org being reachable.
spikes-net:
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 120
continue-on-error: true
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- uses: jetli/wasm-pack-action@v0.4.0
with:
version: v0.13.1
- name: Cache cargo
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
packages/*/target
key: cargo-${{ runner.os }}-${{ hashFiles('packages/*/Cargo.lock') }}
- name: Build Wasm crates for Node
run: npm run build:vfs:node && npm run build:codec:node && npm run build:crypto:node
- run: npm run spikes:net