Skip to content

Coordinate bridge dev builds behind one generation pipeline#20

Merged
wch merged 7 commits into
mainfrom
build-improvements
Jul 21, 2026
Merged

Coordinate bridge dev builds behind one generation pipeline#20
wch merged 7 commits into
mainfrom
build-improvements

Conversation

@wch

@wch wch commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

ai-provider-bridge isn't a TypeScript project reference — it's built by esbuild, with a separate declaration build on top. Previously, watch mode ran multiple watchers writing into the same dist/ independently, so a downstream bundler (Standalone, RStudio, Positron, Desktop) could observe a half-written generation — a missing export, a stale declaration file — and crash or reload-loop. This PR replaces that with one coordinator that owns every JS and declaration write, builds a complete generation in a staging directory, validates it, promotes it without deleting live exports, and only then announces it. Downstream consumers watch that single announcement instead of raw dist/ changes, so a warm dev startup that previously fell into a reload/crash cycle now starts cleanly.

Changes

  • scripts/build-coordinator.ts (new) — the sole writer of bridge JS and declarations. Stages a generation, validates package exports and declaration resolution, promotes complete files, then writes .bridge-watch/generation.json and emits a machine-readable completed-generation event on stdout. Promotion is atomic per file (not a whole-directory transaction — no host filesystem gives that across platforms without changing the package layout), and a failed promotion rolls back only the files it had already replaced.
  • Single-writer cross-process lock — a coordinator claims the lock with an atomic no-replace hard link carrying owner identity, so two coordinators (e.g. a targeted platform watch and the root aggregate watch) can never race to write the same generation. Stale-owner takeover after an ungraceful exit is serialized through a takeover directory and revalidates the claimed lock's identity before replacing it.
  • Warm-start reuse — a coordinator start whose current build-input fingerprint and live dist/ digests both match the persisted record re-announces the previous generation instead of rebuilding, cutting a several-second warm rebuild down to roughly half a second.
  • scripts/build-inputs.ts (new) — one module that both fingerprints and registers file watches from the same discovered input inventory (bridge src//scripts/ trees, package and tsconfig metadata, and built output from */workspace:* sibling dependencies). Fingerprinting and watch coverage previously risked drifting apart if someone updated one without the other; this makes that structurally impossible. A change to a build script itself requests a coordinator restart; every other input change schedules a normal generation build.
  • tsconfig.declarations.json (new) — the single authoritative declaration build, including the Positron subpath. The coordinator rejects vscode imports outside src/positron/ and vscode references outside dist/positron/.
  • CI workflow, package.json script wiring, and .gitignore updates for .bridge-watch/.
  • Unit tests for the coordinator (staging, validation, promotion, locking, reuse) and for the build-input module (discovery, fingerprinting, watch registration).

Design decisions

  • One coordinator, not per-consumer watchers — because the bridge isn't a project reference, letting each platform's bundler watch and rebuild dist/ independently is what caused the original half-written-generation races. Centralizing the write path is the only way to guarantee downstream consumers never see an incomplete generation.
  • Fingerprint and watch coverage share one input inventory — keeping these as two independently maintained lists is exactly the kind of drift that previously let a real input (like a tsconfig.json change) escape both fingerprinting and live rebuild-on-change. build-inputs.ts is now the one place that answers "what affects the build output."
  • Reuse is fail-closed — any mismatch, including a legacy generation record with no fingerprint at all, forces a full rebuild rather than risking a stale reuse. node_modules is deliberately excluded from the fingerprint; the root postinstall --clean already forces a rebuild after every dependency change.

wch added 7 commits July 20, 2026 20:12
The bridge generation validation tests reference the real dist/ directory,
which only exists after building ai-provider-bridge. The check CI job only
builds ai-config and ai-credentials before running tests, so these tests
fail with ENOENT in CI while passing locally (where dist/ exists from
prior builds).

Give the coordinator tests a dedicated vitest config so they only run via
test:build-coordinator in the bridge-generation CI job, which builds the
bridge first.
@wch
wch enabled auto-merge (squash) July 21, 2026 16:52
@wch
wch merged commit d18c83b into main Jul 21, 2026
4 checks passed
@wch
wch deleted the build-improvements branch July 21, 2026 16:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant