Skip to content

Commit 6c16391

Browse files
sohail2721claude
andcommitted
fix(portal): guard livereload bind and type base URL as UrlPath
Guard the livereload HTTP server's bind the same way as the main server: the livereload package attaches its error handler to the inner WebSocket server, not the HTTP server it binds the port on, so a failed bind emitted an unhandled "error" that crashed the process. On failure we now close the server and report serverStartFailed instead. Also model the portal base URL as a UrlPath instead of a raw string in withApiCopilotForPortal and quickstart's defaultBaseUrl, matching the sibling updateBuildConfigBaseUrl signature and the value-object convention. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MXbT3oxYm19pgHaviemkfa
1 parent 285ca8b commit 6c16391

3 files changed

Lines changed: 14 additions & 3 deletions

File tree

src/actions/portal/quickstart.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { ApiService } from '../../infrastructure/services/api-service.js';
2121
import { DEFAULT_COPILOT_WELCOME_MESSAGE } from './copilot.js';
2222

2323
const defaultPort: number = 23513 as const;
24-
const defaultBaseUrl: string = `http://localhost:${defaultPort}` as const;
24+
const defaultBaseUrl = new UrlPath(`http://localhost:${defaultPort}`);
2525

2626
export class PortalQuickstartAction {
2727
private readonly prompts: PortalQuickstartPrompts = new PortalQuickstartPrompts();

src/actions/portal/serve.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,17 @@ export class PortalServeAction {
7575
const liveReloadPort = await this.networkService.getServerPort([35729, 35730, 35731, 35732]);
7676
const liveReloadServer = createLiveReloadServer({ port: liveReloadPort });
7777

78+
// livereload attaches its "error" handler to the inner WebSocket server, not to the
79+
// HTTP server it binds the port on, so a failed bind (e.g. the port was taken in the
80+
// gap since getServerPort) emits an unhandled "error" that would crash the process.
81+
// Guard the HTTP server the same way as the main server below.
82+
const liveReloadHttpServer = (liveReloadServer as unknown as { config: { server: Server } }).config.server;
83+
if ((await this.waitForServerListening(liveReloadHttpServer)).isErr()) {
84+
liveReloadServer.close();
85+
this.prompts.serverStartFailed(liveReloadPort);
86+
return ActionResult.failed();
87+
}
88+
7889
const server = this.application
7990
.use(connectLiveReload())
8091
.use(express.static(portalDirectory.toString(), { extensions: ["html"] }))

src/types/build/build.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,14 +211,14 @@ export class BuildConfig {
211211
// Returns a copy with API Copilot enabled for a locally-served portal: stores the
212212
// Copilot config, points the portal base URL at the local serve URL, and turns on AI
213213
// editor integrations for the configured SDK languages.
214-
public withApiCopilotForPortal(key: string, welcomeMessage: string, baseUrl: string): BuildConfig {
214+
public withApiCopilotForPortal(key: string, welcomeMessage: string, baseUrl: UrlPath): BuildConfig {
215215
const portal = this.data.generatePortal!;
216216
return new BuildConfig({
217217
...this.data,
218218
apiCopilotConfig: { isEnabled: true, key, welcomeMessage },
219219
generatePortal: {
220220
...portal,
221-
baseUrl,
221+
baseUrl: baseUrl.toString(),
222222
portalSettings: PortalSettings.from(portal.portalSettings)
223223
.withAiIntegrations(Object.keys(portal.languageConfig))
224224
.toPortalSettingsData()

0 commit comments

Comments
 (0)