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