Skip to content

Commit fb4788b

Browse files
committed
build: emit @loopover/contract before anything that typechecks or bundles src/
src/mcp/server.ts and packages/loopover-mcp both import @loopover/contract, whose package exports resolve to dist/ -- so every surface that typechecks or bundles src/ needs it emitted first, and nothing did. CI's Typecheck and UI typecheck failed on exactly this, and the selfhost image would have failed at esbuild resolution: the Dockerfile builds the engine but had no step for contract, so build-selfhost.ts could not have resolved the import. Adds the build to ci.yml (before the engine step), the Dockerfile, selfhost.yml and release-selfhost.yml. Unconditional rather than path-gated: every consumer (backend, mcp, miner, ui) can pull it into the typecheck surface, and a zod-only leaf with no workspace dependencies is cheap enough that gating buys nothing but a class of skipped-build failures. Same shape as the engine build-order fix, applied before that step for the same reason. Verified by running build-selfhost.ts --all against a clean contract build. Refs #9517
1 parent d2cb76c commit fb4788b

4 files changed

Lines changed: 22 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,14 @@ jobs:
467467
key: turbo-code-${{ hashFiles('package-lock.json') }}-${{ github.run_id }}
468468
restore-keys: |
469469
turbo-code-${{ hashFiles('package-lock.json') }}-
470+
# @loopover/contract's "types" resolve to packages/loopover-contract/dist/index.d.ts, and src/ +
471+
# packages/loopover-mcp both import it -- so like the engine below, typecheck cannot run until it
472+
# has been built. Unconditional rather than gated on a `contract` path filter: every consumer of it
473+
# (backend, mcp, miner, ui) can pull it into the typecheck surface, and building a zod-only leaf
474+
# package with no dependencies of its own is cheap enough that gating it would buy nothing but a
475+
# class of skipped-build failures.
476+
- name: Build contract package
477+
run: npx turbo run build --filter=@loopover/contract
470478
- name: Build engine package
471479
if: ${{ github.event_name == 'push' || needs.changes.outputs.backend == 'true' || needs.changes.outputs.engine == 'true' || needs.changes.outputs.ui == 'true' }}
472480
run: npx turbo run build --filter=@loopover/engine

.github/workflows/release-selfhost.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,11 @@ jobs:
129129
# That package's dist/ is gitignored and only exists after this build step -- the regular CI smoke
130130
# test's narrower (non --all) build never hits this import chain, so it never caught the gap that
131131
# ci.yml's own validate-code job hit for ordinary backend PRs (fixed there separately).
132+
# Built before the engine for the same reason the Dockerfile does: src/'s import graph reaches
133+
# @loopover/contract, whose package exports resolve to dist/, so anything type-checking or
134+
# bundling src/ needs it emitted first. Zod-only leaf, no workspace dependencies of its own.
135+
- name: Build contract package
136+
run: npm run build --workspace @loopover/contract
132137
- name: Build engine package
133138
run: npm run build --workspace @loopover/engine
134139

.github/workflows/selfhost.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ jobs:
7777
# dist/ is gitignored and only exists after this build step, so the test fails to resolve the
7878
# package's exports without it -- this workflow never needed the engine package built before, so it
7979
# never had this step; it does now.
80+
# Built before the engine for the same reason the Dockerfile does: src/'s import graph reaches
81+
# @loopover/contract, whose package exports resolve to dist/, so anything type-checking or
82+
# bundling src/ needs it emitted first. Zod-only leaf, no workspace dependencies of its own.
83+
- name: Build contract package
84+
run: npm run build --workspace @loopover/contract
8085
- name: Build engine package
8186
run: npm run build --workspace @loopover/engine
8287

Dockerfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ COPY . .
1919
# --ignore-scripts: no native builds are needed (SQLite is the built-in node:sqlite; @hono/node-server is
2020
# pure JS; esbuild ships its binary as an optional dependency, not a script).
2121
RUN npm ci --ignore-scripts
22+
# @loopover/contract before the engine: src/ imports it, and its package exports resolve to dist/,
23+
# so esbuild in build-selfhost.ts below cannot resolve the import until it has been emitted. A
24+
# zod-only leaf with no workspace dependencies, so it builds first and standalone.
25+
RUN npm --workspace @loopover/contract run build
2226
RUN npm --workspace @loopover/engine run build
2327
# --all: bundle every dependency into one self-contained dist/server.mjs, so the runtime image needs no
2428
# node_modules (≈10× smaller). The bundle has zero `cloudflare:*` imports (stubbed at build), so no loader.

0 commit comments

Comments
 (0)