@utoo/pack 1.2.13 → 1.3.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,17 +1,23 @@
1
+ /**
2
+ * Dev server implementation using Hono + @hono/node-server + @hono/node-ws.
3
+ * Keeps the same public API as dev.ts; do not remove dev.ts until this is verified.
4
+ */
5
+ import { serve as honoServe } from "@hono/node-server";
6
+ import { serveStatic } from "@hono/node-server/serve-static";
7
+ import { createNodeWebSocket } from "@hono/node-ws";
1
8
  import fs from "fs";
2
- import http from "http";
9
+ import getPort from "get-port";
10
+ import { Hono } from "hono";
3
11
  import https from "https";
4
- import { isIPv6 } from "net";
5
12
  import path from "path";
6
- import send from "send";
7
- import url from "url";
8
- import { resolveBundleOptions } from "../config/webpackCompat.js";
13
+ import { resolveBundleOptions, } from "../config/webpackCompat.js";
9
14
  import { createHotReloader } from "../core/hmr.js";
10
15
  import { blockStdout, getPackPath } from "../utils/common.js";
11
16
  import { findRootDir } from "../utils/findRoot.js";
12
17
  import { createSelfSignedCertificate } from "../utils/mkcert.js";
13
18
  import { printServerInfo } from "../utils/printServerInfo.js";
14
19
  import { xcodeProfilingReady } from "../utils/xcodeProfile.js";
20
+ // --- Path helpers (same logic as dev.ts, not exported) ---
15
21
  function parsePath(pathStr) {
16
22
  const hashIndex = pathStr.indexOf("#");
17
23
  const queryIndex = pathStr.indexOf("?");
@@ -35,18 +41,13 @@ function pathHasPrefix(pathStr, prefix) {
35
41
  return pathname === prefix || pathname.startsWith(prefix + "/");
36
42
  }
37
43
  function removePathPrefix(pathStr, prefix) {
38
- // If the path doesn't start with the prefix we can return it as is.
39
44
  if (!pathHasPrefix(pathStr, prefix)) {
40
45
  return pathStr;
41
46
  }
42
- // Remove the prefix from the path via slicing.
43
47
  const withoutPrefix = pathStr.slice(prefix.length);
44
- // If the path without the prefix starts with a `/` we can return it as is.
45
48
  if (withoutPrefix.startsWith("/")) {
46
49
  return withoutPrefix;
47
50
  }
48
- // If the path without the prefix doesn't start with a `/` we need to add it
49
- // back to the path to make sure it's a valid path.
50
51
  return `/${withoutPrefix}`;
51
52
  }
52
53
  function normalizedPublicPath(publicPath) {
@@ -56,387 +57,173 @@ function normalizedPublicPath(publicPath) {
56
57
  }
57
58
  try {
58
59
  if (URL.canParse(escapedPublicPath)) {
59
- const url = new URL(escapedPublicPath).toString();
60
- return url.endsWith("/") ? url.slice(0, -1) : url;
60
+ const u = new URL(escapedPublicPath).toString();
61
+ return u.endsWith("/") ? u.slice(0, -1) : u;
61
62
  }
62
63
  }
63
64
  catch (_a) { }
64
65
  return `/${escapedPublicPath}`;
65
66
  }
66
- export function serve(options, projectPath, rootPath, serverOptions) {
67
- const bundleOptions = resolveBundleOptions(options, projectPath, rootPath);
68
- if (!rootPath) {
69
- rootPath = findRootDir(projectPath || process.cwd());
70
- }
71
- return serveInternal(bundleOptions, projectPath, rootPath, serverOptions);
72
- }
73
- async function serveInternal(options, projectPath, rootPath, serverOptions) {
74
- var _a;
75
- blockStdout();
76
- if (process.env.XCODE_PROFILE) {
77
- await xcodeProfilingReady();
78
- }
79
- // FIXME: fix any type
80
- const cfgDevServer = (((_a = options.config) === null || _a === void 0 ? void 0 : _a.devServer) || {});
81
- const serverOpts = {
82
- hostname: (serverOptions === null || serverOptions === void 0 ? void 0 : serverOptions.hostname) || cfgDevServer.host || "localhost",
83
- port: typeof (serverOptions === null || serverOptions === void 0 ? void 0 : serverOptions.port) !== "undefined"
84
- ? serverOptions.port
85
- : cfgDevServer.port || 3000,
86
- https: typeof (serverOptions === null || serverOptions === void 0 ? void 0 : serverOptions.https) !== "undefined"
87
- ? serverOptions.https
88
- : cfgDevServer.https,
89
- logServerInfo: serverOptions === null || serverOptions === void 0 ? void 0 : serverOptions.logServerInfo,
90
- selfSignedCertificate: serverOptions === null || serverOptions === void 0 ? void 0 : serverOptions.selfSignedCertificate,
91
- };
92
- // If HTTPS is requested and no certificate provided, attempt to generate one.
93
- if (serverOpts.https && !serverOpts.selfSignedCertificate) {
67
+ async function resolveDevConfig(options, projectPath, rootPath, serverOptions) {
68
+ var _a, _b, _d, _e, _f, _g;
69
+ const cfgDevServer = (_b = (_a = options.config) === null || _a === void 0 ? void 0 : _a.devServer) !== null && _b !== void 0 ? _b : {};
70
+ const port = typeof (serverOptions === null || serverOptions === void 0 ? void 0 : serverOptions.port) !== "undefined"
71
+ ? serverOptions.port
72
+ : ((_d = cfgDevServer.port) !== null && _d !== void 0 ? _d : 3000);
73
+ const hostname = (_f = (_e = serverOptions === null || serverOptions === void 0 ? void 0 : serverOptions.hostname) !== null && _e !== void 0 ? _e : cfgDevServer.host) !== null && _f !== void 0 ? _f : "localhost";
74
+ const useHttps = typeof (serverOptions === null || serverOptions === void 0 ? void 0 : serverOptions.https) !== "undefined"
75
+ ? serverOptions.https
76
+ : cfgDevServer.https;
77
+ let selfSignedCertificate = serverOptions === null || serverOptions === void 0 ? void 0 : serverOptions.selfSignedCertificate;
78
+ if (useHttps && !selfSignedCertificate) {
94
79
  try {
95
- // createSelfSignedCertificate may return undefined on failure
96
- const cert = await createSelfSignedCertificate(serverOpts.hostname);
80
+ const cert = await createSelfSignedCertificate(hostname);
97
81
  if (cert)
98
- serverOpts.selfSignedCertificate = cert;
82
+ selfSignedCertificate = cert;
99
83
  }
100
- catch (e) {
101
- // ignore and fall back to http if certificate generation fails
84
+ catch (_h) {
85
+ // fall back to http
102
86
  }
103
87
  }
104
- await startServer(serverOpts, {
88
+ const bundleOptions = {
105
89
  ...options,
106
90
  config: {
107
91
  ...options.config,
108
92
  devServer: {
109
93
  hot: true,
110
- ...(options.config.devServer || {}),
94
+ ...(((_g = options.config) === null || _g === void 0 ? void 0 : _g.devServer) || {}),
111
95
  },
112
96
  },
113
97
  packPath: getPackPath(),
114
- }, projectPath || process.cwd(), rootPath);
115
- }
116
- export async function startServer(serverOptions, bundleOptions, projectPath, rootPath) {
117
- let { port, hostname, selfSignedCertificate } = serverOptions;
118
- process.title = "utoopack-dev-server";
119
- let handlersReady = () => { };
120
- let handlersError = () => { };
121
- let handlersPromise = new Promise((resolve, reject) => {
122
- handlersReady = resolve;
123
- handlersError = reject;
124
- });
125
- let requestHandler = async (req, res) => {
126
- if (handlersPromise) {
127
- await handlersPromise;
128
- return requestHandler(req, res);
129
- }
130
- throw new Error("Invariant request handler was not setup");
131
98
  };
132
- let upgradeHandler = async (req, socket, head) => {
133
- if (handlersPromise) {
134
- await handlersPromise;
135
- return upgradeHandler(req, socket, head);
136
- }
137
- throw new Error("Invariant upgrade handler was not setup");
138
- };
139
- async function requestListener(req, res) {
140
- try {
141
- if (handlersPromise) {
142
- await handlersPromise;
143
- handlersPromise = undefined;
144
- }
145
- await requestHandler(req, res);
146
- }
147
- catch (err) {
148
- res.statusCode = 500;
149
- res.end("Internal Server Error");
150
- console.error(`Failed to handle request for ${req.url}`);
151
- console.error(err);
152
- }
99
+ const projectPathResolved = projectPath || process.cwd();
100
+ const rootPathResolved = rootPath !== null && rootPath !== void 0 ? rootPath : projectPathResolved;
101
+ const actualPort = await getPort({ port, host: hostname });
102
+ if (actualPort !== port) {
103
+ console.warn(`Port ${port} is in use, using available port ${actualPort} instead.`);
153
104
  }
154
- const server = selfSignedCertificate
155
- ? https.createServer({
156
- key: fs.readFileSync(selfSignedCertificate.key),
157
- cert: fs.readFileSync(selfSignedCertificate.cert),
158
- }, requestListener)
159
- : http.createServer(requestListener);
160
- server.on("upgrade", async (req, socket, head) => {
161
- try {
162
- await upgradeHandler(req, socket, head);
163
- }
164
- catch (err) {
165
- socket.destroy();
166
- console.error(`Failed to handle request for ${req.url}`);
167
- console.error(err);
168
- }
169
- });
170
- let portRetryCount = 0;
171
- const originalPort = port;
172
- server.on("error", (err) => {
173
- if (port && err.code === "EADDRINUSE" && portRetryCount < 10) {
174
- port += 1;
175
- portRetryCount += 1;
176
- server.listen(port, hostname);
177
- }
178
- else {
179
- console.error(`Failed to start server`);
180
- console.error(err);
181
- process.exit(1);
182
- }
183
- });
184
- await new Promise((resolve) => {
185
- server.on("listening", async () => {
186
- const addr = server.address();
187
- const actualHostname = formatHostname(typeof addr === "object"
188
- ? (addr === null || addr === void 0 ? void 0 : addr.address) || hostname || "localhost"
189
- : addr);
190
- const formattedHostname = !hostname || actualHostname === "0.0.0.0"
191
- ? "localhost"
192
- : actualHostname === "[::]"
193
- ? "[::1]"
194
- : formatHostname(hostname);
195
- port = typeof addr === "object" ? (addr === null || addr === void 0 ? void 0 : addr.port) || port : port;
196
- if (portRetryCount) {
197
- console.warn(`Port ${originalPort} is in use, using available port ${port} instead.`);
198
- }
199
- if (serverOptions.logServerInfo !== false) {
200
- printServerInfo(serverOptions.https ? "https" : "http", formattedHostname, port);
201
- }
202
- try {
203
- let cleanupStarted = false;
204
- let closeUpgraded = null;
205
- const cleanup = () => {
206
- if (cleanupStarted) {
207
- return;
208
- }
209
- cleanupStarted = true;
210
- (async () => {
211
- console.debug("start-server process cleanup");
212
- await new Promise((res) => {
213
- server.close((err) => {
214
- if (err)
215
- console.error(err);
216
- res();
217
- });
218
- server.closeAllConnections();
219
- closeUpgraded === null || closeUpgraded === void 0 ? void 0 : closeUpgraded();
220
- });
221
- console.debug("start-server process cleanup finished");
222
- process.exit(0);
223
- })();
224
- };
225
- const exception = (err) => {
226
- console.error(err);
227
- };
228
- process.on("SIGINT", cleanup);
229
- process.on("SIGTERM", cleanup);
230
- process.on("rejectionHandled", () => { });
231
- process.on("uncaughtException", exception);
232
- process.on("unhandledRejection", exception);
233
- const initResult = await initialize(bundleOptions, projectPath, rootPath);
234
- requestHandler = initResult.requestHandler;
235
- upgradeHandler = initResult.upgradeHandler;
236
- closeUpgraded = initResult.closeUpgraded;
237
- handlersReady();
238
- }
239
- catch (err) {
240
- handlersError();
241
- console.error(err);
242
- process.exit(1);
105
+ const serveOptsBase = {
106
+ port: actualPort,
107
+ hostname,
108
+ ...(useHttps && selfSignedCertificate
109
+ ? {
110
+ createServer: https.createServer,
111
+ serverOptions: {
112
+ key: fs.readFileSync(selfSignedCertificate.key),
113
+ cert: fs.readFileSync(selfSignedCertificate.cert),
114
+ },
243
115
  }
244
- resolve();
245
- });
246
- server.listen(port, hostname);
247
- });
248
- }
249
- export async function initialize(bundleOptions, projectPath, rootPath) {
250
- process.env.NODE_ENV = "development";
251
- const hotReloader = await createHotReloader(bundleOptions, projectPath, rootPath);
252
- await hotReloader.start();
253
- const requestHandlerImpl = async (req, res) => {
254
- req.on("error", console.error);
255
- res.on("error", console.error);
256
- const handleRequest = async () => {
257
- var _a, _b;
258
- if (!(req.method === "GET" || req.method === "HEAD")) {
259
- res.setHeader("Allow", ["GET", "HEAD"]);
260
- res.statusCode = 405;
261
- res.end();
262
- }
263
- const distRoot = path.resolve(projectPath, ((_a = bundleOptions.config.output) === null || _a === void 0 ? void 0 : _a.path) || "./dist");
264
- const publicPath = (_b = bundleOptions.config.output) === null || _b === void 0 ? void 0 : _b.publicPath;
265
- try {
266
- const reqUrl = req.url || "";
267
- let requestPath = url.parse(reqUrl).pathname || "";
268
- if (publicPath && publicPath !== "runtime") {
269
- const normalizedPrefix = normalizedPublicPath(publicPath);
270
- const isAbsoluteUrl = normalizedPrefix.startsWith("http://") ||
271
- normalizedPrefix.startsWith("https://");
272
- if (!isAbsoluteUrl && normalizedPrefix) {
273
- if (pathHasPrefix(requestPath, normalizedPrefix)) {
274
- requestPath = removePathPrefix(requestPath, normalizedPrefix);
275
- }
276
- }
277
- }
278
- return await serveStatic(req, res, requestPath, { root: distRoot });
279
- }
280
- catch (err) {
281
- res.setHeader("Cache-Control", "private, no-cache, no-store, max-age=0, must-revalidate");
282
- res.statusCode = 404;
283
- res.end();
284
- }
285
- };
286
- try {
287
- await handleRequest();
288
- }
289
- catch (err) {
290
- res.statusCode = 500;
291
- res.end("Internal Server Error");
292
- }
293
- };
294
- let requestHandler = requestHandlerImpl;
295
- const logError = async (type, err) => {
296
- if (type === "unhandledRejection") {
297
- console.error("unhandledRejection: ", err);
298
- }
299
- else if (type === "uncaughtException") {
300
- console.error("uncaughtException: ", err);
301
- }
302
- };
303
- process.on("uncaughtException", logError.bind(null, "uncaughtException"));
304
- process.on("unhandledRejection", logError.bind(null, "unhandledRejection"));
305
- const upgradeHandler = async (req, socket, head) => {
306
- var _a;
307
- try {
308
- const isHMRRequest = (_a = req.url) === null || _a === void 0 ? void 0 : _a.includes("turbopack-hmr");
309
- if (isHMRRequest) {
310
- hotReloader.onHMR(req, socket, head);
311
- }
312
- else {
313
- socket.end();
314
- }
315
- }
316
- catch (err) {
317
- console.error("Error handling upgrade request", err);
318
- socket.end();
319
- }
116
+ : {}),
320
117
  };
321
118
  return {
322
- requestHandler,
323
- upgradeHandler,
324
- closeUpgraded() {
325
- hotReloader.close();
326
- },
119
+ bundleOptions,
120
+ projectPathResolved,
121
+ rootPathResolved,
122
+ serveOptsBase,
327
123
  };
328
124
  }
329
- export async function pipeToNodeResponse(readable, res, waitUntilForEnd) {
330
- try {
331
- const { errored, destroyed } = res;
332
- if (errored || destroyed)
333
- return;
334
- const controller = createAbortController(res);
335
- const writer = createWriterFromResponse(res, waitUntilForEnd);
336
- await readable.pipeTo(writer, { signal: controller.signal });
337
- }
338
- catch (err) {
339
- if (isAbortError(err))
340
- return;
341
- throw new Error("failed to pipe response", { cause: err });
342
- }
343
- }
344
- export function createAbortController(response) {
345
- const controller = new AbortController();
346
- response.once("close", () => {
347
- if (response.writableFinished)
348
- return;
349
- controller.abort(new ResponseAborted());
350
- });
351
- return controller;
352
- }
353
- export function isAbortError(e) {
354
- return (e === null || e === void 0 ? void 0 : e.name) === "AbortError" || (e === null || e === void 0 ? void 0 : e.name) === ResponseAbortedName;
355
- }
356
- export const ResponseAbortedName = "ResponseAborted";
357
- export class ResponseAborted extends Error {
358
- constructor() {
359
- super(...arguments);
360
- this.name = ResponseAbortedName;
125
+ // --- Entry ---
126
+ export function serve(options, projectPath, rootPath, serverOptions) {
127
+ const bundleOptions = resolveBundleOptions(options, projectPath, rootPath);
128
+ if (!rootPath) {
129
+ rootPath = findRootDir(projectPath || process.cwd());
361
130
  }
131
+ return runDev(bundleOptions, projectPath, rootPath, serverOptions);
362
132
  }
363
- function createWriterFromResponse(res, waitUntilForEnd) {
364
- let started = false;
365
- let drained = new DetachedPromise();
366
- function onDrain() {
367
- drained.resolve();
133
+ const HMR_PATH = "/turbopack-hmr";
134
+ async function runDev(options, projectPath, rootPath, serverOptions) {
135
+ var _a, _b, _d, _e;
136
+ blockStdout();
137
+ process.title = "utoopack-dev-server";
138
+ if (process.env.XCODE_PROFILE) {
139
+ await xcodeProfilingReady();
368
140
  }
369
- res.on("drain", onDrain);
370
- res.once("close", () => {
371
- res.off("drain", onDrain);
372
- drained.resolve();
373
- });
374
- const finished = new DetachedPromise();
375
- res.once("finish", () => {
376
- finished.resolve();
377
- });
378
- return new WritableStream({
379
- write: async (chunk) => {
380
- if (!started) {
381
- started = true;
382
- res.flushHeaders();
383
- }
141
+ process.env.NODE_ENV = "development";
142
+ const { bundleOptions, projectPathResolved, rootPathResolved, serveOptsBase, } = await resolveDevConfig(options, projectPath, rootPath, serverOptions);
143
+ const hotReloader = await createHotReloader(bundleOptions, projectPathResolved, rootPathResolved);
144
+ await hotReloader.start();
145
+ const distRoot = path.resolve(projectPathResolved, ((_b = (_a = options.config) === null || _a === void 0 ? void 0 : _a.output) === null || _b === void 0 ? void 0 : _b.path) || "./dist");
146
+ const publicPath = (_e = (_d = options.config) === null || _d === void 0 ? void 0 : _d.output) === null || _e === void 0 ? void 0 : _e.publicPath;
147
+ // Skip prefix stripping for "runtime" and when publicPath is absent (match dev.ts).
148
+ const normalizedPrefix = publicPath && publicPath !== "runtime"
149
+ ? normalizedPublicPath(publicPath)
150
+ : "";
151
+ const app = new Hono();
152
+ const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app });
153
+ const rewriteRequestPath = (reqPath) => {
154
+ if (!normalizedPrefix)
155
+ return reqPath;
156
+ // Absolute-URL publicPath: do not rewrite (match dev.ts).
157
+ if (normalizedPrefix.startsWith("http://") ||
158
+ normalizedPrefix.startsWith("https://")) {
159
+ return reqPath;
160
+ }
161
+ if (pathHasPrefix(reqPath, normalizedPrefix)) {
162
+ return removePathPrefix(reqPath, normalizedPrefix);
163
+ }
164
+ return reqPath;
165
+ };
166
+ // HMR WebSocket route must be registered before "/*" so it is not handled by serveStatic
167
+ app.get(HMR_PATH, upgradeWebSocket((_c) => ({
168
+ onOpen(_ev, ws) {
169
+ hotReloader.registerClient(ws);
170
+ },
171
+ onMessage(ev, ws) {
384
172
  try {
385
- const ok = res.write(chunk);
386
- if ("flush" in res && typeof res.flush === "function") {
387
- res.flush();
388
- }
389
- if (!ok) {
390
- await drained.promise;
391
- drained = new DetachedPromise();
392
- }
173
+ const data = typeof ev.data === "string" ? ev.data : ev.data.toString();
174
+ hotReloader.handleClientMessage(ws, data);
393
175
  }
394
176
  catch (err) {
395
- res.end();
396
- throw new Error("failed to write chunk to response", { cause: err });
177
+ console.error("HMR message error", err);
397
178
  }
398
179
  },
399
- abort: (err) => {
400
- if (res.writableFinished)
401
- return;
402
- res.destroy(err);
180
+ onClose(_ev, ws) {
181
+ hotReloader.unregisterClient(ws);
403
182
  },
404
- close: async () => {
405
- if (waitUntilForEnd) {
406
- await waitUntilForEnd;
407
- }
408
- if (res.writableFinished)
409
- return;
410
- res.end();
411
- return finished.promise;
183
+ onError(err) {
184
+ console.error("HMR WebSocket error", err);
412
185
  },
186
+ })));
187
+ // GET handles HEAD automatically in Hono; serveStatic serves both
188
+ app.get("/*", serveStatic({
189
+ root: distRoot,
190
+ rewriteRequestPath,
191
+ }));
192
+ app.all("*", (c) => c.body(null, 405, { Allow: "GET, HEAD" }));
193
+ const server = honoServe({
194
+ ...serveOptsBase,
195
+ fetch: app.fetch,
413
196
  });
414
- }
415
- export class DetachedPromise {
416
- constructor() {
417
- let resolve;
418
- let reject;
419
- this.promise = new Promise((res, rej) => {
420
- resolve = res;
421
- reject = rej;
422
- });
423
- this.resolve = resolve;
424
- this.reject = reject;
197
+ injectWebSocket(server);
198
+ if ((serverOptions === null || serverOptions === void 0 ? void 0 : serverOptions.logServerInfo) !== false) {
199
+ const scheme = serveOptsBase.serverOptions ? "https" : "http";
200
+ const displayHost = serveOptsBase.hostname === "0.0.0.0"
201
+ ? "localhost"
202
+ : serveOptsBase.hostname;
203
+ printServerInfo(scheme, displayHost, serveOptsBase.port);
425
204
  }
426
- }
427
- export function serveStatic(req, res, path, opts) {
428
- return new Promise((resolve, reject) => {
429
- send(req, path, opts)
430
- .on("directory", () => {
431
- const err = new Error("No directory access");
432
- err.code = "ENOENT";
433
- reject(err);
434
- })
435
- .on("error", reject)
436
- .pipe(res)
437
- .on("finish", resolve);
438
- });
439
- }
440
- export function formatHostname(hostname) {
441
- return isIPv6(hostname) ? `[${hostname}]` : hostname;
205
+ const cleanup = () => {
206
+ hotReloader.close();
207
+ // We always create HTTP/1.1 server (http or https), so closeAllConnections exists; Hono's
208
+ // ServerType union includes HTTP/2, so TS does not narrow. Use runtime check to satisfy types.
209
+ if ("closeAllConnections" in server &&
210
+ typeof server.closeAllConnections === "function") {
211
+ server.closeAllConnections();
212
+ }
213
+ server.close((err) => {
214
+ if (err) {
215
+ console.error(err);
216
+ process.exit(1);
217
+ }
218
+ process.exit(0);
219
+ });
220
+ };
221
+ const exception = (err) => {
222
+ console.error(err);
223
+ };
224
+ process.on("SIGINT", cleanup);
225
+ process.on("SIGTERM", cleanup);
226
+ process.on("rejectionHandled", () => { });
227
+ process.on("uncaughtException", exception);
228
+ process.on("unhandledRejection", exception);
442
229
  }
package/esm/core/hmr.d.ts CHANGED
@@ -13,6 +13,11 @@ export interface WebpackStats {
13
13
  toJson(options?: any): any;
14
14
  toString(options?: any): string;
15
15
  }
16
+ /** Client handle for HMR: any object with send(data) usable as Set/WeakMap key (e.g. ws WebSocket or hono WSContext). */
17
+ export interface WSLike {
18
+ send(data: string): void;
19
+ close(code?: number, reason?: string): void;
20
+ }
16
21
  export interface HotReloaderInterface {
17
22
  turbopackProject?: Project;
18
23
  serverStats: WebpackStats | null;
@@ -20,9 +25,18 @@ export interface HotReloaderInterface {
20
25
  clearHmrServerError(): void;
21
26
  start(): Promise<void>;
22
27
  send(action: HMR_ACTION_TYPES): void;
28
+ /**
29
+ * @deprecated Used by legacy dev server (dev-legacy.ts). Prefer registerClient / unregisterClient / handleClientMessage (e.g. dev.ts).
30
+ */
23
31
  onHMR(req: IncomingMessage, socket: Duplex, head: Buffer, onUpgrade?: (client: {
24
32
  send(data: string): void;
25
33
  }) => void): void;
34
+ /** Register a WebSocket client (e.g. from @hono/node-ws upgradeWebSocket). Call unregisterClient on close. */
35
+ registerClient(ws: WSLike): void;
36
+ /** Unregister and cleanup subscriptions for a client. */
37
+ unregisterClient(ws: WSLike): void;
38
+ /** Handle a message from a client (JSON string). */
39
+ handleClientMessage(ws: WSLike, data: string): void;
26
40
  buildFallbackError(): Promise<void>;
27
41
  close(): void;
28
42
  }