Skip to content

Commit 5826a95

Browse files
dmealingclaude
andcommitted
ci(site): gate the site's published snippets, and the showcase in the release preflight
The website publishes generated code under the claim that it is real `meta gen` output. Eight tasks of machinery were built to make that claim checkable — the showcase corpus for five ports, committed excerpts, the subsequence gate, the highlighters, the drift fixture — and then every one of its tests ran in NO lane. `grep -rn "showcase\|scripts/site" scripts/ci-local.sh .github/workflows/` found nothing. The one exception was the TypeScript half, which rides along inside the `cli` package suite; the other four ports' committed output and every published excerpt were gated by nobody. gates lane `gate_site_snippets` — `bun test scripts/site` (75 tests). The excerpt must still be an in-order subsequence of the real generated file, the drift fixture must still fail, and the committed ts+sql output must still be byte-identical to a PRISTINE regen. Pristine because `meta gen` three-way-merges a hand edit and reports `merged`, so `verify --codegen` is blind to a committed edit inside a generated file — measured in task 1, not assumed. preflight `regen-showcase --check --all-ports`, last in release.mjs Phase 0. Before the version bump, which is committed and pushed long before the first publish; last because it is the only slow check there, so a dead npm token still fails first. Two deliberate departures from the plan, both for the same reason — a check that silently covers less than its name says is the failure this repo keeps hitting: - **`--all-ports`, not the plan's bare `--check`.** Without it a missing mvn/dotnet/uv makes regen-showcase SKIP that port and still exit 0, so the preflight would pass on a box that never checked Java at all. - **Named `gate_site_snippets`, not the plan's `gate_site_payload`.** The payload builder is task 9 and does not exist; there is no payload to check, and naming the gate for one would make it read as more coverage than it has. The payload half joins this gate when task 9 lands. Also adds `--preflight-only`, which the plan asks for by name as the way to verify a preflight change safely: `--dry-run` is NOT safe here — it exits only after Phase 1 has written the new version into all 14 package.json files, rebuilt, and regenerated bun.lock, and nothing reverts that. Verified: gates lane 19/19 green with the new step; `--check --all-ports` green on all five ports in 4.3s; `--preflight-only` exercises the new preflight branch without touching a version. Both halves proven by BREAKING them — one hand-edited line appended to a committed generated file makes `bun test scripts/site` exit 1 naming the file, where `verify --codegen` reports no drift. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NynrRND6ZUwGvq3ZUTCfxG
1 parent 4b5030e commit 5826a95

2 files changed

Lines changed: 63 additions & 1 deletion

File tree

scripts/ci-local.sh

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,32 @@ gate_peer_ranges() { bun scripts/check-peer-ranges.ts; }
201201
# partial fragment stays quiet. The self-test replays all three incidents.
202202
gate_doc_examples() { bun scripts/check-doc-examples.ts && bun scripts/test-doc-examples.ts; }
203203

204+
# ── the site's published snippets must still be true ──────────────────────────
205+
# metaobjects.dev publishes generated code under the claim that it is real `meta gen`
206+
# output, and until this gate NOTHING checked that: the showcase corpus, the committed
207+
# excerpts and the highlighters all shipped with tests that ran in no lane at all.
208+
# This runs them — the excerpt must still be an in-order subsequence of the real
209+
# generated file, the drift fixture must still fail, and `regen-showcase --check
210+
# --bun-only` must still find the committed ts+sql output byte-identical to a pristine
211+
# regen (pristine because `meta gen` three-way-merges a hand edit and reports `merged`,
212+
# so `verify --codegen` is blind to a COMMITTED edit inside a generated file).
213+
#
214+
# bun_install FIRST: scripts/site imports highlight.js, and the regen test spawns the
215+
# workspace `meta` CLI. Cold, a bare specifier from inside the workspace otherwise
216+
# resolves to the PUBLISHED release in ~/.bun/install/cache and the gate passes GREEN
217+
# against the wrong code.
218+
#
219+
# SCOPED to `scripts/site` — a bare `bun test` at the repo root walks java/python/
220+
# csharp/fixtures and takes many minutes.
221+
#
222+
# Deliberately does NOT run the other four ports: that shells out to mvn/dotnet/uv,
223+
# and this lane is `step_if bun` — guarded on bun alone and included in `--quick`, the
224+
# documented pre-PR command. The release preflight runs `--check --all-ports`, which
225+
# refuses to leave a port out. The site PAYLOAD half of this gate joins here when the
226+
# payload builder lands (plan task 9); today there is no payload to check, and naming
227+
# the gate for one would be a gate reading as more coverage than it has.
228+
gate_site_snippets() { bun_install && bun test scripts/site; }
229+
204230
# ── repo-root scripts/ typechecks ─────────────────────────────────────────────
205231
# `bun test` transpiles per file and never typechecks, and `bun run --filter '*'
206232
# typecheck` walks WORKSPACE PACKAGES only — so every script here, including every
@@ -488,6 +514,7 @@ if want gates; then step "script-name hook collisions" gate_script_name_
488514
if want gates; then step "metamodel-version bump" gate_metamodel_version; fi
489515
if want gates; then step_if bun "peer-range bounds" gate_peer_ranges; fi
490516
if want gates; then step_if bun "shipped doc examples load" gate_doc_examples; fi
517+
if want gates; then step_if bun "site snippets are true" gate_site_snippets; fi
491518
if want gates; then step_if bun "scripts/ typecheck" gate_scripts_typecheck; fi
492519
if want gates; then step_if bun "requirements ledger verifies" gate_requirements_ledger; fi
493520
if want gates; then step_if bun "requirements cover vocabulary" gate_requirements_vocabulary; fi

scripts/release.mjs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@
44
// bun run release <version> # do it (stops for confirm before publish)
55
// bun run release <version> --dry-run # everything except publish/tag/push
66
// bun run release <version> --yes # skip the interactive confirm (CI)
7+
// bun run release <version> --preflight-only # run PHASE 0 and stop
8+
//
9+
// --preflight-only is NOT --dry-run. --dry-run still runs Phase 1, which writes the
10+
// new version into all 14 package.json files, rebuilds, and regenerates bun.lock —
11+
// nothing reverts that, and this repo carries a scar from a corrupting version bump.
12+
// --preflight-only returns before any of it, so it is the safe way to exercise a
13+
// preflight change.
714
//
815
// Collapses the manual 11-phase ceremony into one command: preflight → bump →
916
// build → relock → pack-verify → [CONFIRM] → commit → publish (tier order) → tag →
@@ -25,6 +32,7 @@ import { publishSet } from "./publish-set.mjs";
2532

2633
const VERSION = process.argv[2];
2734
const DRY = process.argv.includes("--dry-run");
35+
const PREFLIGHT_ONLY = process.argv.includes("--preflight-only");
2836
const YES = process.argv.includes("--yes");
2937

3038
// A scratch root for the post-publish smoke that no stray node_modules can shadow.
@@ -45,7 +53,7 @@ const ok = (m) => console.log(`\x1b[32m✓\x1b[0m ${m}`);
4553
const die = (m) => { console.error(`\x1b[31m✗ ${m}\x1b[0m`); process.exit(1); };
4654

4755
if (!VERSION || !/^\d+\.\d+\.\d+(-rc\.\d+)?$/.test(VERSION)) {
48-
die("usage: bun run release <version> [--dry-run] [--yes] (e.g. 0.12.6)");
56+
die("usage: bun run release <version> [--dry-run] [--yes] [--preflight-only] (e.g. 0.12.6)");
4957
}
5058

5159
// --- PHASE 0: preflight (all must pass) -----------------------------------
@@ -113,6 +121,33 @@ if (taken.length)
113121
ok(`preflight: on main, synced, ${VERSION} free on all ${set.length} packages`);
114122
ok(`lockstep set @ ${current}: ${set.length} packages → ${VERSION}`);
115123

124+
// The showcase corpus is what metaobjects.dev publishes as "real `meta gen` output",
125+
// so a stale tree is a stale claim on a public page. Checked BEFORE the version bump,
126+
// which is committed and pushed long before the first publish.
127+
//
128+
// LAST in Phase 0 on purpose: this is the only slow check here (it shells out to
129+
// mvn/dotnet/uv), so everything cheap — and everything that fails often, like a dead
130+
// npm token — gets to fail first.
131+
//
132+
// `--all-ports`, never a bare `--check`: without it a missing mvn/dotnet/uv makes
133+
// regen-showcase SKIP that port and still exit 0, so the preflight would pass on a box
134+
// that never checked Java at all. This is the one place that refuses to leave a port
135+
// out; the ci-local gate deliberately runs the bun-only half.
136+
try {
137+
sh("bun scripts/regen-showcase.ts --check --all-ports", { quiet: true });
138+
ok("showcase: committed output matches a pristine regen on all five ports");
139+
} catch (e) {
140+
die("showcase output is stale, or a port's toolchain is missing — the site would\n" +
141+
" publish a stale claim. Run `bun scripts/regen-showcase.ts --all-ports`, review\n" +
142+
" the diff, and commit before releasing.\n\n" +
143+
`${e.stdout ?? ""}${e.stderr ?? ""}`);
144+
}
145+
146+
if (PREFLIGHT_ONLY) {
147+
ok("--preflight-only: Phase 0 passed; stopping before the version bump");
148+
process.exit(0);
149+
}
150+
116151
// --- PHASE 1: bump --------------------------------------------------------
117152
for (const p of set) {
118153
p.pkg.version = VERSION;

0 commit comments

Comments
 (0)