Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .changeset/console-http-server-async.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
'@objectstack/cli': patch
---

fix(cli): resolve `http.server` asynchronously in the console / runtime-assets static plugins

`createConsoleStaticPlugin` and `createRuntimeAssetsPlugin` fetched the
`http.server` service with the **synchronous** `ctx.getService('http.server')`.
When `http.server` is registered as an async factory (the console /
schema-migration boot path), that accessor throws
`Service 'http.server' is async - use await`; because the call sat outside
any try/catch, the throw escaped the plugin's `start()` and rolled back
kernel bootstrap — crashing the CONSOLE/migration boot
(`Plugin startup failed: com.objectstack.runtime-assets`). The runtime
`serve` path, where `http.server` is registered synchronously, was
unaffected, which is why only the control-plane migration boot broke.

Resolve both plugins' `http.server` through a shared `resolveHttpServer`
helper that prefers the async accessor (`getServiceAsync`, which resolves a
sync- or async-registered service) and falls back to the sync one, mirroring
plugin-auth's async `cache` lookup. The helper never throws, so these
optional static-asset plugins skip cleanly when no HTTP server is present
instead of taking down boot.
36 changes: 34 additions & 2 deletions packages/cli/src/utils/console.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,38 @@ export function warnOnConsoleShaDrift(

// ─── Plugin Factory ─────────────────────────────────────────────────

/**
* Resolve the `http.server` service from a plugin context, tolerating both
* ways it can be registered:
*
* - **synchronously** — the runtime `serve` path, where `Runtime` registers
* the concrete server instance; and
* - as an **async factory** — the console / schema-migration boot path,
* for which the *synchronous* `getService` throws
* `Service 'http.server' is async - use await`. Without this the throw
* escaped these static-asset plugins' `start()` and aborted kernel
* bootstrap (`com.objectstack.runtime-assets` failed to start), taking
* down the CONSOLE/migration boot entirely.
*
* Prefer the async accessor (`getServiceAsync`, which resolves either kind),
* falling back to the sync one — mirroring plugin-auth's async `cache` lookup.
* Never throws: an unavailable server resolves to `undefined`, so these
* optional static-asset plugins skip cleanly instead of crashing boot.
*/
async function resolveHttpServer(ctx: any): Promise<any> {
try {
const svc = await ctx.getServiceAsync?.('http.server');
if (svc) return svc;
} catch {
// fall through to the synchronous accessor
}
try {
return ctx.getService?.('http.server');
} catch {
return undefined;
}
}

/**
* Create a lightweight kernel plugin that serves the pre-built Console
* portal static files at `/_console/*`.
Expand All @@ -347,7 +379,7 @@ export function createConsoleStaticPlugin(distPath: string, options?: { isDev?:
init: async () => {},

start: async (ctx: any) => {
const httpServer = ctx.getService?.('http.server');
const httpServer = await resolveHttpServer(ctx);
if (!httpServer?.getRawApp) {
ctx.logger?.warn?.('Console static: http.server service not found — skipping');
return;
Expand Down Expand Up @@ -468,7 +500,7 @@ export function createRuntimeAssetsPlugin(distPath: string) {
init: async () => {},

start: async (ctx: any) => {
const httpServer = ctx.getService?.('http.server');
const httpServer = await resolveHttpServer(ctx);
if (!httpServer?.getRawApp) return;

const app = httpServer.getRawApp();
Expand Down