Files
improvise/.github/workflows/ci.yml
T
2026-09-20 13:50:42 -07:00

305 lines
13 KiB
YAML

name: CI
on:
# `push` unrestricted plus `pull_request` ran every job TWICE on any branch
# with an open PR — visible as the (push) / (pull_request) pairs in the checks
# list. Restricting push to main means a PR branch is covered once by
# pull_request, and main once by push, which is also what pages-deploy needs.
push:
branches: [main]
pull_request:
env:
CARGO_TERM_COLOR: always
jobs:
test:
name: Test and lint (host)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt
- uses: Swatinem/rust-cache@v2
- run: cargo fmt --all -- --check
- run: cargo clippy --workspace --all-targets -- -D warnings
- run: cargo test --workspace
# The comment cap and the test-evidence keys (improvise-9c8c); without
# this step the rule is advisory. See context/design-principles.md §6.
- name: The comment checker catches what it has been fixed for
run: python3 scripts/check-comment-runs.py --self-test
- name: Comment runs capped at two lines
run: python3 scripts/check-comment-runs.py
- name: Test-evidence headings name real tests
run: python3 scripts/check-comment-runs.py --check-evidence
# Too slow on GitHub Actions
#
# nix-test:
# name: Nix Test
# runs-on: ubuntu-latest
# steps:
# - name: Free up disk space
# run: |
# sudo rm -rf /usr/share/dotnet \
# /usr/local/lib/android
# sudo df -h
# - uses: actions/checkout@v7
# - name: Install Nix
# uses: DeterminateSystems/nix-installer-action@v14
# - name: Magic Nix Cache
# uses: DeterminateSystems/magic-nix-cache-action@v8
# - name: Check Nix health
# run: nix doctor
# - run: nix build
wasm:
name: wasm32 check (browser bundle crates)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- uses: Swatinem/rust-cache@v2
# Only the crates that go into the browser bundle. The root `improvise`
# crate is the terminal binary and depends on crossterm, which does not
# build for wasm — that is correct, not a gap (improvise-ywd).
- name: improvise-core
run: cargo check -p improvise-core --target wasm32-unknown-unknown
- name: improvise-formula
run: cargo check -p improvise-formula --target wasm32-unknown-unknown
- name: improvise-protocol
run: cargo check -p improvise-protocol --target wasm32-unknown-unknown
- name: improvise-io
run: cargo check -p improvise-io --target wasm32-unknown-unknown
- name: improvise-command
run: cargo check -p improvise-command --target wasm32-unknown-unknown
# The browser client itself. This is the one crate for which a wasm
# build is the *point* rather than a property worth keeping, so a
# failure here is not a portability regression but a broken deliverable
# (improvise-gsw).
- name: improvise-wasm-client
run: cargo check -p improvise-wasm-client --target wasm32-unknown-unknown
# The worker half of the standalone deployment (improvise-9x6). Same
# terms as the client: a wasm build is the point, so a failure here is a
# broken deliverable rather than a portability regression.
- name: improvise-worker-server
run: cargo check -p improvise-worker-server --target wasm32-unknown-unknown
# Not in the browser bundle yet — nothing depends on improvise-gp. Gated
# anyway because it is a candidate (fitting a tree in the browser) and
# staying wasm-clean is free now, expensive to retrofit (improvise-z5eg).
- name: improvise-gp
run: cargo check -p improvise-gp --target wasm32-unknown-unknown
# ---------------------------------------------------------------------------
# The browser JavaScript's unit tests. Its own job, deliberately: a failure
# here is a decision `web/shell.js` or `web/serial.js` got wrong, and
# reporting that as "Browser harness" would put it behind an 80 MB browser
# download and two wasm-pack builds. It needs neither — `node --test` is in
# the runtime, and this job installs nothing at all.
js:
name: Browser JS unit tests (node --test)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v4
with:
node-version: 22
# The glob is the SHELL's, not node's: `web/tests/bundle/` needs a built
# wasm bundle and runs in the browser job instead.
- name: What the page decides between its boundaries
run: node --test web/tests/*.test.js
kb:
name: Knowledge base consistency
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: cachix/install-nix-action@v31
# kb.pl is executable knowledge, so it gets checked like code: rules
# must reject what they should, domains must close, and functional keys
# must not carry conflicting values. See kb_report/0 in the file.
- name: kb_consistent
run: |
nix shell nixpkgs#trealla --command tpl prolog-sessions/kb.pl \
-g "(kb_consistent -> halt(0) ; (kb_report, halt(1)))"
# ---------------------------------------------------------------------------
# The browser harness (improvise-vvl3, absorbing improvise-xf1).
#
# This is the only job that RUNS any of the browser half. `OpfsFs`,
# `web/app.js` and `web/worker.js` were previously compile-checked,
# syntax-checked and read — never executed — so every bug in them was found
# by reading or by a person pressing keys, neither of which is repeatable.
#
# It can fail the build, deliberately: a harness nobody runs is worse than
# none, because it reads as coverage. Playwright's browser download is ~80 MB
# and takes well under a minute on ubuntu-latest, which is affordable next to
# the two wasm-pack builds this job already needs.
browser:
name: Browser harness (OPFS contract + shell)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
# Named rather than assumed: this job lints harness/opfs-contract,
# which the `test` job's workspace-wide fmt and clippy cannot reach.
components: clippy, rustfmt
- uses: Swatinem/rust-cache@v2
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Install wasm-pack
env:
WASM_PACK_VERSION: v0.15.0
run: |
set -eu
url="https://github.com/rustwasm/wasm-pack/releases/download/${WASM_PACK_VERSION}/wasm-pack-${WASM_PACK_VERSION}-x86_64-unknown-linux-musl.tar.gz"
curl -sSfL "$url" | tar -xz --strip-components=1 -C /usr/local/bin \
"wasm-pack-${WASM_PACK_VERSION}-x86_64-unknown-linux-musl/wasm-pack"
wasm-pack --version
# Not a workspace member (it only builds for wasm32), so the `test` job's
# workspace-wide fmt and clippy do not reach it.
- name: Lint the harness crate
working-directory: harness/opfs-contract
run: |
cargo fmt --all -- --check
cargo clippy --target wasm32-unknown-unknown --all-targets -- -D warnings
- name: Install the harness
working-directory: harness
run: npm ci
# All three, because improvise-xf1's acceptance criterion names Chrome,
# Firefox and Safari and `createSyncAccessHandle` is implemented
# separately by each engine. WebKit is installed even though its OPFS is
# absent today: the spec skips on a runtime probe, so the day that build
# gains OPFS the check starts running without a CI change.
- name: Install browsers
working-directory: harness
run: npx playwright install --with-deps chromium firefox webkit
# Builds both site bundles, the harness's own wasm bundle, and assembles
# `harness/serve` — the same tree `pages-build` publishes, plus /opfs/.
- name: Build the harness
run: ./harness/build.sh
# The JS↔wasm boundary: every call web/app.js makes is declared by the
# bundle, with that arity. It needs the bundle and nothing else, so it
# runs here rather than in the `js` job, and before Playwright — a
# renamed export is cheaper to read here than as a blank page.
- name: The page's calls match the bundle it calls
run: node --test web/tests/bundle/*.test.js
- name: Run the harness
working-directory: harness
run: npx playwright test
- if: failure()
uses: actions/upload-artifact@v7
with:
name: playwright-traces
path: harness/test-results
retention-days: 7
# ---------------------------------------------------------------------------
# GitHub Pages delivery (improvise-d31).
#
# Two jobs on purpose. `pages-build` runs on every push and pull request, so
# a change that breaks `web/build.sh` or the bundle fails on the PR that made
# it — the build is a gate. `pages-deploy` runs only on a push to main and
# `needs` the check jobs above, so nothing that failed `cargo test`, the
# wasm32 gate or the browser harness can reach the published site. The deploy
# depends on those checks rather than repeating them (improvise-d31 AC 2).
#
# Not path-filtered, deliberately. The bundles are `improvise-wasm-client` and
# `improvise-worker-server` plus, between them, most of the workspace — the
# worker bundle is the whole App: core, formula, io, protocol and command. A
# filter
# that meant "skip when the bundle cannot have changed" would have to name
# most of the workspace, and the failure mode of getting it wrong is a stale
# site with no signal that it is stale. A redundant build is the cheaper error.
pages-build:
name: Browser bundle + static site
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- uses: Swatinem/rust-cache@v2
# Pinned release binary rather than `cargo install` (a three-minute
# source build) or a third-party action. wasm-pack runs its own bundled
# `wasm-opt`, which is why improvise-wasm-client's Cargo.toml names
# `--enable-bulk-memory`: without it the optimizer rejects the
# `memory.copy` LLVM emits regardless.
# v0.15.0 is what `nix develop` supplies, and the bundle was built and
# measured against it locally (346 KB, 120 KB gzipped). Pinning the same
# version is what makes that local run evidence about this one.
- name: Install wasm-pack
env:
WASM_PACK_VERSION: v0.15.0
run: |
set -eu
url="https://github.com/rustwasm/wasm-pack/releases/download/${WASM_PACK_VERSION}/wasm-pack-${WASM_PACK_VERSION}-x86_64-unknown-linux-musl.tar.gz"
curl -sSfL "$url" | tar -xz --strip-components=1 -C /usr/local/bin \
"wasm-pack-${WASM_PACK_VERSION}-x86_64-unknown-linux-musl/wasm-pack"
wasm-pack --version
# The same script a developer runs locally, so there is one answer to
# "how is the bundle built" rather than two that can drift.
- name: Build the wasm bundle
run: ./web/build.sh
# assemble.sh generates the prose site with node. The runner image ships
# one, but pinning it here is what makes a local run evidence about this.
- uses: actions/setup-node@v4
with:
node-version: "24"
# `web/assemble.sh`, not an inline copy of it: the browser harness job
# below assembles the same tree, and a harness that runs against a
# different arrangement of files is testing something nobody deploys.
- name: Assemble the site
run: ./web/assemble.sh site "$GITHUB_SHA"
- run: find site -type f | sort
- run: du -sh site
- uses: actions/upload-pages-artifact@v5
with:
path: site
pages-deploy:
name: Deploy to GitHub Pages
needs: [test, wasm, js, browser, pages-build]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: read
pages: write
id-token: write
# A superseded deploy is allowed to finish rather than being cancelled:
# `deploy-pages` cancelled midway can leave the Pages deployment in a
# half-applied state, and the next push republishes anyway.
concurrency:
group: pages
cancel-in-progress: false
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
# `enablement: false` (the default) configures the environment URL
# without attempting to create or modify the Pages site via the API.
# GitHub Pages must be enabled once in the repository settings
# (Settings → Pages → Source: GitHub Actions) before this job will
# produce a live URL. Using `enablement: true` fails when the workflow
# token lacks admin permission to create the site (improvise-3gy).
- uses: actions/configure-pages@v6
with:
enablement: false
- id: deployment
uses: actions/deploy-pages@v5