From 1baeebd43183e80d4f070c4548658a46320ca8e2 Mon Sep 17 00:00:00 2001 From: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Date: Sun, 28 Jun 2026 23:48:38 +0800 Subject: [PATCH] ci(framework): assert capability packages ship a runtime entry (no dts-only / half-built) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Capability packages (services / triggers / plugins) are loaded by the multi-tenant runtime via a DYNAMIC import of their published entry. If a package ships a dts-only / half-built / 0-byte dist — an interrupted build, or a package retired in source but still referenced — the import resolves to nothing and the capability SILENTLY fails to load (e.g. record-change automation never fires, with no user-visible signal). The shared tsup config always emits JS, so this can only happen by accident, and nothing in CI caught it. Extend "Verify build outputs" (which runs right after the full `pnpm build`) with a filesystem assertion that every buildable capability package actually produced its declared `main` runtime entry. Self-maintaining: reads each package's own manifest, skips dirs with no package.json (e.g. a retired service-feed / service-ai leftover) and packages without a build script. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/ci.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 300c4e3c1c..86632700ff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -237,6 +237,32 @@ jobs: fi echo "Build outputs verified successfully" + # Capability packages (services / triggers / plugins) are loaded by the + # multi-tenant runtime via a DYNAMIC import of their published entry. If a + # package ships a dts-only / half-built / 0-byte `dist` (an interrupted + # build, or a package retired in source but still referenced), that import + # resolves to nothing and the capability SILENTLY fails to load — e.g. + # record-change automation never fires, with no user-visible signal. The + # build config always emits JS, so this can only happen by accident; assert + # every buildable capability package actually produced its declared runtime + # entry. Self-maintaining: reads each package's own `main`, skips dirs with + # no package.json (e.g. a retired service-feed/service-ai leftover). + - name: Verify capability packages ship a runtime entry (no dts-only / half-built) + run: | + fail=0 + for d in packages/triggers/* packages/services/* packages/plugins/*; do + [ -f "$d/package.json" ] || continue + has_build=$(node -p "Boolean((require('./$d/package.json').scripts||{}).build)" 2>/dev/null || echo false) + [ "$has_build" = "true" ] || continue + main=$(node -p "require('./$d/package.json').main || 'dist/index.js'" 2>/dev/null || echo dist/index.js) + if [ ! -s "$d/$main" ]; then + echo "✗ $d: missing/empty runtime entry '$main' — dts-only or unbuilt? dynamic import would silently fail" + fail=1 + fi + done + if [ "$fail" -ne 0 ]; then exit 1; fi + echo "✓ all buildable capability packages ship a runtime JS entry" + - name: Analyze bundle size run: pnpm --filter @objectstack/spec analyze