Skip to content

🧪 POC stack: render, route, compose, pause, and reconstruct the XMD REPL (#838–#842) #1920

🧪 POC stack: render, route, compose, pause, and reconstruct the XMD REPL (#838–#842)

🧪 POC stack: render, route, compose, pause, and reconstruct the XMD REPL (#838–#842) #1920

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
# `labeled` and `unlabeled` are here because a label decides a required
# result: applying `ci-main-red-fix` has to recompute `green`, and removing
# it has to recompute it back. Listing types replaces the default set, so
# the three defaults are spelled out beside them.
types: [opened, reopened, synchronize, labeled, unlabeled]
jobs:
# An ordinary pull request may claim `green` only once CI has completed
# successfully for `main`'s exact current head.
#
# Main Health reports a red `main`; reporting is not gating. Without this job a
# pull request proves itself against a base nothing proved, and the next `main`
# failure cannot be told apart from the one already open. Exactness is the
# whole claim — a successful run for the previous head is the state a red
# `main` is in one commit after it broke — so the decision reuses Main
# Health's own authoritative-run rules rather than re-deciding main health.
#
# Only `contents: read` and `actions: read`: this job decides a required
# check, so it holds no permission to write an issue, a pull request, or the
# repository. That is also why it checks out the pull request rather than
# `main` — nothing it runs is privileged, and the gate has to be changeable by
# the pull request that changes it.
#
# A `main` push skips it. The push is the run that decides main health, so a
# job waiting on its own workflow would never resolve.
main-green:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
# The gate waits for main's verdict rather than failing when one has not
# arrived. Main's own CI takes roughly half of this, which leaves one broad
# margin and stays finite while main keeps advancing. The waiter gives up
# inside this bound and says what it was waiting on; reaching the bound
# itself means the pull request obtained no proof — not that main failed.
timeout-minutes: 60
permissions:
actions: read # list main's CI runs
contents: read
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: denoland/setup-deno@e95548e56dfa95d4e1a28d6f422fafe75c4c26fb # v2.0.3
with:
deno-version: v2.9.5
- name: CI has proven main's exact current head
env:
GH_TOKEN: ${{ github.token }}
GITHUB_REPOSITORY: ${{ github.repository }}
PULL_REQUEST_LABELS: ${{ toJSON(github.event.pull_request.labels.*.name) }}
run: deno run --frozen --allow-env --allow-run=gh scripts/main-green.ts
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "22"
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
- run: pnpm install
- name: Lint + Format
run: pnpm lint
# The corpus is partitioned by measured per-file weight, so the shards finish
# together rather than one carrying the slow files. `fail-fast: false` is what
# makes a failing shard report its own failure instead of cancelling its
# siblings and hiding theirs.
#
# Everything above `Test` repeats in every shard. That is deliberate: it sits
# outside the window this is trying to shorten, and sharing it would introduce
# an artifact-and-dependency boundary between jobs that #279 spent its length
# removing.
test-deno:
name: test-deno (${{ matrix.shard }}/15)
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: denoland/setup-deno@e95548e56dfa95d4e1a28d6f422fafe75c4c26fb # v2.0.3
with:
deno-version: v2.9.5
# `deno task test` covers `scripts/tests/**`, where the runtime drivers
# spawn a literal `bun`. The runner image ships Node but not bun.
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.0
- name: Typecheck
run: deno task check
- name: Publish workflow is generated from manifests
run: |
deno task gen:publish-workflow
git diff --exit-code .github/workflows/publish-packages.yml \
|| { echo "::error::publish-packages.yml is out of date — run 'deno task gen:publish-workflow' and commit the result"; exit 1; }
# The npm build packages every workspace dependency of the CLI, and
# `@executablemd/web` carries a generated browser bundle that is not
# committed. Without this the CLI's npm artifact cannot be built at all.
# Preparing is its own step: a build installs nothing (AGENTS.md).
- name: Install dependencies
run: deno task deps
- name: Build the browser bundle
run: deno task build:web
- name: Test
run: deno run --allow-all --frozen scripts/runtime-tests.ts deno ${{ matrix.shard }}/15
# The same command the release publishes with, minus --dry-run. Catches slow
# types and unresolvable specifiers before they reach a tag.
jsr:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: denoland/setup-deno@e95548e56dfa95d4e1a28d6f422fafe75c4c26fb # v2.0.3
with:
deno-version: v2.9.5
- name: Install dependencies
run: deno task deps
# With the generated bundle absent the negated `publish.exclude` glob
# matches nothing and the dry run quietly checks a package the release
# would never upload. Building first makes this validate the real shape.
- name: Build the browser bundle
run: deno task build:web
- name: The workspace is publishable to JSR
run: deno task check:jsr
smoke:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: denoland/setup-deno@e95548e56dfa95d4e1a28d6f422fafe75c4c26fb # v2.0.3
with:
deno-version: v2.9.5
# `README.md#Build` prepares the checkout before it compiles, and
# preparation owns both dependency layouts — so this job needs the Node
# and pnpm halves as well as Deno's.
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "22"
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
# Preparing the checkout is the installation owner's job, not a side
# effect of the first `deno task xmd` to run: under
# `nodeModulesDir: "auto"` that task would initialize `node_modules`
# itself, before any README block executed. So this job prepares first,
# exactly as the README tells a contributor to on a fresh clone.
#
# The Build target then prepares again, because preparation is its
# preamble and every target inherits it. The repeat is deliberate: what
# the document does is not conditional on who ran it.
- name: Prepare the checkout
run: deno task setup
# The README documents how a contributor prepares a checkout and builds
# the binary, and this is that document doing it. Everything below
# consumes the `dist/xmd` it produced, so a README whose Build target
# stopped working stops this job.
- name: Prepare and build through the README
run: deno task xmd run README.md#Build
- name: Smoke test the compiled binary
run: |
./dist/xmd test smoke-test/README.md \
--include smoke-test \
--include packages/core/components \
--raw
- name: Smoke test attached-service ping-pong with the compiled binary
run: |
./dist/xmd test smoke-test/attached-service-ping-pong.test.md \
--include smoke-test \
--include packages/core/components \
--raw
# Foreground execution is about what a caller sees and what status they
# get, and only the binary shows both surviving `deno compile`.
- name: Smoke test foreground output and fail-fast with the compiled binary
run: deno run --allow-all --frozen scripts/smoke-foreground.ts
# The script installs a second copy of core beside a repository component.
# The declaration must cross into the bundled engine so the failure prints
# and execution continues.
- name: Smoke test metadata from a separately loaded core
run: deno run --allow-all --frozen scripts/smoke-loaded-copy.ts
# The same two-copies shape, asserted rather than smoked: which form a
# component runs when the binary minted the invocation and a file on the
# search path supplied the implementation. It lives here because its
# subject is `dist/xmd`, and this is the job that builds one.
- name: Form dispatch across the compiled binary's loaded copies
run: deno test --allow-all --frozen scripts/tests/component-form-dispatch.test.ts
# `<Plan>` is packaged Markdown rather than a module, so it is exactly the
# kind of thing `deno compile` ships only because an `--include` names it.
# Asking the binary which Component source it carries is what catches a forgotten
# include here rather than at a person's first `xmd plan`.
- name: The <Plan> Component the compiled binary carries
run: deno test --allow-all --frozen scripts/tests/plan-component-compiled.test.ts
# Each package's `components.md` is a packaged asset on the same footing,
# and the long-form documentation is the only thing that reports whether
# one shipped — a binary missing them still lists every component and
# prints every component's registry metadata. `release.yml` runs this
# against its own Linux x64 binary before attesting; here it runs against
# the one the README's Build target produced, so a dropped include fails a
# pull request rather than a release.
- name: Smoke test the packaged component documentation
run: deno run --allow-all --frozen scripts/smoke-documentation.ts
# The ordinary repository provider is assembled at a runtime-named
# entrypoint and holds managed checkouts with a kernel-released advisory
# lock, so only the binary shows both surviving `deno compile`. The script
# runs two of them at once against a managed root of its own.
- name: Smoke test repository composition with the compiled binary
run: deno run --allow-all --frozen scripts/smoke-run-composition.ts
# `<Fetch>` resolves from core's registry, requests through the contextual
# Fetch adapter, and detaches the response before binding it. All three
# live in the module graph, so only the binary shows they survived
# `deno compile`. The script owns the server the request reaches.
- name: Smoke test a retained HTTP read with the compiled binary
run: deno run --allow-all --frozen scripts/smoke-fetch.ts
# A built-in resolves from the module graph rather than a search path.
# Only the compiled binary proves it survives `deno compile`. The
# directory target discovers every colocated document beneath core's
# source at once — the built-in components under `components/`, and the
# structural directives, which resolve no file at all.
- name: Smoke test the built-ins with no search path
run: ./dist/xmd test packages/core/src --raw
# An inline root document exercises the compiled module graph the same way
# a file does, and only the binary proves the graph survived `deno compile`.
- name: Smoke test an inline document
run: |
set -eu
test "$(./dist/xmd -e '# Hello' --raw)" = "# Hello"
# Components resolve from the current directory, not from the root's
# identity, so a search path still finds them.
./dist/xmd -e '<Badge />' --include smoke-test --raw | grep -q '✓ verified'
# So does an ordinary relative filesystem read.
./dist/xmd -e '<File path="smoke-test/Badge.md" />' --raw | grep -q 'verified'
# A stray <Else> is a positioned diagnostic nothing recovers, so the
# run reports it and exits 1. The identity is what is being checked.
stray="${RUNNER_TEMP:-/tmp}/stray.err"
if ./dist/xmd -e '<Else>orphan</Else>' --raw 2>"$stray"; then
echo "expected a stray <Else> to end the run" >&2
exit 1
fi
grep -q '(<eval>:1:1)' "$stray"
# Nothing is written to run a document that was never a file.
before=$(ls -A)
./dist/xmd -e '# Hello' --raw > /dev/null
test "$(ls -A)" = "$before"
# Secret detection is on by default and `--no-secret-detection` is the
# only way off, so both directions belong to the binary rather than only
# to the source runner. The credential is assembled here, so no
# usable-looking literal is committed.
- name: Smoke test the secret-detection opt-out
run: |
set -eu
canary="ghp_$(printf 'abcdefghijklmnopqrstuvwxyz0123456789')"
document="# Smoke
token $canary
"
# Default-on: the run fails, and the credential reaches no output.
if ./dist/xmd -e "$document" --raw > /tmp/on.out 2> /tmp/on.err; then
echo "::error::the compiled binary persisted a credential by default"
exit 1
fi
grep -q 'secret detection rejected content' /tmp/on.err
! grep -q "$canary" /tmp/on.out
# Opted out: the run succeeds, renders the document, and says so once.
./dist/xmd -e "$document" --raw --no-secret-detection \
> /tmp/off.out 2> /tmp/off.err
grep -q "$canary" /tmp/off.out
test "$(grep -cx 'WARNING: secret detection is disabled; credentials may be persisted.' /tmp/off.err)" = "1"
# The value form is refused rather than read as enabled.
if ./dist/xmd -e "$document" --raw --secret-detection=false 2> /tmp/bad.err; then
echo "::error::the compiled binary accepted --secret-detection=false"
exit 1
fi
grep -q 'does not take a value' /tmp/bad.err
# The guide documents this command and its output; running it keeps the
# value-root contract executable rather than described.
- name: Smoke test a value root's JSON result
run: |
test "$(./dist/xmd run smoke-test/value-root.md)" = \
'{"passed":true,"summary":"no findings"}'
# Reading a document's own outline and projecting it happen inside the
# engine, so only the compiled binary proves the catalog and the
# projection survived `deno compile`.
- name: Smoke test document targets
run: |
set -eu
printf '%s\n%s\n' \
'smoke-test/document-targets.md#Alpha' \
'smoke-test/document-targets.md#Beta' > /tmp/targets.expected
./dist/xmd run smoke-test/document-targets.md --help > /tmp/help.out
grep -o 'smoke-test/document-targets\.md#[A-Za-z]*' /tmp/help.out > /tmp/targets.out
diff /tmp/targets.expected /tmp/targets.out
grep -q 'ALPHA_DESCRIBED' /tmp/help.out
./dist/xmd run 'smoke-test/document-targets.md#Alpha' --raw > /tmp/targeted.out
grep -q 'ALPHA_RAN' /tmp/targeted.out
! grep -q 'BETA_RAN' /tmp/targeted.out
# `<WebForm>` is registered by the CLI, so the compiled binary must know it.
# The document fails in preflight, before a listener or a browser, which is
# what makes this runnable on a headless runner: a binary missing the
# registration reports an unresolved component instead, and one that served
# before checking would hang.
- name: Smoke test WebForm registration and preflight
run: |
./dist/xmd run smoke-test/web-form-preflight.md 2>&1 \
| grep -q '<WebForm> schema must be a JSON object'
# The preflight smoke above stops before assets, so it cannot tell a binary
# that embedded the browser bundle from one that did not — and a bundle-less
# compile succeeds silently. This serves a real form and reads the client
# script back over HTTP. Headless is fine: the opener fails and that is a
# warning by design, so the URL is still printed and the form still serves.
- name: Smoke test the compiled binary serving a real form
run: |
set -eu
./dist/xmd run smoke-test/web-form-live.md > /tmp/web-form-live.log 2>&1 &
xmd_pid=$!
trap 'kill "$xmd_pid" 2>/dev/null || true' EXIT
url=""
for _ in $(seq 1 40); do
url=$(grep -oE 'http://127\.0\.0\.1:[0-9]+/f/[A-Za-z0-9_-]+/' /tmp/web-form-live.log | head -1 || true)
[ -n "$url" ] && break
sleep 0.5
done
if [ -z "$url" ]; then
echo "::error::the compiled binary never printed a form URL"
cat /tmp/web-form-live.log
exit 1
fi
curl -fsS "$url" | grep -q '<div id="root"></div>'
bytes=$(curl -fsS "${url}client.js" | wc -c | tr -d ' ')
# The real bundle is ~600 KB of React and RJSF; a placeholder or an
# empty asset would be orders of magnitude smaller.
if [ "$bytes" -lt 100000 ]; then
echo "::error::client.js was $bytes bytes — the binary did not embed the browser bundle"
exit 1
fi
echo "served a real client bundle: $bytes bytes"
# The themed stylesheet is built the same way and by the same task, so
# an embedded font face is the cheapest proof the binary is serving it
# rather than the vendored default.
curl -fsS "${url}theme.css" | grep -q 'font/woff2;base64'
echo "served the themed stylesheet with embedded fonts"
# Terminating with the form still open is the interruption path.
kill "$xmd_pid"
wait "$xmd_pid" 2>/dev/null || true
# Covers the compiled binary relaunching itself as `xmd test-agent`,
# which the source-mode worker command never exercises.
- name: Smoke test the compiled binary as a test agent
run: ./dist/xmd test smoke-test/test-agent/README.md --raw
- name: Smoke test xmd run through ACPX
run: |
./dist/xmd test smoke-test/agent/README.md \
--include smoke-test/agent/components \
--raw
# The host document-filesystem contract, on every target a release ships.
#
# `test-deno`, `test-node`, and `test-bun` run the whole corpus on Linux x64
# and prove the contract holds there. What they cannot prove is that it holds
# on the other four triples: the host adapter's containment is path arithmetic
# plus `realpath`, and both are the platform's — Windows has drive letters,
# UNC paths, junctions, and reparse points that POSIX does not, and the two
# macOS rows resolve `/var` through a symlink that a naive comparison reads as
# an escape.
#
# So this row is focused rather than exhaustive: one suite, plus a compiled
# probe, on each of the five. The compiled probe is the second half of the
# claim — the shipped artifact is a binary, and the adapter reaches
# `node:path`, `node:fs`, and `node:os` through whatever `deno compile` put in
# its graph.
filesystem-contract:
strategy:
fail-fast: false
matrix:
include:
- runner: macos-15
target: aarch64-apple-darwin
- runner: macos-15-intel
target: x86_64-apple-darwin
- runner: ubuntu-24.04
target: x86_64-unknown-linux-gnu
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
- runner: windows-2025
target: x86_64-pc-windows-msvc
runs-on: ${{ matrix.runner }}
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: denoland/setup-deno@e95548e56dfa95d4e1a28d6f422fafe75c4c26fb # v2.0.3
with:
deno-version: v2.9.5
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "22"
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.0
# `deno install` rather than `deno task deps`: the task also caches the
# graphs a browser build and a release compile walk, and it reaches them
# by spawning a child — which does not survive the Windows runner's path
# handling. Nothing here builds the bundle or compiles the CLI, so the
# plain frozen install is the whole preparation this job needs.
- name: Install dependencies
run: deno install --frozen
# The compile below runs under `--node-modules-dir=none`, which resolves
# npm packages from the Deno cache rather than from `node_modules`. This
# caches the probe's graph in that mode without touching the layout the
# step above just created.
- name: Cache the probe's graph for a compile
run: >
deno install --entrypoint --node-modules-dir=none --frozen
scripts/files-contract-probe.ts
# Deno first, and the compile with it: `pnpm install` and `bun install`
# each rewrite `node_modules` into their own layout, so a Deno step after
# one of them resolves through links the other pruned (#279).
- name: Host contract under Deno
run: deno test --allow-all --frozen packages/runtime/tests/host-files.test.ts
- name: Host contract as a compiled binary
run: |
set -eu
deno compile --node-modules-dir=none --cached-only --frozen --allow-all \
--output dist/files-contract-probe scripts/files-contract-probe.ts
if [ -f dist/files-contract-probe.exe ]; then
./dist/files-contract-probe.exe
else
./dist/files-contract-probe
fi
- name: Install the Node layout
run: pnpm install
- name: Host contract under Node
run: pnpm exec tsx --tsconfig tsconfig.node.json --test packages/runtime/tests/host-files.test.ts
- name: Install the Bun layout
run: bun install
- name: Host contract under Bun
run: bun test --timeout=300000 packages/runtime/tests/host-files.test.ts
# The same chain `deno task verify:clean` runs locally. It is the regression
# for #279's ownership claim: a build that installs anything moves the
# prepared-state fingerprint, prunes pnpm's links, and fails the resolution
# probe.
#
# It proves ownership and non-interference, and nothing else. Correctness
# belongs to the jobs `green` already requires: the sharded `test-deno`,
# `test-node` and `test-bun` matrices, which also carry the Deno and Node
# typechecks; `lint`, `jsr` and `site`; and the documentation check inside
# `smoke`. This job used to re-run the complete runtime corpora for load,
# which cost ~1,420s of its ~31 minutes and proved nothing about ownership
# that the direct probe does not (#546).
composability:
runs-on: ubuntu-latest
# `main` only. What this job uniquely proves — a clean checkout prepares,
# builds stay cache-pure and offline, and the real producer cannot corrupt
# or replace the state Deno, Node and Bun resolve through while it runs — is
# worth a post-merge run rather than a place on every pull request's
# critical path (#279).
#
# A `ci-main-red-fix` pull request is the exception, and the reason the
# exception is safe: it is excused the main-health lookup because the base it
# would prove is the broken one, so it proves itself the way `main` is
# proven instead. `green` requires this job to succeed there.
if: >-
github.event_name == 'push'
|| contains(github.event.pull_request.labels.*.name, 'ci-main-red-fix')
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "22"
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
# The interference proof runs a Bun consumer beside the Deno and Node
# ones; without the runtime it fails at spawn, in zero seconds, with an
# empty spool.
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.0
- uses: denoland/setup-deno@e95548e56dfa95d4e1a28d6f422fafe75c4c26fb # v2.0.3
with:
deno-version: v2.9.5
# Caches the harness's own graph, so it can run --cached-only below.
- name: Prepare this checkout
run: deno task deps
# Clones itself, prepares that clone against a scratch DENO_DIR, then
# fingerprints node_modules, the cache's dependency content, and the lock
# around every build phase — each run offline — and finishes with the
# concurrent interference proof and one comparison of what the repository
# owns.
- name: The chain holds from a clean checkout
run: deno task verify:clean
site:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: denoland/setup-deno@e95548e56dfa95d4e1a28d6f422fafe75c4c26fb # v2.0.3
with:
deno-version: v2.9.5
- name: Install workspace deps
run: deno install
- name: Check (fmt + lint + typecheck)
run: deno task check
working-directory: site
- name: Production build (clean runner)
run: deno task build
working-directory: site
test-node:
name: test-node (${{ matrix.shard }}/10)
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "22"
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
# `tsc` resolves the literal dynamic import of the generated browser
# bundle, which `deno check` leaves alone, so the typecheck needs the file
# to exist. The specifier stays literal on purpose: `deno compile` follows
# it to embed the bundle in the binary, and an opaque one would ship a
# binary that cannot serve a form.
#
# Before `pnpm install`, not after: `deno task build:web` rewrites
# node_modules into Deno's layout, which strips the packages pnpm placed
# there and fails the typecheck on two dozen unrelated modules. Installing
# afterwards restores pnpm's layout, and the bundle is outside
# node_modules so it survives.
- uses: denoland/setup-deno@e95548e56dfa95d4e1a28d6f422fafe75c4c26fb # v2.0.3
with:
deno-version: v2.9.5
- name: Install dependencies
run: deno task deps
- name: Build the browser bundle
run: deno task build:web
- run: pnpm install
- name: Typecheck
run: pnpm exec tsc --project tsconfig.node.json --noEmit
- name: Test
run: pnpm test:node ${{ matrix.shard }}/10
test-bun:
name: test-bun (${{ matrix.shard }}/5)
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4, 5]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.0
- run: bun install
- name: Test
run: bun run test:bun ${{ matrix.shard }}/5
# test:bun never loads packages/cli/src/bun.ts, so it cannot tell whether
# the Bun entrypoint's API.Env providers work. This document drives a
# <TestAgent> scenario, which makes the parent relaunch bun.ts as
# `test-agent` through the command it builds — the only check that
# exercises that relaunch.
- name: Bun entrypoint smoke
run: bun run packages/cli/src/bun.ts test smoke-test/test-agent/README.md --raw
# The one required check. It installs nothing and uses no action, so the
# result it reports is the jobs' and never the infrastructure's.
#
# "Succeeded or skipped" was too weak once two jobs became conditional: it
# read a `main-green` that never ran, and a `composability` that never ran, as
# satisfied. So the requirement is per event instead. `main-green` runs on a
# pull request and may be skipped on a push; `composability` runs on a push and
# on a `ci-main-red-fix` pull request, and may be skipped on an ordinary one.
# Every other job must succeed outright — an unexpected skip is an unproven
# job, which is exactly what this check exists to catch.
green:
needs:
[
lint,
main-green,
test-deno,
jsr,
smoke,
filesystem-contract,
composability,
site,
test-node,
test-bun,
]
if: always()
runs-on: ubuntu-latest
steps:
- name: Every CI job produced the result this event requires
env:
RESULTS: ${{ toJSON(needs) }}
EVENT: ${{ github.event_name }}
REPAIR: ${{ contains(github.event.pull_request.labels.*.name, 'ci-main-red-fix') }}
run: |
set -euo pipefail
echo "$RESULTS" | jq -r 'to_entries[] | "\(.value.result)\t\(.key)"' | sort
echo "event=$EVENT repair=$REPAIR"
unproven=$(echo "$RESULTS" | jq -r --arg event "$EVENT" --arg repair "$REPAIR" '
def required($job):
if $job == "main-green" then
(if $event == "push" then ["success", "skipped"] else ["success"] end)
elif $job == "composability" then
(if $event == "push" or $repair == "true"
then ["success"]
else ["success", "skipped"] end)
else ["success"]
end;
to_entries[]
| . as $entry
| select((required($entry.key) | index($entry.value.result)) == null)
| "\($entry.key)=\($entry.value.result)"')
if [ -n "$unproven" ]; then
echo "::error::CI is not green: $(echo "$unproven" | tr '\n' ' ')"
exit 1
fi