feat(FN-2170): add dev-server preview detection and log viewer flow

- Add dev-server port detection utilities and wire detected preview state into process/manager lifecycle
- Normalize dev-server status API responses with preview URL, detected port, and manual override compatibility
- Introduce useDevServerLogs and DevServerLogViewer, and integrate them into the DevServer dashboard view
- Expand dashboard test coverage for detection edges, process/routes behavior, log history handling, and CSS regressions
This commit is contained in:
Fusion
2026-04-20 15:37:32 -07:00
committed by gsxdsm
parent 6522369168
commit c8203210c2
17 changed files with 3585 additions and 1111 deletions

View File

@@ -1,7 +1,7 @@
import { EventEmitter } from "node:events";
import { resolve } from "node:path";
import { SessionEventBuffer, type SessionBufferedEvent } from "./sse-buffer.js";
import { DevServerProcessManager } from "./dev-server-process.js";
import { DevServerProcessManager, type DevServerProcessManagerOptions } from "./dev-server-process.js";
import {
loadDevServerStore,
resetDevServerStore,
@@ -15,7 +15,7 @@ const DEFAULT_LOG_LIMIT = 200;
const DEFAULT_BUFFER_CAPACITY = 400;
// Reserved dashboard port 4040 must never be suggested as a fallback dev-server port.
export const FALLBACK_PORTS = [3000, 4173, 5173, 6006, 8080, 8888, 4000, 4200] as const;
export const FALLBACK_PORTS = [5173, 3000, 4173, 6006, 8080, 4200, 4400, 8888, 4321, 4000] as const;
type LegacyDevServerStatus = DevServerStatus | "idle";
type DevServerLogSource = "stdout" | "stderr" | "system";
@@ -58,6 +58,13 @@ export interface DevServerSnapshot {
logs: DevServerPersistedLogEntry[];
}
export interface DevServerUrlDetectedEvent {
url: string;
port: number;
source: string;
detectedAt: string;
}
export interface DevServerManagerEvent {
type: "state" | "log";
data: DevServerPersistedState | DevServerPersistedLogEntry;
@@ -65,6 +72,11 @@ export interface DevServerManagerEvent {
type DevServerSubscriber = (event: DevServerManagerEvent, eventId: number) => void;
export interface DevServerManagerOptions {
logLimit?: number;
processOptions?: DevServerProcessManagerOptions;
}
export class DevServerManager extends EventEmitter {
private readonly subscribers = new Set<DevServerSubscriber>();
private readonly eventBuffer = new SessionEventBuffer(DEFAULT_BUFFER_CAPACITY);
@@ -78,11 +90,11 @@ export class DevServerManager extends EventEmitter {
constructor(
private readonly rootDir: string,
private readonly store: DevServerStore,
options?: { logLimit?: number },
options?: DevServerManagerOptions,
) {
super();
this.logLimit = options?.logLimit ?? DEFAULT_LOG_LIMIT;
this.processManager = new DevServerProcessManager(store);
this.processManager = new DevServerProcessManager(store, options?.processOptions);
this.bindProcessEvents();
}
@@ -91,10 +103,10 @@ export class DevServerManager extends EventEmitter {
this.applyDevServerState(state);
});
this.processManager.on("output", (payload: { line: string; timestamp: string }) => {
this.processManager.on("output", (payload: { line: string; stream: "stdout" | "stderr"; timestamp: string }) => {
this.appendLog({
serverKey: this.state.serverKey,
source: "stdout",
source: payload.stream,
message: payload.line,
timestamp: payload.timestamp,
});
@@ -120,8 +132,9 @@ export class DevServerManager extends EventEmitter {
});
});
this.processManager.on("url-detected", () => {
this.processManager.on("url-detected", (payload: DevServerUrlDetectedEvent) => {
this.applyDevServerState(this.store.getState());
this.emit("url-detected", payload);
});
}
@@ -155,6 +168,10 @@ export class DevServerManager extends EventEmitter {
return { ...this.state };
}
hasPendingFallbackProbeTimer(): boolean {
return this.processManager.hasPendingProbeTimer();
}
getRecentLogs(limit = this.logLimit): DevServerPersistedLogEntry[] {
if (!Number.isFinite(limit) || limit <= 0) {
return [];