Skip to content

Harden review infrastructure with contextual APIs #899

Harden review infrastructure with contextual APIs

Harden review infrastructure with contextual APIs #899

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
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
test-deno:
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.3.14
- 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 task test
# 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
- name: Install dependencies
run: deno task deps
- name: Build the xmd binary
run: deno task build
- name: Smoke test the compiled binary
run: |
./dist/xmd test smoke-test/README.md \
--component-dir smoke-test \
--component-dir 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 \
--component-dir smoke-test \
--component-dir packages/core/components \
--raw
# 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
# 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 />' --component-dir 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 the root collects, so this
# renders it and exits 0. The identity is what is being checked.
./dist/xmd -e '<Else>orphan</Else>' --raw | grep -q '(<eval>:1:1)'
# 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"}'
# `<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 \
--component-dir smoke-test/agent/components \
--raw
# 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 duplicates work other jobs do — that is the cost of asserting the
# whole chain end to end rather than each link separately.
composability:
runs-on: ubuntu-latest
# `main` only. The battery this job runs spends ~1,420s on the Deno, Node,
# and Bun suites — which `test-deno`, `test-node`, and `test-bun` already
# run in parallel, on their own runners. Compressing them onto one four-core
# runner made the workflow's critical path 434s → 649s to prove a property
# about installation that most changes cannot break. What this job uniquely
# proves — a clean checkout prepares, builds stay cache-pure and offline,
# and concurrent checks do not dirty what another reads — is worth a
# post-merge run, not a place on every pull request's critical path (#279).
if: github.event_name == 'push'
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "22"
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
# The battery this job runs includes `bun run test:bun`; without the
# runtime that command fails at spawn, in zero seconds, with an empty
# spool.
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.3.14
- 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.
- 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:
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
test-bun:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.3.14
- run: bun install
- name: Test
run: bun run test:bun
# 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
green:
needs: [lint, test-deno, jsr, smoke, composability, site, test-node, test-bun]
if: always()
runs-on: ubuntu-latest
steps:
- name: Every CI job succeeded
env:
RESULTS: ${{ toJSON(needs) }}
run: |
set -euo pipefail
echo "$RESULTS" | jq -r 'to_entries[] | "\(.value.result)\t\(.key)"' | sort
unproven=$(echo "$RESULTS" | jq -r '
to_entries[]
| select(.value.result != "success" and .value.result != "skipped")
| .key')
if [ -n "$unproven" ]; then
echo "::error::CI is not green: $(echo "$unproven" | tr '\n' ' ')"
exit 1
fi