Skip to content

Drop the absent arms from s.fieldOr's Output #2146

Drop the absent arms from s.fieldOr's Output

Drop the absent arms from s.fieldOr's Output #2146

Workflow file for this run

name: CI
on:
push:
branches:
- main
- "v\\d+.\\d+.\\d+-patch"
pull_request:
# For running the drift check on demand between pushes to main.
workflow_dispatch:
jobs:
sury:
name: Sury - Build & Test & Pack
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v6
with:
node-version: 24.16.0
cache: "pnpm"
- run: pnpm install
- run: pnpm lint:deadcode
- run: pnpm build
working-directory: packages/sury
# The *.res.mjs beside each *.res are compiled output, checked in because
# they ship in the published package. Every job that needs them rebuilds
# them first, so a commit that forgot to regenerate passes the whole suite
# while the file in git says something else - the drift is only ever read
# by a consumer installing from npm. The build above just regenerated
# them; anything that moved was stale in the commit.
- name: Compiled ReScript matches its source
run: ../../scripts/assert-no-drift.sh '*.res.mjs'
working-directory: packages/sury
# `coverage` below typechecks only what vitest is pointed at
# (tests/**/*_test.ts). This covers src/ and scripts/ too - the fuzzers
# live in scripts/ and reach the library through its public entry, so
# without this a renamed export leaves them calling a name that no longer
# exists and nothing says so until someone runs them by hand.
- run: pnpm typecheck
working-directory: packages/sury
- run: pnpm coverage
working-directory: packages/sury
# Guards the `escapeFree` flag, which switches jsonString to splicing a
# value between bare quotes with no escaping - so a format flagged wrongly
# emits syntactically broken JSON. That claim is a property of each
# format's regex, and nothing else in the suite checks it. Roughly 90s.
- run: pnpm fuzz:escfree
working-directory: packages/sury
# The only check that the union compiler's dispatch matches a sequential
# try of each member (CLAUDE.md, "Changing the union compiler"). Fixed
# seed so a red run reproduces locally with the same command. Shares
# scripts/knownBugs.ts with fuzz:schema.
- run: pnpm fuzz:union --seed=1
working-directory: packages/sury
# A spec pins one schema's code and the values it writes down, and says
# nothing about the branch no spec reaches (CLAUDE.md, "Fuzzing a single
# schema"). The default invocation is the gate: forty seeds, both families.
# It fails on a finding scripts/knownBugs.ts does not list, and on an
# entry there it no longer reaches. Fixed range so a red run reproduces
# locally with the same command. ~25s.
- run: pnpm fuzz:schema
working-directory: packages/sury
# A form field's reading depends on its wrapper as much as its type, and
# a spec covers one schema (CLAUDE.md, "Changing the form codec"). This
# crosses the two and checks what a golden cannot: that a field works in
# both directions or neither, that an encode leaves its input alone, that
# a value survives an encode and the decode of it, and that every entry
# list a client could send is either rejected or read as a value the
# schema itself accepts. Exhaustive, so no seed. Seconds.
- run: pnpm fuzz:formdata
working-directory: packages/sury
# Which reading a link takes - pack or unpack - is written down by a slot,
# a field position or a declared payload and mirrored by `reverse`, and a
# spec pins one link (CLAUDE.md, "Changing the content codec"). This
# crosses every source with every target and slot and checks that a link
# compiles both directions or neither, that its reverse reads the same
# values, that a value survives decode, encode, decode, and that a slot
# does what it declares. Exhaustive, so no seed. Seconds.
- run: pnpm fuzz:content
working-directory: packages/sury
# artifact_test.ts checks jsr.json against package.json; only JSR itself
# checks the parts npm has no opinion on (slow types, the exclude list).
- run: npx --yes jsr@0.14.3 publish --dry-run --allow-dirty
working-directory: packages/sury/artifacts
- uses: codecov/codecov-action@v7
with:
files: packages/sury/coverage/lcov.info
# Doesn't work with rescript v12
# - run: pnpm lint:stdlib
# working-directory: packages/sury
- uses: actions/upload-artifact@v4
with:
name: sury
path: packages/sury/artifacts
compliance:
name: JSON Schema Test Suite
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v6
with:
node-version: 24.16.0
cache: "pnpm"
- run: pnpm install
# Fetches the pinned upstream revision (see suite-ref.json) and fails if
# the score drifts from packages/json-schema-test-suite/goldens/ in
# either direction - a fix that improves coverage is expected to land the
# regenerated golden alongside it.
- name: Restore suite checkout
uses: actions/cache@v4
with:
path: packages/json-schema-test-suite/.suite
key: json-schema-test-suite-${{ hashFiles('packages/json-schema-test-suite/suite-ref.json') }}
- run: pnpm compliance
protobuf-compliance:
name: Protobuf - Conformance & Test Suite
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v6
with:
node-version: 24.16.0
cache: "pnpm"
- run: pnpm install
# Our own corpus: fast, readable, and checked against two reference
# implementations. It says what broke.
- name: Protobuf test suite
shell: bash
run: |
set -o pipefail
pnpm protobuf:compliance | tee compliance.txt
echo "### $(tail -n 1 compliance.txt)" >> "$GITHUB_STEP_SUMMARY"
# Generated message graphs, round-tripped and mutated against protobufjs
# and protobuf-es: the combinations no case in the corpus names.
- run: pnpm protobuf:fuzz
# Google's own conformance runner, which generates its cases inside the
# binary - the one that says whether we are right. Pinned corpus, cached
# by the commit it is pinned to.
- name: Restore conformance corpus
uses: actions/cache@v4
with:
path: packages/protobuf-conformance/.upstream
key: protobuf-conformance-${{ hashFiles('packages/protobuf-conformance/conformance-ref.json') }}
- name: Protobuf conformance suite
shell: bash
run: |
set -o pipefail
pnpm protobuf:conformance | tee conformance.txt
echo "### $(tail -n 1 conformance.txt)" >> "$GITHUB_STEP_SUMMARY"
# The four pages under docs/benchmarks/ are generated, and this is what keeps
# them true. Three of their four sections are the same on any machine, so
# every run checks them and fails on a page that no longer matches a fresh
# measurement. The fourth is timings, which move with the runner: those are
# not committed anywhere, but remeasured on main into the charts the pages
# embed. A benchmark page nobody regenerates is worse than no page, and main
# is protected, so the numbers that have to be rewritten by a bot live on a
# branch of their own instead.
benchmarks:
name: Benchmarks
runs-on: ubuntu-24.04
permissions:
# Writing is only reached on the main branch, and only to the `benchmarks`
# branch the charts are published to.
contents: write
steps:
- uses: actions/checkout@v4
with:
# The push below needs the token this leaves behind.
persist-credentials: true
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v6
with:
node-version: 24.16.0
cache: "pnpm"
- run: pnpm install
- name: Pages match a fresh measurement
run: pnpm benchmarks
# On the ref rather than the event: `benchmarks` is one branch every page
# reads, and a dispatch or a v*-patch push is a non-pull-request event
# too, so either would replace main's timings with a measurement of code
# that is not on main.
#
# Pushing a branch rather than a commit to main is what keeps this off the
# protected-branch rule, and the branch carries nothing this workflow runs
# on, so there is no `[skip ci]` to remember.
- name: Publish the timings
if: github.ref == 'refs/heads/main'
run: pnpm benchmarks --publish
performance:
name: Performance (PR)
if: github.event_name == 'pull_request'
runs-on: ubuntu-24.04
concurrency:
group: performance-${{ github.event.pull_request.number }}
cancel-in-progress: true
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
# The spec performance step below builds its baseline from the PR's
# base branch, which a shallow checkout doesn't have.
fetch-depth: 0
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v6
with:
node-version: 24.16.0
cache: "pnpm"
- run: pnpm install
# Every spec measured against the same library built from the PR's base
# branch - schema creation, creation+compilation, and each example. Both
# versions run interleaved in one process, so a shared runner's noise hits
# them equally and cancels in the ratio. Advisory: a regression never
# fails the job (a failed *measurement* still does).
- name: Spec performance
# For pipefail: the default `bash -e` would let `tee` mask a failure.
shell: bash
run: pnpm --filter=sury spec check --perf=only | tee spec-perf.txt
- uses: actions/upload-artifact@v4
id: spec-perf-artifact
with:
name: spec-perf
path: spec-perf.txt
- name: Format spec performance comment
run: npx tsx packages/spec/perfComment.ts spec-perf.txt spec-perf-comment.md
env:
ARTIFACT_URL: ${{ steps.spec-perf-artifact.outputs.artifact-url }}
# The PR's head, not GITHUB_SHA - on a pull_request event that's the
# ephemeral merge commit, which names nothing a reader can go look at.
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
# A new comment per push, not one edited in place. Editing keeps the PR
# tidy at the cost of the history: the numbers a reviewer read are gone
# the moment the next push lands, and nothing says whether a delta was
# already there two commits ago or arrived with the one under review.
# Each comment names the head it measured, so the thread reads in order.
- name: Post spec performance comment
if: github.event.pull_request.head.repo.full_name == github.repository
uses: actions/github-script@v7
env:
BODY_FILE: spec-perf-comment.md
with:
script: |
const fs = require("fs");
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: fs.readFileSync(process.env.BODY_FILE, "utf8"),
});
# The `performance` job above only ever compares a PR against its own base, so
# a change small enough to sit under the noise floor leaves no trace anywhere
# - and twenty of them in a row still leave none. This measures main against
# the last release instead, where that same drift has had time to add up to
# something the floor can see. Reported to the run summary, not gated: the
# answer to a real drift is a fix, not a red main.
performance-drift:
name: Performance drift since last release
if: github.event_name != 'pull_request'
runs-on: ubuntu-24.04
# Reads the repo and writes a file ($GITHUB_STEP_SUMMARY); it never calls
# the API, so it needs strictly less than the `performance` job above.
permissions:
contents: read
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
# Needs the tags, and every commit back to the one they point at.
fetch-depth: 0
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v6
with:
node-version: 24.16.0
cache: "pnpm"
- run: pnpm install
- name: Spec performance vs last release
# bash for pipefail, as above.
shell: bash
run: |
tag=$(git describe --tags --abbrev=0)
echo "baseline: $tag"
pnpm --filter=sury spec check --perf=only --against "$tag" | tee spec-perf.txt
- name: Write drift to run summary
run: npx tsx packages/spec/perfComment.ts spec-perf.txt "$GITHUB_STEP_SUMMARY" "Performance drift since last release"
# e2e needs this slice on every run, so it stays its own job.
ppx-build-linux:
uses: ./.github/workflows/ppx-build.yml
with:
target: linux
runs-on: ubuntu-24.04
# Static linking pins the binary off the runner's glibc. The macOS and
# Windows toolchains have no working equivalent.
build-flags: --profile static
# The other four slices only feed the published tarball, and macOS runners
# bill at 10x. A PR that breaks them breaks the linux build too, near enough.
ppx-build-rest:
if: github.event_name != 'pull_request'
strategy:
# A slice failing to build is per-platform news; the others still tell us
# whether the failure is local to one toolchain.
fail-fast: false
matrix:
include:
- target: linux-arm
runs-on: ubuntu-24.04-arm
build-flags: --profile static
- target: macos
# Rosetta translates x64 -> arm64, never the reverse, so the x64
# slice needs an actual Intel runner rather than macos-latest.
runs-on: macos-15-intel
build-flags: ""
- target: macos-arm
runs-on: macos-latest
build-flags: ""
- target: windows
runs-on: windows-2025
build-flags: ""
uses: ./.github/workflows/ppx-build.yml
with:
target: ${{ matrix.target }}
runs-on: ${{ matrix.runs-on }}
build-flags: ${{ matrix.build-flags }}
pack-sury-ppx:
name: Pack sury-ppx
# Skipped on PRs because ppx-build-rest is: there is no tarball to pack
# without all five slices.
needs: [ppx-build-linux, ppx-build-rest]
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v6
with:
node-version: 24.16.0
cache: "pnpm"
- run: pnpm install
- name: Download ppx artifacts
uses: actions/download-artifact@v4
with:
pattern: ppx-build-*
path: binaries
- name: Move ppx artifacts
run: |
mv binaries/ppx-build-linux/bin.exe packages/sury-ppx/ppx-linux.exe
mv binaries/ppx-build-linux-arm/bin.exe packages/sury-ppx/ppx-linux-arm.exe
mv binaries/ppx-build-macos/bin.exe packages/sury-ppx/ppx-osx.exe
mv binaries/ppx-build-macos-arm/bin.exe packages/sury-ppx/ppx-osx-arm.exe
mv binaries/ppx-build-windows/bin.exe packages/sury-ppx/ppx-windows.exe
# Artifact download drops the executable bit. install.cjs chmods the
# binary it picks, but installers that skip postinstall (pnpm >=10 by
# default) fall through to the `bin` script, which execs it as-is.
chmod +x packages/sury-ppx/ppx-*.exe
- name: npm pack (sury-ppx)
run: npm pack
working-directory: packages/sury-ppx
# The last gate before an artifact becomes a published tarball: what the
# binaries are, what they need at runtime, and what reached the package.
- name: Verify the ppx binaries and tarball
run: npx tsx packages/sury-ppx/checkBinaries.ts packages/sury-ppx/sury-ppx-*.tgz
- name: "Upload artifact: sury-ppx npm package"
uses: actions/upload-artifact@v4
with:
name: sury-ppx
path: packages/sury-ppx/sury-ppx-*.tgz
e2e:
name: E2E Test
needs: [ppx-build-linux]
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v6
with:
node-version: 24.16.0
cache: "pnpm"
- run: pnpm install
- name: Download ppx linux artifacts
uses: actions/download-artifact@v4
with:
name: ppx-build-linux
path: binaries/linux
- name: Move ppx artifacts
run: |
mv binaries/linux/bin.exe packages/sury-ppx/ppx-linux.exe
- name: Install ppx
run: pnpm ppx:install
working-directory: packages/e2e
- run: pnpm rescript && pnpm test
working-directory: packages/e2e
# Same guard as the sury job, for the ppx-generated output. This one is
# never published, but it is the only readable record of what the ppx
# emits, and the step above regenerates it before asserting on it - so a
# stale commit here is invisible in every other signal.
- name: Compiled ReScript matches its source
run: ../../scripts/assert-no-drift.sh '*.res.mjs'
working-directory: packages/e2e