diff --git a/.changeset/console-http-server-async.md b/.changeset/console-http-server-async.md new file mode 100644 index 000000000..d8d560a36 --- /dev/null +++ b/.changeset/console-http-server-async.md @@ -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. diff --git a/packages/cli/src/utils/console.ts b/packages/cli/src/utils/console.ts index 4db566481..c4a630e6c 100644 --- a/packages/cli/src/utils/console.ts +++ b/packages/cli/src/utils/console.ts @@ -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 { + 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/*`. @@ -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; @@ -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();