/** * Staged e2e suites, as one shared mechanism. * * A staged suite is a pipeline: stage 1 builds the fixture, later stages assert * against it, and every stage after the first is guarded so one failure skips * the rest instead of reporting a cascade of fake failures. * * Every suite used to re-declare that guard by hand: a `stageError` variable * set in each stage's catch, and a `requireStage` check at the top of every * later stage. The copies had a hole (celilo#1272). A stage that TIMED OUT * never reached its own catch, because bun aborts the test at its cap. * stageError stayed null, requireStage did not fire, and every later stage ran * against a half-built fixture. The downstream error was louder than the cause * and named the wrong stage. * * This module closes the hole. `stage()` wraps bun's test() and tracks each * stage through three endings: * * resolved -> completed; the next stage runs * rejected -> failed; the FIRST failure's message is recorded, and every * later stage reports `Skipped: ...` * aborted -> the stage body never settled. This is what a bun timeout looks * like from inside: no catch, no finally, no code after the * await. The stage stays pending, and the next requireStage sees * a pending stage and skips. * * The `Skipped:` prefix is load-bearing: packages/e2e/src/extract-failure.ts * keys on it to split real failures from cascade-skips in the run summary. * Several hand-rolled copies used messages without the colon, so their * cascade-skips were tallied as real failures. One message fixes that too. * * State lives in the closure `createStages()` returns, not in module scope. One * bun process runs every test file in a suite, so a module-level tracker would * leak one suite's failure into the next file's first stage. */ import { test } from 'bun:test'; /** * The stage lifecycle state machine, without bun. You almost certainly want * `createStages()`, which drives this for you. Exposed separately so its * contract (especially the aborted ending) has unit tests that do not need to * spawn a real timed-out test. */ export interface StageGuard { /** Marks a stage begun. Throws if a prior stage is still pending. */ begin(name: string): void; /** Marks the pending stage completed successfully. */ ok(): void; /** Marks the pending stage failed. The first failure's message wins. */ fail(error: unknown): void; /** * Throws `Skipped: