mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-10-30 09:16:55 +00:00
fix(cli): preview stops at once on Ctrl+C after Studio was open (#6782)
* fix(cli): preview stops at once on Ctrl+C while a Studio tab is open * fix(cli): a closed Studio tab leaves no timer that holds a stopping preview open * fix(cli): name the tab-closed wait instead of commenting it * test(cli): the tab-disconnect test checks the route's own keep-alive timer, not every timer
This commit is contained in:
@@ -54,6 +54,7 @@ import { runRenderSetupWorker } from "../utils/cancellableProcess.js";
|
||||
import { formatLintStartupMessage } from "../utils/lintFormat.js";
|
||||
import {
|
||||
activeServerOnPort,
|
||||
closeServer,
|
||||
findPortAndServe,
|
||||
scanActiveServers,
|
||||
killActiveServers,
|
||||
@@ -1743,7 +1744,7 @@ export async function runEmbeddedMode(
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
watcher.close();
|
||||
result.server.close(() => resolveRun());
|
||||
void closeServer(result.server).then(resolveRun);
|
||||
});
|
||||
};
|
||||
// `on`, not `once`: a repeat Ctrl+C/SIGTERM while shutdown is running must
|
||||
|
||||
@@ -2,10 +2,11 @@ import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { createServer, type Server } from "node:net";
|
||||
import { spawn } from "node:child_process";
|
||||
import { resolve } from "node:path";
|
||||
import { createServer as createHttpServer, type Server as HttpServer } from "node:http";
|
||||
import { createServer as createHttpServer, get, type Server as HttpServer } from "node:http";
|
||||
import {
|
||||
PORT_PROBE_HOSTS,
|
||||
activeServerOnPort,
|
||||
closeServer,
|
||||
detectHyperframesServer,
|
||||
findPortAndServe,
|
||||
testPortOnAllHosts,
|
||||
@@ -306,3 +307,25 @@ describe("detectHyperframesServer", () => {
|
||||
expect(result).toEqual({ type: "match" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("closeServer", () => {
|
||||
it("closes while a client holds an event stream open", async () => {
|
||||
const server = createHttpServer((_req, res) => {
|
||||
res.writeHead(200, { "content-type": "text/event-stream" });
|
||||
res.write(": open\n\n");
|
||||
});
|
||||
const port = await new Promise<number>((resolve) =>
|
||||
server.listen(0, "127.0.0.1", () =>
|
||||
resolve((server.address() as import("node:net").AddressInfo).port),
|
||||
),
|
||||
);
|
||||
await new Promise<void>((resolve) =>
|
||||
get(`http://127.0.0.1:${port}/`, (res) => {
|
||||
res.on("error", () => {});
|
||||
resolve();
|
||||
}).on("error", () => {}),
|
||||
);
|
||||
await closeServer(server);
|
||||
expect(server.listening).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -413,6 +413,12 @@ export async function killActiveServers(
|
||||
return { killed, unverified };
|
||||
}
|
||||
|
||||
/** Stops `server` once every open connection is closed too; an open Studio tab holds its event stream for its life. */
|
||||
export function closeServer(server: import("@hono/node-server").ServerType): Promise<void> {
|
||||
if ("closeAllConnections" in server) server.closeAllConnections();
|
||||
return new Promise((resolve) => server.close(() => resolve()));
|
||||
}
|
||||
|
||||
// ── Smart port selection ───────────────────────────────────────────────────
|
||||
|
||||
export type FindPortResult =
|
||||
|
||||
@@ -891,6 +891,17 @@ describe("Studio file-change SSE", () => {
|
||||
return { projectDir, projectUrl };
|
||||
}
|
||||
|
||||
it("leaves no timer behind once the tab disconnects, so a stopping CLI can exit", async () => {
|
||||
server = createStudioServer({ projectDir: tmpProject() });
|
||||
const timeouts = vi.spyOn(globalThis, "setTimeout");
|
||||
const [stream] = await subscribe(1);
|
||||
await stream!.cancel();
|
||||
|
||||
const keepAlive = timeouts.mock.calls.filter(([, ms]) => (ms ?? 0) >= 30_000);
|
||||
timeouts.mockRestore();
|
||||
expect(keepAlive).toEqual([]);
|
||||
});
|
||||
|
||||
it("marks a notes write as not affecting the preview, so the tab does not reload", async () => {
|
||||
const { projectDir } = await previewedProject();
|
||||
const [stream] = await subscribe(1);
|
||||
|
||||
@@ -1034,14 +1034,8 @@ export function createStudioServer(options: StudioServerOptions): StudioServer {
|
||||
if (shouldWatchProjectFile(changedPath)) listener(changedPath);
|
||||
};
|
||||
watcher.addListener(wrappedListener);
|
||||
stream.onAbort(() => watcher.removeListener(wrappedListener));
|
||||
try {
|
||||
while (true) {
|
||||
await stream.sleep(30000);
|
||||
}
|
||||
} finally {
|
||||
watcher.removeListener(wrappedListener);
|
||||
}
|
||||
if (!stream.aborted) await new Promise<void>((tabClosed) => stream.onAbort(tabClosed));
|
||||
watcher.removeListener(wrappedListener);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user