Skip to content

Commit b043f24

Browse files
os-zhuangclaude
andcommitted
fix(cli): mount metadata-HMR SSE route under host-config serve --dev
Host configs (their `plugins[]` holds instantiated Plugin objects) make `shouldBootWithLibrary()` return false, so `serve` skips createStandaloneStack — the only place `@objectstack/metadata`'s MetadataPlugin is composed. That plugin registers `GET /api/v1/dev/metadata-events` in its start(), so serving a host config (e.g. examples/app-showcase) left the Console's MetadataHmrReloader hitting a 404 and never auto-reloading after a recompile. Compose a lightweight, artifact-sourced MetadataPlugin in the host-config path, mirroring the standalone stack's dev config. Guarded to keep blast radius small: - isDev only — production / cloud host deployments are untouched - flags.server — pointless without an HTTP server to mount on - skipped when a MetadataPlugin is already present (standalone / artifact paths) - skipped when no local compiled artifact resolves to source from Verified: a minimal host-config `serve --dev` now returns 200 (text/event-stream) for GET /api/v1/dev/metadata-events; was 404. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 8f23746 commit b043f24

3 files changed

Lines changed: 53 additions & 0 deletions

File tree

packages/cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"@objectstack/formula": "workspace:*",
5252
"@objectstack/lint": "workspace:*",
5353
"@objectstack/mcp": "workspace:*",
54+
"@objectstack/metadata": "workspace:*",
5455
"@objectstack/objectql": "workspace:^",
5556
"@objectstack/observability": "workspace:^",
5657
"@objectstack/platform-objects": "workspace:*",

packages/cli/src/commands/serve.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,55 @@ export default class Serve extends Command {
849849
}
850850
}
851851

852+
// ── Dev-only: metadata HMR SSE endpoint for host/aggregator configs ──
853+
//
854+
// Host configs (their `plugins[]` holds instantiated Plugin objects) skip
855+
// the standalone stack — see `shouldBootWithLibrary()`. That stack is the
856+
// only place `@objectstack/metadata`'s MetadataPlugin gets composed, and
857+
// MetadataPlugin is what registers `GET /api/v1/dev/metadata-events` in
858+
// its start(). So when serving a host config the Console's dev
859+
// metadata-HMR reloader hits a 404 and the page never auto-reloads after
860+
// a recompile.
861+
//
862+
// Compose a lightweight, artifact-sourced MetadataPlugin here so the
863+
// endpoint exists regardless of boot shape. Guards:
864+
// • `isDev` only — production / cloud host deployments are untouched.
865+
// • `flags.server` — pointless without an HTTP server to mount it on.
866+
// • skipped when a MetadataPlugin is already present (the standalone /
867+
// artifact-fallback paths compose their own upstream).
868+
// • skipped when no local compiled artifact resolves to source from.
869+
// Registered AFTER the HonoServer plugin so the `http-server` service
870+
// (and its `getRawApp()`) is available when MetadataPlugin.start() mounts
871+
// the route.
872+
if (isDev && flags.server) {
873+
const hasMetadataPlugin = plugins.some(
874+
(p: any) => p?.constructor?.name === 'MetadataPlugin'
875+
);
876+
if (!hasMetadataPlugin) {
877+
try {
878+
const { resolveDefaultArtifactPath } = await import('@objectstack/runtime');
879+
const hmrArtifactPath = resolveDefaultArtifactPath();
880+
if (hmrArtifactPath && !/^https?:\/\//i.test(hmrArtifactPath)) {
881+
const { MetadataPlugin } = await import('@objectstack/metadata');
882+
// Mirror the standalone stack's dev config exactly
883+
// (packages/runtime/src/standalone-stack.ts): declarative
884+
// metadata is loaded from the compiled artifact — no source-file
885+
// scanner (redundant + EMFILE-prone) — and `artifactWatch` polls
886+
// the single artifact file so an `os dev` recompile broadcasts a
887+
// reload over the SSE stream.
888+
await kernel.use(new MetadataPlugin({
889+
watch: false,
890+
artifactWatch: true,
891+
artifactSource: { mode: 'local-file', path: hmrArtifactPath },
892+
}));
893+
trackPlugin('Metadata');
894+
}
895+
} catch (e: any) {
896+
console.warn(chalk.yellow(` ⚠ Dev metadata-HMR endpoint not enabled: ${e?.message}`));
897+
}
898+
}
899+
}
900+
852901
// Serve /runtime/assets/* unconditionally — branding logos, favicons,
853902
// and other static runtime assets must resolve even when the Console
854903
// dist hasn't been built yet. The directory is resolved as:

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)