Skip to content

Commit aeb6db3

Browse files
os-zhuangclaude
andcommitted
fix(cloud-connection,metadata): the canonical-first read needs a per-name try -- getService throws on an empty slot (#4251)
CI caught the shape my local run could not (I built cloud-connection but ran only metadata's tests -- cloud-connection's own suites mock a kernel that registers ONLY the alias): `getService` THROWS for an unregistered slot, so try { a = getService('http.server') ?? getService('http-server'); } catch {...} never reaches the alias -- the first name's throw exits the whole try. Split into a per-name try (readServer helper) in all four readers. Worth recording: the pre-existing alias-first read in metadata/plugin.ts had the SAME shape, so its `?? getService('http.server')` fallback never once fired either -- a decorative fallback, the declared-vs-actual gap this work line keeps finding, now actually implemented in both directions. Verified serially (the earlier 6-file FAIL was local vitest concurrency noise while spec's dts build ran in parallel -- single-file and serial reruns green): cloud-connection 64/12, metadata 281/13, both dts builds, eslint clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 18f5bcd commit aeb6db3

4 files changed

Lines changed: 45 additions & 30 deletions

File tree

packages/cloud-connection/src/marketplace-install-local-plugin.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -121,16 +121,20 @@ export class MarketplaceInstallLocalPlugin implements Plugin {
121121
await this.rehydrate(ctx);
122122

123123
// 2. Mount HTTP endpoints.
124-
// [#4251] Canonical name first — see marketplace-proxy-plugin.
125-
let httpServer: IHttpServer | undefined;
126-
try {
127-
httpServer = ctx.getService<IHttpServer>('http.server')
128-
?? ctx.getService<IHttpServer>('http-server');
129-
} catch {
124+
// [#4251] Read canonical-first with a REAL per-name fallback:
125+
// `getService` THROWS for an empty slot, so a single try around
126+
// `canonical ?? alias` never reaches the alias — the shape the old
127+
// alias-first read had too, meaning its fallback never once fired.
128+
const readServer = (name: string): IHttpServer | undefined => {
129+
try { return ctx.getService<IHttpServer>(name); } catch { return undefined; }
130+
};
131+
// Canonical first — see marketplace-proxy-plugin.
132+
const httpServer = readServer('http.server') ?? readServer('http-server');
133+
if (!httpServer) {
130134
ctx.logger?.warn?.('[MarketplaceInstallLocal] http-server not available — install endpoints not mounted');
131135
return;
132136
}
133-
if (!httpServer || typeof httpServer.getRawApp !== 'function') {
137+
if (typeof httpServer.getRawApp !== 'function') {
134138
ctx.logger?.warn?.('[MarketplaceInstallLocal] http-server missing getRawApp() — install endpoints not mounted');
135139
return;
136140
}

packages/cloud-connection/src/marketplace-proxy-plugin.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -183,19 +183,21 @@ export class MarketplaceProxyPlugin implements Plugin {
183183
manifest?.register?.(MARKETPLACE_BROWSE_UI_BUNDLE);
184184
} catch { /* no manifest service */ }
185185

186-
// [#4251] `http.server` is the canonical name (the only one every
187-
// provider registers — runtime's `config.server` path registers no
188-
// alias, which made this alias-only read an empty-slot miss there).
189-
// The alias fallback dies with the alias registrations.
190-
let httpServer: IHttpServer | undefined;
191-
try {
192-
httpServer = ctx.getService<IHttpServer>('http.server')
193-
?? ctx.getService<IHttpServer>('http-server');
194-
} catch {
186+
// [#4251] Read canonical-first with a REAL per-name fallback:
187+
// `getService` THROWS for an empty slot, so a single try around
188+
// `canonical ?? alias` never reaches the alias — the shape the old
189+
// alias-first read had too, meaning its fallback never once fired.
190+
const readServer = (name: string): IHttpServer | undefined => {
191+
try { return ctx.getService<IHttpServer>(name); } catch { return undefined; }
192+
};
193+
// `http.server` is canonical (the only name every provider
194+
// registers); the alias fallback dies with the alias registrations.
195+
const httpServer = readServer('http.server') ?? readServer('http-server');
196+
if (!httpServer) {
195197
ctx.logger?.warn?.('[MarketplaceProxyPlugin] http-server not available — marketplace routes not mounted');
196198
return;
197199
}
198-
if (!httpServer || typeof httpServer.getRawApp !== 'function') {
200+
if (typeof httpServer.getRawApp !== 'function') {
199201
ctx.logger?.warn?.('[MarketplaceProxyPlugin] http-server missing getRawApp() — marketplace routes not mounted');
200202
return;
201203
}

packages/cloud-connection/src/runtime-config-plugin.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -183,17 +183,20 @@ export class RuntimeConfigPlugin implements Plugin {
183183

184184
start = async (ctx: PluginContext): Promise<void> => {
185185
ctx.hook('kernel:ready', async () => {
186-
// [#4251] Canonical name first — see marketplace-proxy-plugin for
187-
// the alias-only-miss this fixes; the fallback dies with the alias.
188-
let httpServer: IHttpServer | undefined;
189-
try {
190-
httpServer = ctx.getService<IHttpServer>('http.server')
191-
?? ctx.getService<IHttpServer>('http-server');
192-
} catch {
186+
// [#4251] Read canonical-first with a REAL per-name fallback:
187+
// `getService` THROWS for an empty slot, so a single try around
188+
// `canonical ?? alias` never reaches the alias — the shape the old
189+
// alias-first read had too, meaning its fallback never once fired.
190+
const readServer = (name: string): IHttpServer | undefined => {
191+
try { return ctx.getService<IHttpServer>(name); } catch { return undefined; }
192+
};
193+
// Canonical first — see marketplace-proxy-plugin.
194+
const httpServer = readServer('http.server') ?? readServer('http-server');
195+
if (!httpServer) {
193196
ctx.logger?.warn?.('[RuntimeConfigPlugin] http-server not available — runtime/config not mounted');
194197
return;
195198
}
196-
if (!httpServer || typeof httpServer.getRawApp !== 'function') {
199+
if (typeof httpServer.getRawApp !== 'function') {
197200
ctx.logger?.warn?.('[RuntimeConfigPlugin] http-server missing getRawApp() — runtime/config not mounted');
198201
return;
199202
}

packages/metadata/src/plugin.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -409,11 +409,17 @@ export class MetadataPlugin implements Plugin {
409409
try {
410410
// [#4251] Both names are the SAME instance; `http.server` is the
411411
// canonical one (the only name every provider registers), read
412-
// first. `getRawApp?()` is on the contract now — the deliberate
413-
// framework-handle escape, declared once there instead of per
414-
// consumer. The alias fallback dies with the alias registrations.
415-
const httpServer = ctx.getService<IHttpServer>('http.server')
416-
?? ctx.getService<IHttpServer>('http-server');
412+
// first. Per-name try — `getService` THROWS for an empty slot, so
413+
// a single try around `canonical ?? alias` never reaches the
414+
// alias: exactly the shape this read had before (alias-first),
415+
// whose fallback therefore never once fired. `getRawApp?()` is on
416+
// the contract now — the deliberate framework-handle escape,
417+
// declared once there instead of per consumer. The alias fallback
418+
// dies with the alias registrations.
419+
const readServer = (name: string): IHttpServer | undefined => {
420+
try { return ctx.getService<IHttpServer>(name); } catch { return undefined; }
421+
};
422+
const httpServer = readServer('http.server') ?? readServer('http-server');
417423
if (httpServer && typeof httpServer.getRawApp === 'function') {
418424
const { registerMetadataHmrRoutes } = await import('./routes/hmr-routes.js');
419425
const hub = registerMetadataHmrRoutes(httpServer.getRawApp(), this.manager);

0 commit comments

Comments
 (0)