weifuwu 0.19.8 → 0.19.10

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.
Files changed (111) hide show
  1. package/README.md +63 -70
  2. package/cli/template/app.ts +3 -8
  3. package/cli/template/ui/{page.tsx → app/page.tsx} +2 -2
  4. package/cli/template/ui/components/Greeting.tsx +1 -1
  5. package/cli.ts +4 -11
  6. package/dist/auth.d.ts +8 -0
  7. package/dist/cli.js +5 -5
  8. package/dist/compile.d.ts +2 -0
  9. package/dist/cors.d.ts +10 -0
  10. package/dist/env.d.ts +2 -0
  11. package/dist/html-shell.d.ts +1 -0
  12. package/dist/index.d.ts +7 -10
  13. package/dist/index.js +823 -721
  14. package/dist/live.d.ts +4 -1
  15. package/dist/logger.d.ts +5 -0
  16. package/dist/rate-limit.d.ts +2 -2
  17. package/dist/router.d.ts +5 -3
  18. package/dist/ssr.d.ts +5 -1
  19. package/dist/stream.d.ts +0 -1
  20. package/dist/tailwind.d.ts +3 -1
  21. package/package.json +3 -3
  22. package/dist/agent/migrate.d.ts +0 -6
  23. package/dist/dist/agent/client.d.ts +0 -2
  24. package/dist/dist/agent/index.d.ts +0 -2
  25. package/dist/dist/agent/migrate.d.ts +0 -6
  26. package/dist/dist/agent/rest.d.ts +0 -13
  27. package/dist/dist/agent/run.d.ts +0 -17
  28. package/dist/dist/agent/types.d.ts +0 -51
  29. package/dist/dist/ai/workflow.d.ts +0 -14
  30. package/dist/dist/analytics.d.ts +0 -15
  31. package/dist/dist/client-locale.d.ts +0 -5
  32. package/dist/dist/client-pref.d.ts +0 -3
  33. package/dist/dist/client-state.d.ts +0 -22
  34. package/dist/dist/client-theme.d.ts +0 -7
  35. package/dist/dist/compress.d.ts +0 -6
  36. package/dist/dist/cookie.d.ts +0 -12
  37. package/dist/dist/deploy/config.d.ts +0 -2
  38. package/dist/dist/deploy/gateway.d.ts +0 -2
  39. package/dist/dist/deploy/index.d.ts +0 -4
  40. package/dist/dist/deploy/manager.d.ts +0 -16
  41. package/dist/dist/deploy/process.d.ts +0 -14
  42. package/dist/dist/deploy/types.d.ts +0 -62
  43. package/dist/dist/head.d.ts +0 -6
  44. package/dist/dist/helmet.d.ts +0 -18
  45. package/dist/dist/iii/client.d.ts +0 -2
  46. package/dist/dist/iii/index.d.ts +0 -4
  47. package/dist/dist/iii/register-worker.d.ts +0 -10
  48. package/dist/dist/iii/rest.d.ts +0 -3
  49. package/dist/dist/iii/stream.d.ts +0 -82
  50. package/dist/dist/iii/types.d.ts +0 -133
  51. package/dist/dist/iii/worker.d.ts +0 -2
  52. package/dist/dist/iii/ws.d.ts +0 -29
  53. package/dist/dist/index.js +0 -8180
  54. package/dist/dist/messager/agent.d.ts +0 -6
  55. package/dist/dist/messager/client.d.ts +0 -2
  56. package/dist/dist/messager/index.d.ts +0 -2
  57. package/dist/dist/messager/migrate.d.ts +0 -2
  58. package/dist/dist/messager/rest.d.ts +0 -15
  59. package/dist/dist/messager/types.d.ts +0 -56
  60. package/dist/dist/messager/ws.d.ts +0 -14
  61. package/dist/dist/preferences.d.ts +0 -14
  62. package/dist/dist/react.d.ts +0 -12
  63. package/dist/dist/react.js +0 -637
  64. package/dist/dist/request-id.d.ts +0 -6
  65. package/dist/dist/seo.d.ts +0 -39
  66. package/dist/dist/ssr/compile.d.ts +0 -2
  67. package/dist/dist/ssr/error-boundary.d.ts +0 -2
  68. package/dist/dist/ssr/index.d.ts +0 -7
  69. package/dist/dist/ssr/index.js +0 -933
  70. package/dist/dist/ssr/layout.d.ts +0 -2
  71. package/dist/dist/ssr/live.d.ts +0 -6
  72. package/dist/dist/ssr/not-found.d.ts +0 -2
  73. package/dist/dist/ssr/ssr.d.ts +0 -2
  74. package/dist/dist/ssr/stream.d.ts +0 -14
  75. package/dist/dist/ssr/tailwind.d.ts +0 -2
  76. package/dist/dist/tenant/client.d.ts +0 -2
  77. package/dist/dist/tenant/graphql.d.ts +0 -3
  78. package/dist/dist/tenant/index.d.ts +0 -2
  79. package/dist/dist/tenant/migrate.d.ts +0 -6
  80. package/dist/dist/tenant/rest.d.ts +0 -3
  81. package/dist/dist/tenant/schema.d.ts +0 -5
  82. package/dist/dist/tenant/types.d.ts +0 -48
  83. package/dist/dist/tenant/utils.d.ts +0 -10
  84. package/dist/dist/types.d.ts +0 -19
  85. package/dist/dist/use-flash-message.d.ts +0 -1
  86. package/dist/i18n.d.ts +0 -6
  87. package/dist/logdb/migrate.d.ts +0 -5
  88. package/dist/messager/migrate.d.ts +0 -2
  89. package/dist/middleware.d.ts +0 -21
  90. package/dist/opencode/migrate.d.ts +0 -2
  91. package/dist/postgres/migrate.d.ts +0 -3
  92. package/dist/postgres/table.d.ts +0 -4
  93. package/dist/root-layout.d.ts +0 -4
  94. package/dist/tenant/migrate.d.ts +0 -6
  95. package/dist/tsx-instance.d.ts +0 -43
  96. package/dist/tsx.d.ts +0 -8
  97. package/dist/user/migrate.d.ts +0 -6
  98. package/dist/workflow/engine.d.ts +0 -7
  99. package/dist/workflow/index.d.ts +0 -8
  100. package/dist/workflow/llm.d.ts +0 -10
  101. package/dist/workflow/nodes.d.ts +0 -10
  102. package/dist/workflow/reference.d.ts +0 -3
  103. package/dist/workflow/route.d.ts +0 -11
  104. package/dist/workflow/sse.d.ts +0 -2
  105. package/dist/workflow/tool.d.ts +0 -8
  106. package/dist/workflow/types.d.ts +0 -86
  107. /package/cli/template/ui/{app.css → app/globals.css} +0 -0
  108. /package/cli/template/ui/{layout.tsx → app/layout.tsx} +0 -0
  109. /package/opencode/ui/{app.css → app/globals.css} +0 -0
  110. /package/opencode/ui/{layout.tsx → app/layout.tsx} +0 -0
  111. /package/opencode/ui/{page.tsx → app/page.tsx} +0 -0
package/dist/index.js CHANGED
@@ -1,10 +1,17 @@
1
1
  // env.ts
2
- import { readFileSync, existsSync } from "node:fs";
2
+ import { readFileSync } from "node:fs";
3
3
  import { resolve } from "node:path";
4
+ function isDev() {
5
+ return process.env.NODE_ENV === "development";
6
+ }
4
7
  function loadEnv(path2) {
5
8
  const filePath = resolve(process.cwd(), path2 ?? ".env");
6
- if (!existsSync(filePath)) return;
7
- const content = readFileSync(filePath, "utf-8");
9
+ let content;
10
+ try {
11
+ content = readFileSync(filePath, "utf-8");
12
+ } catch {
13
+ return;
14
+ }
8
15
  for (const line of content.split("\n")) {
9
16
  const trimmed = line.trim();
10
17
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -17,7 +24,7 @@ function loadEnv(path2) {
17
24
  if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
18
25
  value = value.slice(1, -1);
19
26
  } else {
20
- const commentIdx = value.indexOf(" #");
27
+ const commentIdx = value.search(/\s#/);
21
28
  if (commentIdx !== -1) {
22
29
  value = value.slice(0, commentIdx).trimEnd();
23
30
  }
@@ -28,24 +35,24 @@ function loadEnv(path2) {
28
35
 
29
36
  // serve.ts
30
37
  import http from "node:http";
38
+ var HttpError = class extends Error {
39
+ status;
40
+ constructor(message, status) {
41
+ super(message);
42
+ this.status = status;
43
+ this.name = "HttpError";
44
+ }
45
+ };
31
46
  async function readBody(req, maxSize) {
32
47
  if (maxSize) {
33
48
  const cl = parseInt(req.headers["content-length"] ?? "0", 10);
34
- if (cl > maxSize) {
35
- const err = new Error("Request body too large");
36
- err.status = 413;
37
- throw err;
38
- }
49
+ if (cl > maxSize) throw new HttpError("Request body too large", 413);
39
50
  }
40
51
  const chunks = [];
41
52
  let total = 0;
42
53
  for await (const chunk of req) {
43
54
  total += chunk.byteLength;
44
- if (maxSize && total > maxSize) {
45
- const err = new Error("Request body too large");
46
- err.status = 413;
47
- throw err;
48
- }
55
+ if (maxSize && total > maxSize) throw new HttpError("Request body too large", 413);
49
56
  chunks.push(chunk);
50
57
  }
51
58
  return Buffer.concat(chunks);
@@ -92,7 +99,7 @@ async function sendResponse(res, response) {
92
99
  res.end();
93
100
  }
94
101
  async function createTestServer(handler) {
95
- const server = serve(handler, { port: 0 });
102
+ const server = serve(handler, { port: 0, shutdown: false });
96
103
  await server.ready;
97
104
  return { server, url: `http://localhost:${server.port}` };
98
105
  }
@@ -106,7 +113,7 @@ function serve(handler, options) {
106
113
  const response = await handler(request, { params: {}, query });
107
114
  await sendResponse(res, response);
108
115
  } catch (err) {
109
- if (err?.status === 413) {
116
+ if (err instanceof HttpError && err.status === 413) {
110
117
  res.writeHead(413, { "Content-Type": "text/plain" });
111
118
  res.end("Request Body Too Large");
112
119
  return;
@@ -122,6 +129,7 @@ function serve(handler, options) {
122
129
  const ready = new Promise((r) => {
123
130
  resolveReady = r;
124
131
  });
132
+ let shutdownHandler = null;
125
133
  if (options?.shutdown !== false) {
126
134
  let shuttingDown = false;
127
135
  const shutdown = () => {
@@ -130,6 +138,7 @@ function serve(handler, options) {
130
138
  server.close();
131
139
  process.exit(0);
132
140
  };
141
+ shutdownHandler = shutdown;
133
142
  process.on("SIGTERM", shutdown);
134
143
  process.on("SIGINT", shutdown);
135
144
  }
@@ -155,13 +164,19 @@ function serve(handler, options) {
155
164
  }
156
165
  server.on("error", (err) => {
157
166
  console.error("Failed to start server:", err.message);
158
- process.exit(1);
167
+ server.close();
168
+ resolveReady();
159
169
  });
160
170
  server.listen(port, hostname, () => {
161
171
  resolveReady();
162
172
  });
163
173
  return {
164
174
  stop: () => {
175
+ if (shutdownHandler) {
176
+ process.off("SIGTERM", shutdownHandler);
177
+ process.off("SIGINT", shutdownHandler);
178
+ shutdownHandler = null;
179
+ }
165
180
  server.close();
166
181
  },
167
182
  ready,
@@ -190,75 +205,50 @@ var createWsNode = () => ({
190
205
  children: /* @__PURE__ */ new Map(),
191
206
  middlewares: []
192
207
  });
193
- var getTrieNode = (node, segment) => {
194
- if (segment.startsWith(":")) {
195
- if (!node.children.has(":")) {
196
- const child2 = createTrieNode();
197
- child2.param = segment.slice(1);
198
- node.children.set(":", child2);
199
- }
200
- const child = node.children.get(":");
201
- if (child.param !== segment.slice(1)) {
202
- throw new Error(
203
- `Param name conflict: ":${child.param}" already registered at this path position, cannot register ":"${segment.slice(1)}"`
204
- );
205
- }
206
- return child;
207
- }
208
- if (!node.children.has(segment)) {
209
- node.children.set(segment, createTrieNode());
210
- }
211
- return node.children.get(segment);
212
- };
213
- var matchTrieNode = (node, segment, params) => {
214
- if (node.children.has(segment)) return node.children.get(segment);
215
- if (node.children.has(":")) {
216
- const child = node.children.get(":");
217
- if (child.param) params[child.param] = segment;
218
- return child;
208
+ function createParamChild(node, segment, createNode) {
209
+ const paramName = segment.slice(1);
210
+ if (!node.children.has(":")) {
211
+ const child2 = createNode();
212
+ child2.param = paramName;
213
+ node.children.set(":", child2);
214
+ }
215
+ const child = node.children.get(":");
216
+ if (child.param !== paramName) {
217
+ throw new Error(
218
+ `Param name conflict: ":${child.param}" already registered, cannot register ":"${paramName}"`
219
+ );
219
220
  }
220
- return null;
221
- };
222
- var getWsNode = (node, segment) => {
223
- if (segment === "*") {
221
+ return child;
222
+ }
223
+ function getOrCreateChild(node, segment, createNode, allowWildcard) {
224
+ if (allowWildcard && segment === "*") {
224
225
  node.wildcard = true;
225
226
  return node;
226
227
  }
227
- if (segment.startsWith(":")) {
228
- if (!node.children.has(":")) {
229
- const child2 = createWsNode();
230
- child2.param = segment.slice(1);
231
- node.children.set(":", child2);
232
- }
233
- const child = node.children.get(":");
234
- if (child.param !== segment.slice(1)) {
235
- throw new Error(
236
- `Param name conflict: ":${child.param}" already registered at this path position`
237
- );
238
- }
239
- return child;
240
- }
241
- if (!node.children.has(segment)) {
242
- node.children.set(segment, createWsNode());
243
- }
228
+ if (segment.startsWith(":")) return createParamChild(node, segment, createNode);
229
+ if (!node.children.has(segment)) node.children.set(segment, createNode());
244
230
  return node.children.get(segment);
245
- };
246
- var matchWsNode = (node, segment, params) => {
231
+ }
232
+ function matchChild(node, segment, params, allowWildcard = false) {
247
233
  if (node.children.has(segment)) return node.children.get(segment);
248
234
  if (node.children.has(":")) {
249
235
  const child = node.children.get(":");
250
236
  if (child.param) params[child.param] = segment;
251
237
  return child;
252
238
  }
253
- if (node.wildcard) return node;
239
+ if (allowWildcard && node.wildcard) return node;
254
240
  return null;
255
- };
241
+ }
256
242
  var Router = class _Router {
257
243
  root = createTrieNode();
258
244
  wsRoot = createWsNode();
259
245
  globalMws = [];
260
246
  errorHandler;
261
- wss = new WebSocketServer({ noServer: true });
247
+ _wss;
248
+ get wss() {
249
+ if (!this._wss) this._wss = new WebSocketServer({ noServer: true });
250
+ return this._wss;
251
+ }
262
252
  use(arg1, arg2) {
263
253
  if (typeof arg1 === "string") {
264
254
  if (arg2 instanceof _Router) {
@@ -266,12 +256,10 @@ var Router = class _Router {
266
256
  } else if (typeof arg2 === "function") {
267
257
  let node = this.root;
268
258
  for (const segment of this.splitPath(arg1)) {
269
- node = getTrieNode(node, segment);
259
+ node = getOrCreateChild(node, segment, createTrieNode, false);
270
260
  }
271
261
  node.pathMws.push(arg2);
272
262
  }
273
- } else if (arg1 instanceof _Router) {
274
- this._mountRouter("/", arg1);
275
263
  } else if (typeof arg1 === "function") {
276
264
  this.globalMws.push(arg1);
277
265
  }
@@ -308,7 +296,7 @@ var Router = class _Router {
308
296
  route(method, path2, ...args) {
309
297
  const last = args[args.length - 1];
310
298
  if (last instanceof _Router) {
311
- this._mountRouter(path2, last);
299
+ this._mountRouter(path2, last, args.slice(0, -1));
312
300
  return this;
313
301
  }
314
302
  const handler = args.pop();
@@ -326,7 +314,7 @@ var Router = class _Router {
326
314
  if (middlewares.length > 0) node.middlewares.set(method, middlewares);
327
315
  return this;
328
316
  }
329
- node = getTrieNode(node, segment);
317
+ node = getOrCreateChild(node, segment, createTrieNode, false);
330
318
  }
331
319
  node.handlers.set(method, handler);
332
320
  if (middlewares.length > 0) node.middlewares.set(method, middlewares);
@@ -338,16 +326,16 @@ var Router = class _Router {
338
326
  const segments = this.splitPath(path2);
339
327
  let node = this.wsRoot;
340
328
  for (const segment of segments) {
341
- node = getWsNode(node, segment);
329
+ node = getOrCreateChild(node, segment, createWsNode, true);
342
330
  }
343
331
  node.handler = handler;
344
- if (middlewares.length > 0) node.middlewares = middlewares;
332
+ node.middlewares = middlewares;
345
333
  return this;
346
334
  }
347
335
  handler() {
348
336
  return (req, ctx) => {
349
337
  const url = new URL(req.url);
350
- return this.handle(req, ctx, this.splitPath(url.pathname), Object.fromEntries(url.searchParams));
338
+ return this.handle(req, ctx, this.splitPath(url.pathname));
351
339
  };
352
340
  }
353
341
  websocketHandler() {
@@ -357,86 +345,80 @@ var Router = class _Router {
357
345
  const url = new URL(req.url ?? "/", "http://localhost");
358
346
  const segments = url.pathname.split("/").filter(Boolean);
359
347
  const match = router.matchWsTrie(wsRoot, segments);
360
- if (match) {
361
- const query = Object.fromEntries(url.searchParams);
362
- const webReq = new Request(url.href, {
363
- method: req.method ?? "GET",
364
- headers: Object.fromEntries(
365
- Object.entries(req.headers).map(([k, v]) => [k, Array.isArray(v) ? v.join(", ") : v ?? ""])
366
- )
367
- });
368
- const ctx = { params: match.params, query };
369
- if (match.middlewares.length === 0) {
370
- upgradeSocket(router.wss, req, socket, head, match.handler, ctx);
371
- return;
372
- }
373
- let index = 0;
374
- const dispatch = async (innerReq, ctx2) => {
375
- if (index < match.middlewares.length) {
376
- const mw = match.middlewares[index++];
377
- return mw(innerReq, ctx2, dispatch);
378
- }
379
- return await new Promise((resolve15) => {
380
- try {
381
- upgradeSocket(router.wss, req, socket, head, match.handler, ctx2);
382
- resolve15(new Response(null, { status: 101 }));
383
- } catch {
384
- socket.destroy();
385
- resolve15(new Response("WebSocket upgrade failed", { status: 500 }));
386
- }
387
- });
388
- };
389
- Promise.resolve(dispatch(webReq, ctx)).then((result) => {
390
- if (result.status !== 101) {
391
- sendHttpResponseOnSocket(socket, result);
392
- }
393
- }).catch(() => {
394
- socket.destroy();
395
- });
348
+ if (!match) {
349
+ socket.destroy();
396
350
  return;
397
351
  }
398
- socket.destroy();
352
+ const query = Object.fromEntries(url.searchParams);
353
+ const ctx = { params: match.params, query };
354
+ const allMws = router.globalMws.length === 0 && match.middlewares.length === 0 ? [] : [...router.globalMws, ...match.middlewares];
355
+ if (allMws.length === 0) {
356
+ upgradeSocket(router.wss, req, socket, head, match.handler, ctx);
357
+ return;
358
+ }
359
+ const finalHandler = () => {
360
+ try {
361
+ upgradeSocket(router.wss, req, socket, head, match.handler, ctx);
362
+ } catch {
363
+ socket.destroy();
364
+ return new Response("WebSocket upgrade failed", { status: 500 });
365
+ }
366
+ return new Response(null, { status: 200 });
367
+ };
368
+ const webReq = new Request(url.href, {
369
+ method: req.method ?? "GET",
370
+ headers: nodeReqHeadersToRecord(req.headers)
371
+ });
372
+ void router.runChain(allMws, finalHandler, webReq, ctx).then((result) => {
373
+ if (result.status >= 400) {
374
+ sendHttpResponseOnSocket(socket, result);
375
+ }
376
+ }).catch(() => {
377
+ socket.destroy();
378
+ });
399
379
  };
400
380
  }
401
- _mountRouter(prefix, sub) {
381
+ _mountRouter(prefix, sub, extraMws = []) {
402
382
  const base = prefix === "/" ? "" : prefix.replace(/\/$/, "");
403
383
  const mountMw = (req, ctx, next) => {
404
384
  ctx.mountPath = (ctx.mountPath || "") + base;
405
385
  return next(req, ctx);
406
386
  };
407
- this.globalMws.push(...sub.globalMws);
387
+ const allExtra = extraMws.length === 0 && sub.globalMws.length === 0 ? [mountMw] : [mountMw, ...extraMws, ...sub.globalMws];
408
388
  const routes = [];
409
389
  this._collect(sub.root, "", routes, []);
410
390
  for (const { method, path: path2, handler, middlewares } of routes) {
411
- this.route(method, base + path2, mountMw, ...sub.globalMws, ...middlewares, handler);
391
+ this.route(method, base + path2, ...allExtra, ...middlewares, handler);
412
392
  }
413
393
  const wsRoutes = [];
414
394
  this._collectWs(sub.wsRoot, "", wsRoutes);
415
- for (const { path: path2, handler } of wsRoutes) {
416
- this.ws(base + path2, handler);
395
+ for (const { path: path2, handler, middlewares } of wsRoutes) {
396
+ this.ws(base + path2, ...allExtra, ...middlewares, handler);
417
397
  }
418
398
  }
399
+ mergeMws(base, extra) {
400
+ if (base.length === 0) return extra.length === 0 ? base : extra;
401
+ if (extra.length === 0) return base;
402
+ return [...base, ...extra];
403
+ }
419
404
  _collect(node, prefix, result, pathMwsAcc) {
420
- const mws = [...pathMwsAcc, ...node.pathMws];
421
- if (node.wildcard) {
422
- for (const [method, handler] of node.handlers) {
423
- result.push({ method, path: prefix + "/*", handler, middlewares: [...mws, ...node.middlewares.get(method) || []] });
424
- }
425
- } else {
426
- for (const [method, handler] of node.handlers) {
427
- result.push({ method, path: prefix || "/", handler, middlewares: [...mws, ...node.middlewares.get(method) || []] });
428
- }
405
+ const mws = this.mergeMws(pathMwsAcc, node.pathMws);
406
+ for (const [method, handler] of node.handlers) {
407
+ const rmws = node.middlewares.get(method) || [];
408
+ const suffix = node.wildcard ? "/*" : "";
409
+ result.push({ method, path: (prefix || "/") + suffix, handler, middlewares: this.mergeMws(mws, rmws) });
429
410
  }
430
411
  for (const [seg, child] of node.children) {
431
- if (seg === ":") this._collect(child, prefix + "/:" + child.param, result, mws);
432
- else this._collect(child, prefix + "/" + seg, result, mws);
412
+ const next = seg === ":" ? `/:${child.param}` : `/${seg}`;
413
+ this._collect(child, prefix + next, result, mws);
433
414
  }
434
415
  }
435
- _collectWs(node, prefix, result) {
436
- if (node.handler) result.push({ path: prefix || "/", handler: node.handler });
416
+ _collectWs(node, prefix, result, pathMwsAcc = []) {
417
+ const mws = this.mergeMws(pathMwsAcc, node.middlewares);
418
+ if (node.handler) result.push({ path: prefix || "/", handler: node.handler, middlewares: mws });
437
419
  for (const [seg, child] of node.children) {
438
- if (seg === ":") this._collectWs(child, prefix + "/:" + child.param, result);
439
- else this._collectWs(child, prefix + "/" + seg, result);
420
+ const next = seg === ":" ? `/:${child.param}` : `/${seg}`;
421
+ this._collectWs(child, prefix + next, result, mws);
440
422
  }
441
423
  }
442
424
  splitPath(path2) {
@@ -460,8 +442,7 @@ var Router = class _Router {
460
442
  }
461
443
  }
462
444
  const segment = segments[i];
463
- if (!segment) break;
464
- const next = matchTrieNode(node, segment, params);
445
+ const next = matchChild(node, segment, params, false);
465
446
  if (!next) {
466
447
  if (wildcardHandler) {
467
448
  params["*"] = segments.slice(wildcardIdx).join("/");
@@ -486,56 +467,77 @@ var Router = class _Router {
486
467
  params["*"] = segments.slice(wildcardIdx).join("/");
487
468
  return { handler: wildcardHandler, middlewares: wildcardMws, pathMws, params };
488
469
  }
470
+ if (node.handlers.size > 0) {
471
+ return { middlewares: [], pathMws, params, allowedMethods: [...node.handlers.keys()].filter((k) => k !== "*") };
472
+ }
489
473
  return null;
490
474
  }
491
475
  matchWsTrie(root, segments) {
492
476
  let node = root;
493
477
  const params = {};
494
478
  for (const segment of segments) {
495
- const next = matchWsNode(node, segment, params);
479
+ const next = matchChild(node, segment, params, true);
496
480
  if (!next) return null;
497
481
  node = next;
498
482
  }
499
483
  return node.handler ? { handler: node.handler, middlewares: node.middlewares, params } : null;
500
484
  }
501
- async handle(req, ctx, segments, query) {
485
+ async handleError(e, req, ctx) {
486
+ const err = e instanceof Error ? e : new Error(String(e));
487
+ console.error(err);
488
+ return this.errorHandler ? await this.errorHandler(err, req, ctx) : new Response("Internal Server Error", { status: 500 });
489
+ }
490
+ async handle(req, ctx, segments) {
502
491
  const match = this.matchTrie(req.method, segments);
503
- if (match?.handler) {
504
- const { handler, middlewares: routeMws, pathMws, params } = match;
505
- const allMws = this.globalMws.length + pathMws.length + routeMws.length === 0 ? [] : [...this.globalMws, ...pathMws, ...routeMws];
506
- const ctxWithMatch = { ...ctx, params: { ...ctx.params, ...params } };
507
- try {
508
- return await this.runChain(allMws, handler, req, ctxWithMatch);
509
- } catch (e) {
510
- const err = e instanceof Error ? e : new Error(String(e));
511
- console.error(err);
512
- return this.errorHandler ? this.errorHandler(err, req, ctxWithMatch) : new Response("Internal Server Error", { status: 500 });
492
+ if (match) {
493
+ Object.assign(ctx.params, match.params);
494
+ if (match.handler) {
495
+ const { handler, middlewares: routeMws, pathMws } = match;
496
+ const mws = this.mergeMws(this.mergeMws(this.globalMws, pathMws), routeMws);
497
+ try {
498
+ return await this.runChain(mws, handler, req, ctx);
499
+ } catch (e) {
500
+ return this.handleError(e, req, ctx);
501
+ }
502
+ }
503
+ if (match.allowedMethods && match.allowedMethods.length > 0) {
504
+ if (this.globalMws.length > 0) {
505
+ try {
506
+ return await this.runChain(this.globalMws, () => new Response("Method Not Allowed", {
507
+ status: 405,
508
+ headers: { "Allow": match.allowedMethods.join(", ") }
509
+ }), req, ctx);
510
+ } catch (e) {
511
+ return this.handleError(e, req, ctx);
512
+ }
513
+ }
514
+ return new Response("Method Not Allowed", {
515
+ status: 405,
516
+ headers: { "Allow": match.allowedMethods.join(", ") }
517
+ });
513
518
  }
514
519
  }
515
520
  if (this.globalMws.length > 0) {
516
521
  try {
517
- const delegate = () => new Response("Not Found", { status: 404 });
518
- return await this.runChain(this.globalMws, delegate, req, ctx);
522
+ return await this.runChain(this.globalMws, () => new Response("Not Found", { status: 404 }), req, ctx);
519
523
  } catch (e) {
520
- const err = e instanceof Error ? e : new Error(String(e));
521
- console.error(err);
522
- return this.errorHandler ? this.errorHandler(err, req, ctx) : new Response("Internal Server Error", { status: 500 });
524
+ return this.handleError(e, req, ctx);
523
525
  }
524
526
  }
525
527
  return new Response("Not Found", { status: 404 });
526
528
  }
527
529
  async runChain(middlewares, finalHandler, req, ctx) {
528
- let index = 0;
529
- const dispatch = async (req2, ctx2) => {
530
- if (index < middlewares.length) {
531
- const mw = middlewares[index++];
532
- return mw ? await mw(req2, ctx2, dispatch) : new Response("Middleware error", { status: 500 });
533
- }
534
- return await finalHandler(req2, ctx2);
535
- };
536
- return dispatch(req, ctx);
530
+ if (middlewares.length === 0) return await finalHandler(req, ctx);
531
+ return await runChainLoop(middlewares, 0, finalHandler, req, ctx);
537
532
  }
538
533
  };
534
+ function runChainLoop(middlewares, index, finalHandler, req, ctx) {
535
+ if (index < middlewares.length) {
536
+ const mw = middlewares[index];
537
+ return Promise.resolve(mw(req, ctx, (r, c) => runChainLoop(middlewares, index + 1, finalHandler, r, c)));
538
+ }
539
+ return Promise.resolve(finalHandler(req, ctx));
540
+ }
539
541
  function upgradeSocket(wss, req, socket, head, handler, ctx) {
540
542
  wss.handleUpgrade(req, socket, head, (ws) => {
541
543
  if (handler.open) {
@@ -552,6 +554,13 @@ function upgradeSocket(wss, req, socket, head, handler, ctx) {
552
554
  });
553
555
  });
554
556
  }
557
+ function nodeReqHeadersToRecord(headers) {
558
+ const result = {};
559
+ for (const [k, v] of Object.entries(headers)) {
560
+ if (v !== void 0) result[k] = Array.isArray(v) ? v.join(", ") : v;
561
+ }
562
+ return result;
563
+ }
555
564
  function sendHttpResponseOnSocket(socket, response) {
556
565
  const statusLine = `HTTP/1.1 ${response.status} ${response.statusText}`;
557
566
  const headerLines = [statusLine];
@@ -562,8 +571,8 @@ function sendHttpResponseOnSocket(socket, response) {
562
571
  headerLines.push("");
563
572
  const headerStr = headerLines.join("\r\n");
564
573
  response.arrayBuffer().then((buf) => {
565
- const body = Buffer.from(buf);
566
- socket.write(headerStr + "\r\n" + body.toString());
574
+ socket.write(headerStr + "\r\n");
575
+ if (buf.byteLength > 0) socket.write(Buffer.from(buf));
567
576
  socket.end();
568
577
  }).catch(() => {
569
578
  socket.write(headerStr + "\r\n");
@@ -601,7 +610,7 @@ function setCtx(value) {
601
610
  }
602
611
  var TsxContext = createContext(DEFAULT_CTX);
603
612
 
604
- // middleware.ts
613
+ // logger.ts
605
614
  function logger(options) {
606
615
  return async (req, ctx, next) => {
607
616
  const start = Date.now();
@@ -622,6 +631,8 @@ function logger(options) {
622
631
  }
623
632
  };
624
633
  }
634
+
635
+ // cors.ts
625
636
  function cors(options) {
626
637
  const opts = {
627
638
  origin: "*",
@@ -670,6 +681,8 @@ function cors(options) {
670
681
  return Promise.resolve(next(req, ctx)).then((res) => setCORSHeaders(res, acao));
671
682
  };
672
683
  }
684
+
685
+ // auth.ts
673
686
  function auth(options) {
674
687
  if (!options.token && !options.verify && !options.proxy) {
675
688
  throw new Error("auth() requires at least one of: token, verify, or proxy");
@@ -756,7 +769,6 @@ function auth(options) {
756
769
  }
757
770
 
758
771
  // static.ts
759
- import { createHash } from "node:crypto";
760
772
  import { open, realpath } from "node:fs/promises";
761
773
  import { extname, resolve as resolve2, normalize, sep } from "node:path";
762
774
  import { Readable } from "node:stream";
@@ -776,13 +788,13 @@ function serveStatic(root, options) {
776
788
  let fileHandle;
777
789
  try {
778
790
  fileHandle = await open(filePath, "r");
779
- let stat = await fileHandle.stat();
791
+ let stat2 = await fileHandle.stat();
780
792
  const realPath = await realpath(filePath);
781
793
  if (!realPath.startsWith(rootDir + sep) && realPath !== rootDir) {
782
794
  await fileHandle.close();
783
795
  return new Response("Forbidden", { status: 403 });
784
796
  }
785
- if (stat.isDirectory()) {
797
+ if (stat2.isDirectory()) {
786
798
  await fileHandle.close();
787
799
  const indexFile = opts.index ?? "index.html";
788
800
  filePath = resolve2(filePath, indexFile);
@@ -790,29 +802,29 @@ function serveStatic(root, options) {
790
802
  return new Response("Forbidden", { status: 403 });
791
803
  }
792
804
  fileHandle = await open(filePath, "r");
793
- stat = await fileHandle.stat();
794
- if (!stat.isFile()) {
805
+ stat2 = await fileHandle.stat();
806
+ if (!stat2.isFile()) {
795
807
  await fileHandle.close();
796
808
  return new Response("Not Found", { status: 404 });
797
809
  }
798
810
  }
799
811
  const mimeType = MIME_TYPES[extname(filePath).toLowerCase()] ?? "application/octet-stream";
800
- const etag = `"${createHash("md5").update(`${stat.size}-${stat.mtimeMs}`).digest("hex")}"`;
812
+ const etag = `"${stat2.ino}-${stat2.size}-${stat2.mtimeMs}"`;
801
813
  const ifNoneMatch = req.headers.get("if-none-match");
802
814
  if (ifNoneMatch === etag) {
803
815
  await fileHandle.close();
804
816
  return new Response(null, { status: 304 });
805
817
  }
806
818
  const ifModifiedSince = req.headers.get("if-modified-since");
807
- if (ifModifiedSince && stat.mtimeMs <= new Date(ifModifiedSince).getTime()) {
819
+ if (ifModifiedSince && stat2.mtimeMs <= new Date(ifModifiedSince).getTime()) {
808
820
  await fileHandle.close();
809
821
  return new Response(null, { status: 304 });
810
822
  }
811
823
  const headers = {
812
824
  "Content-Type": mimeType,
813
- "Content-Length": String(stat.size),
825
+ "Content-Length": String(stat2.size),
814
826
  "ETag": etag,
815
- "Last-Modified": stat.mtime.toUTCString(),
827
+ "Last-Modified": stat2.mtime.toUTCString(),
816
828
  "Cache-Control": opts.immutable ? `public, max-age=${opts.maxAge ?? 31536e3}, immutable` : `public, max-age=${opts.maxAge ?? 0}`
817
829
  };
818
830
  const readStream = fileHandle.createReadStream();
@@ -921,15 +933,9 @@ function validate(schemas) {
921
933
  issues.push({ path: ["body"], message: "Request body is required" });
922
934
  } else {
923
935
  const ct = req.headers.get("content-type") ?? "";
936
+ const shouldParseJson = ct.includes("application/json") || ct.includes("text/") || ct.includes("*/json") || !ct.includes("multipart/form-data") && !ct.includes("application/x-www-form-urlencoded");
924
937
  let bodyValue = bodyText;
925
- if (ct.includes("application/json") || ct.includes("text/") || ct.includes("*/json")) {
926
- try {
927
- bodyValue = JSON.parse(bodyText);
928
- } catch {
929
- }
930
- } else if (ct.includes("application/x-www-form-urlencoded") || ct.includes("multipart/form-data")) {
931
- bodyValue = bodyText;
932
- } else {
938
+ if (shouldParseJson) {
933
939
  try {
934
940
  bodyValue = JSON.parse(bodyText);
935
941
  } catch {
@@ -982,6 +988,9 @@ function getCookies(req) {
982
988
  return cookies;
983
989
  }
984
990
  function serializeCookie(name, value, options) {
991
+ if (/[\x00-\x1F\x7F-\x9F;,]/.test(name) || /[\x00-\x1F\x7F-\x9F;,]/.test(value)) {
992
+ throw new Error(`Invalid cookie name or value: contains control characters or special chars`);
993
+ }
985
994
  const parts = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
986
995
  if (options?.maxAge != null) parts.push(`Max-Age=${options.maxAge}`);
987
996
  if (options?.expires) parts.push(`Expires=${options.expires.toUTCString()}`);
@@ -1053,8 +1062,12 @@ function upload(options) {
1053
1062
  return async (req, ctx, next) => {
1054
1063
  const ct = req.headers.get("content-type") ?? "";
1055
1064
  if (!ct.includes("multipart/form-data")) return next(req, ctx);
1056
- if (saveDir) await mkdir(saveDir, { recursive: true }).catch(() => {
1057
- });
1065
+ try {
1066
+ if (saveDir) await mkdir(saveDir, { recursive: true });
1067
+ } catch (e) {
1068
+ console.error("upload: failed to create directory", saveDir, e);
1069
+ return Response.json({ error: "Server configuration error" }, { status: 500 });
1070
+ }
1058
1071
  let formData;
1059
1072
  try {
1060
1073
  formData = await req.formData();
@@ -1084,7 +1097,7 @@ function upload(options) {
1084
1097
  buffer: saveDir ? void 0 : buf
1085
1098
  };
1086
1099
  if (saveDir) {
1087
- const safeName = value.name.replace(/[/\\\0]/g, "_");
1100
+ const safeName = value.name.replace(/[/\\\0]/g, "_").replace(/\.\./g, "_");
1088
1101
  const filePath = join(saveDir, `${randomUUID()}-${safeName}`);
1089
1102
  await writeFile(filePath, buf);
1090
1103
  uf.path = filePath;
@@ -1108,14 +1121,14 @@ function upload(options) {
1108
1121
  function rateLimit(options) {
1109
1122
  const max = options?.max ?? 100;
1110
1123
  const window2 = options?.window ?? 6e4;
1111
- const getKey = options?.key ?? ((req) => {
1112
- const forwarded = req.headers.get("x-forwarded-for");
1124
+ const getKey = options?.key ?? ((_req, _ctx) => {
1125
+ const forwarded = _req.headers.get("x-forwarded-for");
1113
1126
  if (forwarded) return forwarded.split(",")[0].trim();
1114
- const realIp = req.headers.get("x-real-ip");
1127
+ const realIp = _req.headers.get("x-real-ip");
1115
1128
  if (realIp) return realIp;
1116
- const cfIp = req.headers.get("cf-connecting-ip");
1129
+ const cfIp = _req.headers.get("cf-connecting-ip");
1117
1130
  if (cfIp) return cfIp;
1118
- return req.headers.get("x-forwarded-for") || req.headers.get("x-real-ip") || "global";
1131
+ return "global";
1119
1132
  });
1120
1133
  const message = options?.message ?? "Too Many Requests";
1121
1134
  const MAX_ENTRIES = 1e4;
@@ -1126,24 +1139,25 @@ function rateLimit(options) {
1126
1139
  if (entry.reset < now) hits.delete(key);
1127
1140
  }
1128
1141
  if (hits.size > MAX_ENTRIES) {
1129
- const toDelete = [...hits.entries()].sort((a, b) => a[1].reset - b[1].reset).slice(0, hits.size - MAX_ENTRIES);
1130
- for (const [k] of toDelete) hits.delete(k);
1142
+ const toDelete = hits.size - MAX_ENTRIES;
1143
+ let deleted = 0;
1144
+ for (const key of hits.keys()) {
1145
+ if (deleted >= toDelete) break;
1146
+ hits.delete(key);
1147
+ deleted++;
1148
+ }
1131
1149
  }
1132
1150
  }, Math.min(window2, 3e4));
1133
1151
  if (interval.unref) interval.unref();
1134
1152
  const mw = async (req, ctx, next) => {
1135
- const key = getKey(req);
1153
+ const key = getKey(req, ctx);
1136
1154
  const now = Date.now();
1137
1155
  let entry = hits.get(key);
1138
1156
  if (!entry || entry.reset < now) {
1139
1157
  hits.set(key, { count: 1, reset: now + window2 });
1140
1158
  entry = { count: 1, reset: now + window2 };
1141
1159
  const res2 = await next(req, ctx);
1142
- const headers2 = new Headers(res2.headers);
1143
- headers2.set("X-RateLimit-Limit", String(max));
1144
- headers2.set("X-RateLimit-Remaining", String(max - 1));
1145
- headers2.set("X-RateLimit-Reset", String(Math.ceil((now + window2) / 1e3)));
1146
- return new Response(res2.body, { status: res2.status, statusText: res2.statusText, headers: headers2 });
1160
+ return addRateLimitHeaders(res2, max, max - 1, now + window2);
1147
1161
  }
1148
1162
  entry.count++;
1149
1163
  const remaining = Math.max(0, max - entry.count);
@@ -1159,11 +1173,7 @@ function rateLimit(options) {
1159
1173
  });
1160
1174
  }
1161
1175
  const res = await next(req, ctx);
1162
- const headers = new Headers(res.headers);
1163
- headers.set("X-RateLimit-Limit", String(max));
1164
- headers.set("X-RateLimit-Remaining", String(remaining));
1165
- headers.set("X-RateLimit-Reset", String(Math.ceil(entry.reset / 1e3)));
1166
- return new Response(res.body, { status: res.status, statusText: res.statusText, headers });
1176
+ return addRateLimitHeaders(res, max, remaining, entry.reset);
1167
1177
  };
1168
1178
  mw.stop = () => {
1169
1179
  clearInterval(interval);
@@ -1171,9 +1181,20 @@ function rateLimit(options) {
1171
1181
  };
1172
1182
  return mw;
1173
1183
  }
1184
+ function addRateLimitHeaders(res, limit, remaining, reset) {
1185
+ const headers = new Headers(res.headers);
1186
+ headers.set("X-RateLimit-Limit", String(limit));
1187
+ headers.set("X-RateLimit-Remaining", String(remaining));
1188
+ headers.set("X-RateLimit-Reset", String(Math.ceil(reset / 1e3)));
1189
+ return new Response(res.body, { status: res.status, statusText: res.statusText, headers });
1190
+ }
1174
1191
 
1175
1192
  // compress.ts
1176
- import { constants, gzipSync, brotliCompressSync, deflateSync } from "node:zlib";
1193
+ import { constants, brotliCompress, gzip, deflate } from "node:zlib";
1194
+ import { promisify } from "node:util";
1195
+ var brotliCompressAsync = promisify(brotliCompress);
1196
+ var gzipAsync = promisify(gzip);
1197
+ var deflateAsync = promisify(deflate);
1177
1198
  function compress(options) {
1178
1199
  const level = options?.level ?? 6;
1179
1200
  const threshold = options?.threshold ?? 1024;
@@ -1190,22 +1211,23 @@ function compress(options) {
1190
1211
  if (!ct || ct.startsWith("audio/") || ct.startsWith("video/") || ct.startsWith("image/") || ct === "application/zip") {
1191
1212
  return res;
1192
1213
  }
1214
+ if (!res.body) return res;
1193
1215
  const body = await res.bytes();
1194
1216
  if (body.byteLength < threshold) return res;
1195
1217
  let compressed;
1196
- let enc;
1197
- if (encoding === "br") {
1198
- compressed = brotliCompressSync(body, { params: { [constants.BROTLI_PARAM_QUALITY]: Math.min(level, 11) } });
1199
- enc = "br";
1200
- } else if (encoding === "gzip") {
1201
- compressed = gzipSync(body, { level: Math.min(level, 9) });
1202
- enc = "gzip";
1203
- } else {
1204
- compressed = deflateSync(body, { level: Math.min(level, 9) });
1205
- enc = "deflate";
1218
+ try {
1219
+ if (encoding === "br") {
1220
+ compressed = await brotliCompressAsync(body, { params: { [constants.BROTLI_PARAM_QUALITY]: Math.min(level, 11) } });
1221
+ } else if (encoding === "gzip") {
1222
+ compressed = await gzipAsync(body, { level: Math.min(level, 9) });
1223
+ } else {
1224
+ compressed = await deflateAsync(body, { level: Math.min(level, 9) });
1225
+ }
1226
+ } catch {
1227
+ return res;
1206
1228
  }
1207
1229
  const headers = new Headers(res.headers);
1208
- headers.set("Content-Encoding", enc);
1230
+ headers.set("Content-Encoding", encoding);
1209
1231
  headers.set("Content-Length", String(compressed.byteLength));
1210
1232
  headers.delete("Content-Range");
1211
1233
  const existingVary = headers.get("Vary");
@@ -1425,9 +1447,20 @@ function graphiqlHTML(endpoint) {
1425
1447
  }
1426
1448
  function graphql(handler) {
1427
1449
  const r = new Router();
1428
- r.get("/", async (req, ctx) => {
1450
+ let cachedHandler = null;
1451
+ let cachedSchema = null;
1452
+ async function getSchema(req, ctx) {
1429
1453
  const options = await handler(req, ctx);
1454
+ if (cachedHandler === options && cachedSchema) {
1455
+ return { options, schema: cachedSchema };
1456
+ }
1430
1457
  const schema = buildSchemaFromOptions(options);
1458
+ cachedHandler = options;
1459
+ cachedSchema = schema;
1460
+ return { options, schema };
1461
+ }
1462
+ r.get("/", async (req, ctx) => {
1463
+ const { options, schema } = await getSchema(req, ctx);
1431
1464
  const url = new URL(req.url);
1432
1465
  if (options.graphiql && !url.searchParams.has("query")) {
1433
1466
  return new Response(graphiqlHTML(url.pathname), {
@@ -1442,8 +1475,7 @@ function graphql(handler) {
1442
1475
  return executeQuery(schema, params, options, req, ctx);
1443
1476
  });
1444
1477
  r.post("/", async (req, ctx) => {
1445
- const options = await handler(req, ctx);
1446
- const schema = buildSchemaFromOptions(options);
1478
+ const { options, schema } = await getSchema(req, ctx);
1447
1479
  const params = await parseParamsFromPost(req);
1448
1480
  if (!params) {
1449
1481
  return Response.json({ errors: [{ message: "Missing query" }] }, { status: 400 });
@@ -2845,7 +2877,7 @@ function createHub(opts) {
2845
2877
  }
2846
2878
  });
2847
2879
  }
2848
- function join9(key, ws) {
2880
+ function join8(key, ws) {
2849
2881
  if (!channels.has(key)) {
2850
2882
  channels.set(key, /* @__PURE__ */ new Set());
2851
2883
  redisSub?.subscribe(`${prefix}${key}`);
@@ -2857,36 +2889,57 @@ function createHub(opts) {
2857
2889
  wsKeys.set(ws, keys);
2858
2890
  }
2859
2891
  keys.add(key);
2892
+ if (typeof ws.addEventListener === "function") {
2893
+ ws.addEventListener("close", () => removeFromChannels(ws));
2894
+ ws.addEventListener("error", () => removeFromChannels(ws));
2895
+ }
2860
2896
  }
2861
- function leave(ws) {
2897
+ function removeFromChannels(ws) {
2862
2898
  const keys = wsKeys.get(ws);
2863
2899
  if (keys) {
2864
2900
  for (const key of keys) {
2865
- channels.get(key)?.delete(ws);
2901
+ const members = channels.get(key);
2902
+ if (members) {
2903
+ members.delete(ws);
2904
+ if (members.size === 0) channels.delete(key);
2905
+ }
2866
2906
  }
2867
2907
  wsKeys.delete(ws);
2868
2908
  }
2869
2909
  }
2910
+ function leave(ws) {
2911
+ removeFromChannels(ws);
2912
+ }
2870
2913
  function broadcast(key, data) {
2871
2914
  const msg = JSON.stringify(data);
2872
2915
  const members = channels.get(key);
2873
2916
  if (members) {
2917
+ const dead = [];
2874
2918
  for (const ws of members) {
2875
2919
  try {
2876
2920
  ws.send(msg);
2877
2921
  } catch {
2922
+ dead.push(ws);
2878
2923
  }
2879
2924
  }
2925
+ for (const ws of dead) removeFromChannels(ws);
2880
2926
  }
2881
2927
  redisPub?.publish(`${prefix}${key}`, msg);
2882
2928
  }
2883
2929
  async function close() {
2930
+ for (const ws of wsKeys.keys()) {
2931
+ removeFromChannels(ws);
2932
+ }
2884
2933
  channels.clear();
2934
+ wsKeys.clear();
2885
2935
  if (redisSub) {
2936
+ redisSub.removeAllListeners("message");
2886
2937
  await redisSub.quit();
2887
2938
  }
2939
+ redisPub = void 0;
2940
+ redisSub = null;
2888
2941
  }
2889
- return { join: join9, leave, broadcast, close };
2942
+ return { join: join8, leave, broadcast, close };
2890
2943
  }
2891
2944
 
2892
2945
  // queue/index.ts
@@ -3033,7 +3086,7 @@ function queue(opts) {
3033
3086
  running = true;
3034
3087
  poll();
3035
3088
  };
3036
- mw.stop = function stop() {
3089
+ mw.stop = function stop2() {
3037
3090
  running = false;
3038
3091
  epoch++;
3039
3092
  if (pollTimer) {
@@ -4697,14 +4750,14 @@ function forkApp(opts) {
4697
4750
  return { child, port: opts.port };
4698
4751
  }
4699
4752
  function stopProcess(mp, timeout = 1e4) {
4700
- return new Promise((resolve15) => {
4753
+ return new Promise((resolve14) => {
4701
4754
  const timer = setTimeout(() => {
4702
4755
  mp.child.kill("SIGKILL");
4703
- resolve15();
4756
+ resolve14();
4704
4757
  }, timeout);
4705
4758
  mp.child.on("exit", () => {
4706
4759
  clearTimeout(timer);
4707
- resolve15();
4760
+ resolve14();
4708
4761
  });
4709
4762
  mp.child.kill("SIGTERM");
4710
4763
  });
@@ -5022,21 +5075,19 @@ async function deploy(config) {
5022
5075
  // opencode/client.ts
5023
5076
  import { createOpenAI as createOpenAI3 } from "@ai-sdk/openai";
5024
5077
 
5025
- // opencode/rest.ts
5026
- import { join as join7 } from "node:path";
5027
-
5028
5078
  // ssr.ts
5029
- import { createElement } from "react";
5079
+ import { createElement as createElement3 } from "react";
5030
5080
  import { createHash as createHash3 } from "node:crypto";
5031
- import { dirname as dirname2, resolve as resolve4 } from "node:path";
5081
+ import { existsSync as existsSync4, readdirSync, statSync } from "node:fs";
5082
+ import { dirname as dirname3, join as join5, resolve as resolve6 } from "node:path";
5032
5083
  import { AsyncLocalStorage } from "node:async_hooks";
5033
5084
 
5034
5085
  // compile.ts
5035
5086
  import * as esbuild from "esbuild";
5036
- import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2 } from "node:fs";
5087
+ import { existsSync, mkdirSync, readFileSync as readFileSync2 } from "node:fs";
5037
5088
  import { join as join2, resolve as resolve3, dirname } from "node:path";
5038
5089
  import { pathToFileURL } from "node:url";
5039
- import { createHash as createHash2 } from "node:crypto";
5090
+ import { createHash } from "node:crypto";
5040
5091
  import vm from "node:vm";
5041
5092
  import { createRequire } from "node:module";
5042
5093
  var _cjsRequire = createRequire(import.meta.url);
@@ -5059,7 +5110,7 @@ function resolveAliases() {
5059
5110
  const configFiles = ["tsconfig.json", "jsconfig.json"];
5060
5111
  for (const file of configFiles) {
5061
5112
  const p = resolve3(file);
5062
- if (existsSync2(p)) {
5113
+ if (existsSync(p)) {
5063
5114
  try {
5064
5115
  const config = JSON.parse(readFileSync2(p, "utf-8"));
5065
5116
  const paths = config.compilerOptions?.paths;
@@ -5081,7 +5132,7 @@ function resolveAliases() {
5081
5132
  return {};
5082
5133
  }
5083
5134
  function id(s) {
5084
- return createHash2("md5").update(s).digest("hex").slice(0, 8);
5135
+ return createHash("md5").update(s).digest("hex").slice(0, 8);
5085
5136
  }
5086
5137
  function clearCompileCache() {
5087
5138
  cache.clear();
@@ -5090,12 +5141,13 @@ function clearCompileCache() {
5090
5141
  async function compileTsx(path2) {
5091
5142
  const absPath = resolve3(path2);
5092
5143
  if (cache.has(absPath)) return cache.get(absPath);
5093
- mkdirSync(OUT_DIR, { recursive: true });
5144
+ const outDir = resolve3(OUT_DIR);
5145
+ mkdirSync(outDir, { recursive: true });
5094
5146
  const hash = id(absPath);
5095
- const outPath = join2(OUT_DIR, hash + ".js");
5147
+ const outPath = join2(outDir, hash + ".js");
5096
5148
  await esbuild.build({
5097
5149
  entryPoints: { [hash]: absPath },
5098
- outdir: OUT_DIR,
5150
+ outdir: outDir,
5099
5151
  format: "esm",
5100
5152
  platform: "node",
5101
5153
  jsx: "automatic",
@@ -5139,7 +5191,7 @@ async function compileTsxDev(path2) {
5139
5191
  return mod;
5140
5192
  }
5141
5193
  function compile(path2) {
5142
- return process.env.NODE_ENV !== "production" ? compileTsxDev(path2) : compileTsx(path2);
5194
+ return isDev() ? compileTsxDev(path2) : compileTsx(path2);
5143
5195
  }
5144
5196
  var vendorBundle = null;
5145
5197
  async function compileVendorBundle() {
@@ -5212,11 +5264,10 @@ function getPublicEnv() {
5212
5264
  return _publicEnv;
5213
5265
  }
5214
5266
  function buildHeadPayload(opts) {
5215
- const { ctx, compiledTailwindCss, isDev: isDev2 } = opts;
5216
- const rb = opts.rootBase || "";
5267
+ const { ctx, base, compiledTailwindCss, isDev: isDev3 } = opts;
5217
5268
  let result = "";
5218
- if (isDev2) {
5219
- const vUrl = `${rb}/__wfw/v/bundle`;
5269
+ if (isDev3) {
5270
+ const vUrl = `${base}/__wfw/v/bundle`;
5220
5271
  result += `<script type="importmap">{
5221
5272
  "imports": {
5222
5273
  "react": "${vUrl}",
@@ -5239,18 +5290,29 @@ function buildHeadPayload(opts) {
5239
5290
  }
5240
5291
  const localeData = ctx.parsed?.__localeData ?? globalThis.__LOCALE_DATA__;
5241
5292
  if (localeData && Object.keys(localeData).length > 0) {
5242
- result += `<script>window.__LOCALE_DATA__=${JSON.stringify(localeData)}</script>
5293
+ if (!_localeDataCache || _localeDataCache.data !== localeData) {
5294
+ _localeDataCache = { data: localeData, json: JSON.stringify(localeData) };
5295
+ }
5296
+ result += `<script>window.__LOCALE_DATA__=${_localeDataCache.json}</script>
5243
5297
  `;
5244
5298
  }
5245
5299
  const loaderData = opts.loaderData || {};
5246
5300
  const ctxData = {
5247
5301
  params: ctx.params,
5248
5302
  query: ctx.query,
5249
- user: ctx.user,
5250
5303
  parsed: ctx.parsed,
5251
5304
  prefs: ctx.prefs,
5252
5305
  loaderData
5253
5306
  };
5307
+ if (ctx.user && typeof ctx.user === "object") {
5308
+ const safeUser = {};
5309
+ for (const k of ["id", "name", "email", "role", "avatar"]) {
5310
+ if (k in ctx.user) {
5311
+ safeUser[k] = ctx.user[k];
5312
+ }
5313
+ }
5314
+ ctxData.user = safeUser;
5315
+ }
5254
5316
  const publicEnv = getPublicEnv();
5255
5317
  if (Object.keys(publicEnv).length > 0) {
5256
5318
  ctxData.env = publicEnv;
@@ -5269,6 +5331,7 @@ function buildBodyScripts(opts) {
5269
5331
  }
5270
5332
  return parts.join("\n");
5271
5333
  }
5334
+ var _localeDataCache = null;
5272
5335
  function streamResponse(reactStream, opts) {
5273
5336
  const decoder = new TextDecoder2();
5274
5337
  const encoder2 = new TextEncoder2();
@@ -5315,9 +5378,8 @@ function streamResponse(reactStream, opts) {
5315
5378
  const body = buildBodyScripts(opts);
5316
5379
  if (body) controller.enqueue(encoder2.encode("\n" + body));
5317
5380
  if (opts.isDev) {
5318
- const rb = opts.rootBase || "";
5319
- const wsUrl = `${rb}/__weifuwu/livereload`;
5320
- const hbUrl = `${rb}/__wfw/h/`;
5381
+ const wsUrl = `${opts.base}/__weifuwu/livereload`;
5382
+ const hbUrl = `${opts.base}/__wfw/h/`;
5321
5383
  controller.enqueue(encoder2.encode(
5322
5384
  `
5323
5385
  <script>(function(){var ws=new WebSocket((location.protocol==='https:'?'wss:':'ws:')+'//'+location.host+'${wsUrl}');var t=0;ws.onmessage=function(e){try{var m=JSON.parse(e.data);if(m.type==='component'){if(m.entry&&m.entry!==window.__WFW_ENTRY)return;import('${hbUrl}'+m.hash+'?'+Date.now()).catch(function(){location.reload()});if(m.css){var s=document.querySelector('style[data-lr]')||function(){var x=document.createElement('style');x.setAttribute('data-lr','');document.head.appendChild(x);return x}();s.textContent=m.css}return}if(m.type==='css'){var s=document.querySelector('style[data-lr]')||function(){var x=document.createElement('style');x.setAttribute('data-lr','');document.head.appendChild(x);return x}();s.textContent=m.css;return}}catch(_){}if(e.data==='reload'&&Date.now()-t>1e3){t=Date.now();location.reload()}};ws.onclose=function(){if(Date.now()-t>1e3){t=Date.now();setTimeout(function(){location.reload()},500)}}})()</script>`
@@ -5340,272 +5402,117 @@ function streamResponse(reactStream, opts) {
5340
5402
  // ssr-entries.ts
5341
5403
  var ssrEntries = /* @__PURE__ */ new Map();
5342
5404
 
5343
- // ssr.ts
5344
- var als = new AsyncLocalStorage();
5345
- __registerAls(() => als.getStore());
5346
- var isDev = process.env.NODE_ENV !== "production";
5347
- var bundleCache = /* @__PURE__ */ new Map();
5348
- var _bundleDirty = false;
5349
- function markClientBundleDirty() {
5350
- _bundleDirty = true;
5405
+ // tailwind.ts
5406
+ import { createHash as createHash2 } from "node:crypto";
5407
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "node:fs";
5408
+ import { join as join3, relative, resolve as resolve4 } from "node:path";
5409
+ var extraSources = /* @__PURE__ */ new Set();
5410
+ var cssCache = /* @__PURE__ */ new Map();
5411
+ function tailwindContext(dir) {
5412
+ const cssDir = resolve4(dir);
5413
+ const cssPath = join3(cssDir, "app", "globals.css");
5414
+ return async (req, ctx, next) => {
5415
+ if (!cssCache.has(cssPath)) {
5416
+ await compileTailwindCss(cssPath, cssDir);
5417
+ }
5418
+ const entry = cssCache.get(cssPath);
5419
+ ctx.compiledTailwindCss = entry.css;
5420
+ const base = (ctx.mountPath || "").replace(/\/$/, "");
5421
+ ctx.tailwindCssUrl = base ? `${base}/__wfw/style/${entry.hash}.css` : `/__wfw/style/${entry.hash}.css`;
5422
+ return next(req, ctx);
5423
+ };
5351
5424
  }
5352
- function getBundle(key) {
5353
- if (_bundleDirty) {
5354
- bundleCache.clear();
5355
- _bundleDirty = false;
5425
+ function tailwindRouter(dir) {
5426
+ const cssDir = resolve4(dir);
5427
+ const cssPath = join3(cssDir, "app", "globals.css");
5428
+ const r = new Router();
5429
+ r.get("/__wfw/style/:hash.css", async (req, ctx) => {
5430
+ if (!cssCache.has(cssPath)) {
5431
+ await compileTailwindCss(cssPath, cssDir);
5432
+ }
5433
+ const entry = cssCache.get(cssPath);
5434
+ if (!entry) return new Response("", { status: 404 });
5435
+ return new Response(entry.css, {
5436
+ headers: { "content-type": "text/css; charset=utf-8" }
5437
+ });
5438
+ });
5439
+ return r;
5440
+ }
5441
+ async function compileTailwindCss(cssPath, cssDir) {
5442
+ try {
5443
+ if (!existsSync2(cssPath)) {
5444
+ mkdirSync2(cssDir, { recursive: true });
5445
+ writeFileSync(cssPath, '@import "tailwindcss"\n', "utf-8");
5446
+ }
5447
+ const { default: tailwindPlugin } = await import("@tailwindcss/postcss");
5448
+ const { default: postcss } = await import("postcss");
5449
+ let src = readFileSync3(cssPath, "utf-8");
5450
+ src = `@source "./";
5451
+ ${src}`;
5452
+ for (const srcDir of extraSources) {
5453
+ const rel = relative(cssDir, srcDir) || ".";
5454
+ src = `@source "${rel.startsWith(".") ? rel : "./" + rel}";
5455
+ ${src}`;
5456
+ }
5457
+ const result = await postcss([tailwindPlugin()]).process(src, { from: cssPath });
5458
+ const hash = createHash2("md5").update(result.css).digest("hex").slice(0, 8);
5459
+ cssCache.set(cssPath, { css: result.css, hash });
5460
+ return result.css;
5461
+ } catch (err) {
5462
+ console.warn("Tailwind CSS processing failed:", err.message);
5463
+ return "";
5356
5464
  }
5357
- return bundleCache.get(key);
5358
5465
  }
5359
- function setBundle(key, buf) {
5360
- if (_bundleDirty) {
5361
- bundleCache.clear();
5362
- _bundleDirty = false;
5466
+
5467
+ // live.ts
5468
+ import chokidar from "chokidar";
5469
+ import { existsSync as existsSync3 } from "node:fs";
5470
+ import { dirname as dirname2, join as join4, resolve as resolve5 } from "node:path";
5471
+ var clients = /* @__PURE__ */ new Set();
5472
+ var hotBundleCache = /* @__PURE__ */ new Map();
5473
+ var hotKeys = [];
5474
+ var MAX_HOT = 10;
5475
+ function setHot(hash, code) {
5476
+ if (!hotBundleCache.has(hash)) {
5477
+ hotKeys.push(hash);
5478
+ if (hotKeys.length > MAX_HOT) {
5479
+ const old = hotKeys.shift();
5480
+ hotBundleCache.delete(old);
5481
+ }
5363
5482
  }
5364
- bundleCache.set(key, buf);
5483
+ hotBundleCache.set(hash, code);
5365
5484
  }
5366
- function id2(s) {
5367
- return createHash3("md5").update(s).digest("hex").slice(0, 8);
5485
+ function broadcastReload() {
5486
+ for (const ws of clients) {
5487
+ try {
5488
+ ws.send("reload");
5489
+ } catch {
5490
+ clients.delete(ws);
5491
+ }
5492
+ }
5368
5493
  }
5369
- function serializeLoaderData(ctx) {
5370
- const ld = ctx.loaderData;
5371
- return ld && typeof ld === "object" ? ld : {};
5494
+ function broadcastCss(css) {
5495
+ const msg = JSON.stringify({ type: "css", css });
5496
+ for (const ws of clients) {
5497
+ try {
5498
+ ws.send(msg);
5499
+ } catch {
5500
+ clients.delete(ws);
5501
+ }
5502
+ }
5372
5503
  }
5373
- async function buildClientBundle(entryPath, layoutPaths) {
5374
- try {
5375
- const absEntry = resolve4(entryPath);
5376
- const absLayouts = layoutPaths.map((p) => resolve4(p));
5377
- const layoutImports = absLayouts.map((p) => `import${JSON.stringify(p)};`).join("");
5378
- const _sc = `(function(){var k='__WEIFUWU_CTX_STORE';var s=typeof globalThis!='undefined'&&globalThis[k];if(!s)return function(){};return function(v){s._ctx={...s._ctx,...v};s._snapshot={params:s._ctx.params,query:s._ctx.query,user:s._ctx.user,parsed:s._ctx.parsed,prefs:s._ctx.prefs,env:s._ctx.env};s._listeners.forEach(function(fn){fn()})}})()`;
5379
- const code = [
5380
- layoutImports,
5381
- `${isDev ? "import{createRoot}from'react-dom/client';" : "import{hydrateRoot}from'react-dom/client';"}`,
5382
- `import{createElement}from'react';`,
5383
- `import{TsxContext}from'weifuwu/react';`,
5384
- `import P from${JSON.stringify(absEntry)};`,
5385
- `var setCtx=${_sc};`,
5386
- `const c=document.getElementById('__weifuwu_root');`,
5387
- `if(window.__WEIFUWU_PROPS)setCtx({loaderData:window.__WEIFUWU_PROPS});`,
5388
- // Dev: stable proxy chain — _P → _W (stable) → actual component
5389
- isDev ? `const _W=function(props){return(_W._fn||P)(props)};_W._fn=P;const _P=function(props){return createElement(_W,props)};` : "",
5390
- // Dev: HMR handler — updates proxy + re-renders root
5391
- isDev ? `window.__WFW_ENTRY=${JSON.stringify(id2(absEntry))};window.__WFW_REFRESH=function(n){_W._fn=n;window.__WFW_ROOT.render(createElement(App))};` : "",
5392
- `function App(){`,
5393
- `const ctx=window.__WEIFUWU_CTX||{};`,
5394
- `return createElement(TsxContext.Provider,{value:ctx},`,
5395
- isDev ? `createElement(_P,null))` : `createElement(P,null))`,
5396
- `}`,
5397
- isDev ? `window.__WFW_ROOT=createRoot(c);window.__WFW_ROOT.render(createElement(App));` : `hydrateRoot(c,createElement(App));`
5398
- ].filter(Boolean).join("");
5399
- const { default: esbuild2 } = await import("esbuild");
5400
- const result = await esbuild2.build({
5401
- stdin: { contents: code, loader: "tsx", resolveDir: dirname2(absEntry) },
5402
- bundle: true,
5403
- format: "esm",
5404
- jsx: "automatic",
5405
- jsxImportSource: "react",
5406
- banner: { js: "self.process={env:{}};" },
5407
- loader: { ".node": "empty" },
5408
- external: isDev ? ["react", "react-dom", "react-dom/client", "react/jsx-runtime", "weifuwu", "weifuwu/react"] : void 0,
5409
- write: false,
5410
- minify: !isDev
5411
- });
5412
- return result.outputFiles[0].contents;
5413
- } catch (err) {
5414
- console.error("hydration bundle failed:", err);
5415
- return null;
5416
- }
5417
- }
5418
- function ssr(path2) {
5419
- const absPath = resolve4(path2);
5420
- const entryId = id2(absPath);
5421
- ssrEntries.set(entryId, { path: absPath });
5422
- const bundleKey = `/__ssr/${entryId}.js`;
5423
- const r = new Router();
5424
- r.get("/__ssr/:path", (req, ctx) => {
5425
- const buf = getBundle("/__ssr/" + ctx.params.path);
5426
- return buf ? new Response(buf, {
5427
- headers: { "content-type": "application/javascript; charset=utf-8" }
5428
- }) : new Response("", { status: 404 });
5429
- });
5430
- r.get("/", async (req, ctx) => {
5431
- const pageMod = await compile(path2);
5432
- const Component = pageMod.default;
5433
- if (!Component) return new Response("", { status: 500 });
5434
- const layouts = ctx.layoutStack || [];
5435
- const layoutComponents = layouts.map((l) => l.component);
5436
- const layoutPaths = layouts.map((l) => l.path);
5437
- const base = (ctx.mountPath || "").replace(/\/$/, "");
5438
- const loaderData = serializeLoaderData(ctx);
5439
- const ctxValue = {
5440
- params: ctx.params,
5441
- query: ctx.query,
5442
- user: ctx.user ?? {},
5443
- parsed: ctx.parsed ?? {},
5444
- prefs: ctx.prefs ?? {},
5445
- loaderData,
5446
- env: ctx.env ?? {}
5447
- };
5448
- return als.run(ctxValue, async () => {
5449
- setCtx(ctxValue);
5450
- if (ctxValue.parsed?.__localeData) {
5451
- ;
5452
- globalThis.__LOCALE_DATA__ = ctxValue.parsed.__localeData;
5453
- }
5454
- let element = createElement(
5455
- "div",
5456
- { id: "__weifuwu_root" },
5457
- createElement(
5458
- TsxContext.Provider,
5459
- { value: ctxValue },
5460
- createElement(Component, null)
5461
- )
5462
- );
5463
- if (layoutComponents.length === 0) {
5464
- element = createElement(
5465
- "html",
5466
- { lang: "en" },
5467
- createElement(
5468
- "head",
5469
- null,
5470
- createElement("meta", { charSet: "utf-8" }),
5471
- createElement("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }),
5472
- createElement("title", null, "weifuwu")
5473
- ),
5474
- createElement("body", null, element)
5475
- );
5476
- } else {
5477
- for (const L of layoutComponents.toReversed()) {
5478
- element = createElement(L, { children: element });
5479
- }
5480
- }
5481
- let bundle = null;
5482
- if (!getBundle(bundleKey)) {
5483
- const buf = await buildClientBundle(path2, layoutPaths);
5484
- if (buf) setBundle(bundleKey, buf);
5485
- }
5486
- if (getBundle(bundleKey)) {
5487
- bundle = { url: bundleKey };
5488
- }
5489
- const { renderToReadableStream } = await import("react-dom/server");
5490
- const stream = await renderToReadableStream(element);
5491
- return streamResponse(stream, {
5492
- ctx,
5493
- base,
5494
- rootBase: ctx.rootLayoutBase || "",
5495
- isDev,
5496
- bundle,
5497
- loaderData,
5498
- compiledTailwindCss: ctx.compiledTailwindCss
5499
- });
5500
- });
5501
- });
5502
- return r;
5503
- }
5504
-
5505
- // root-layout.ts
5506
- import { existsSync as existsSync5 } from "node:fs";
5507
- import { join as join5, resolve as resolve7 } from "node:path";
5508
-
5509
- // tailwind.ts
5510
- import { createHash as createHash4 } from "node:crypto";
5511
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "node:fs";
5512
- import { join as join3, relative, resolve as resolve5 } from "node:path";
5513
- var extraSources = /* @__PURE__ */ new Set();
5514
- var cssCache = /* @__PURE__ */ new Map();
5515
- function tailwind(dir) {
5516
- const cssDir = resolve5(dir);
5517
- const cssPath = join3(cssDir, "app.css");
5518
- const r = new Router();
5519
- r.use(async (req, ctx, next) => {
5520
- if (!cssCache.has(cssPath)) {
5521
- await compileTailwindCss(cssPath, cssDir);
5522
- }
5523
- const entry = cssCache.get(cssPath);
5524
- ctx.compiledTailwindCss = entry.css;
5525
- const base = (ctx.mountPath || "").replace(/\/$/, "");
5526
- ctx.tailwindCssUrl = base ? `${base}/__wfw/style/${entry.hash}.css` : `/__wfw/style/${entry.hash}.css`;
5527
- return next(req, ctx);
5528
- });
5529
- r.get("/__wfw/style/:hash.css", async (req, ctx) => {
5530
- if (!cssCache.has(cssPath)) {
5531
- await compileTailwindCss(cssPath, cssDir);
5532
- }
5533
- const entry = cssCache.get(cssPath);
5534
- if (!entry) return new Response("", { status: 404 });
5535
- return new Response(entry.css, {
5536
- headers: { "content-type": "text/css; charset=utf-8" }
5537
- });
5538
- });
5539
- return r;
5540
- }
5541
- async function compileTailwindCss(cssPath, cssDir) {
5542
- try {
5543
- if (!existsSync3(cssPath)) {
5544
- mkdirSync2(cssDir, { recursive: true });
5545
- writeFileSync(cssPath, '@import "tailwindcss"\n', "utf-8");
5546
- }
5547
- const { default: tailwindPlugin } = await import("@tailwindcss/postcss");
5548
- const { default: postcss } = await import("postcss");
5549
- let src = readFileSync3(cssPath, "utf-8");
5550
- src = `@source "./";
5551
- ${src}`;
5552
- for (const srcDir of extraSources) {
5553
- const rel = relative(cssDir, srcDir) || ".";
5554
- src = `@source "${rel.startsWith(".") ? rel : "./" + rel}";
5555
- ${src}`;
5556
- }
5557
- const result = await postcss([tailwindPlugin()]).process(src, { from: cssPath });
5558
- const hash = createHash4("md5").update(result.css).digest("hex").slice(0, 8);
5559
- cssCache.set(cssPath, { css: result.css, hash });
5560
- return result.css;
5561
- } catch (err) {
5562
- console.warn("Tailwind CSS processing failed:", err.message);
5563
- return "";
5564
- }
5565
- }
5566
-
5567
- // live.ts
5568
- import chokidar from "chokidar";
5569
- import { existsSync as existsSync4 } from "node:fs";
5570
- import { dirname as dirname3, join as join4, resolve as resolve6 } from "node:path";
5571
- var clients = /* @__PURE__ */ new Set();
5572
- var hotBundleCache = /* @__PURE__ */ new Map();
5573
- var hotKeys = [];
5574
- var MAX_HOT = 10;
5575
- function setHot(hash, code) {
5576
- if (!hotBundleCache.has(hash)) {
5577
- hotKeys.push(hash);
5578
- if (hotKeys.length > MAX_HOT) {
5579
- const old = hotKeys.shift();
5580
- hotBundleCache.delete(old);
5581
- }
5582
- }
5583
- hotBundleCache.set(hash, code);
5584
- }
5585
- function broadcastReload() {
5586
- for (const ws of clients) {
5587
- try {
5588
- ws.send("reload");
5589
- } catch {
5590
- clients.delete(ws);
5591
- }
5592
- }
5593
- }
5594
- function broadcastCss(css) {
5595
- const msg = JSON.stringify({ type: "css", css });
5596
- for (const ws of clients) {
5597
- try {
5598
- ws.send(msg);
5599
- } catch {
5600
- clients.delete(ws);
5504
+ function liveWs() {
5505
+ return {
5506
+ open(ws) {
5507
+ clients.add(ws);
5508
+ ws.on("close", () => clients.delete(ws));
5509
+ ws.on("error", () => clients.delete(ws));
5601
5510
  }
5602
- }
5511
+ };
5603
5512
  }
5604
- function liveReload(dir) {
5513
+ function liveRouter(dir) {
5605
5514
  const r = new Router();
5606
- const resolved = resolve6(dir);
5607
- const entryPath = join4(resolved, "page.tsx");
5608
- r.get("/__wfw/v/bundle", async (req, ctx) => {
5515
+ r.get("/__wfw/v/bundle", async () => {
5609
5516
  const code = await compileVendorBundle();
5610
5517
  return new Response(code, {
5611
5518
  headers: { "content-type": "application/javascript; charset=utf-8" }
@@ -5619,13 +5526,11 @@ function liveReload(dir) {
5619
5526
  headers: { "content-type": "application/javascript; charset=utf-8" }
5620
5527
  });
5621
5528
  });
5622
- r.ws("/__weifuwu/livereload", {
5623
- open(ws) {
5624
- clients.add(ws);
5625
- ws.on("close", () => clients.delete(ws));
5626
- ws.on("error", () => clients.delete(ws));
5627
- }
5628
- });
5529
+ return r;
5530
+ }
5531
+ function liveWatcher(dir) {
5532
+ const resolved = resolve5(dir);
5533
+ const entryPath = join4(resolved, "page.tsx");
5629
5534
  const watcher = chokidar.watch(dir, {
5630
5535
  ignored: /(^|[/\\])\.|node_modules|[/\\]\.weifuwu[/\\]/,
5631
5536
  ignoreInitial: true
@@ -5637,7 +5542,7 @@ function liveReload(dir) {
5637
5542
  if (entry.path === changedPath) {
5638
5543
  matched.push(entry.path);
5639
5544
  } else {
5640
- const ed = dirname3(entry.path);
5545
+ const ed = dirname2(entry.path);
5641
5546
  if (changedPath.startsWith(ed)) matched.push(entry.path);
5642
5547
  }
5643
5548
  }
@@ -5655,12 +5560,12 @@ function liveReload(dir) {
5655
5560
  }
5656
5561
  clearCompileCache();
5657
5562
  markClientBundleDirty();
5658
- const targets = existsSync4(entryPath) ? [entryPath] : findEntries(resolve6(filePath));
5563
+ const targets = existsSync3(entryPath) ? [entryPath] : findEntries(resolve5(filePath));
5659
5564
  if (targets.length === 0) return broadcastReload();
5660
5565
  try {
5661
5566
  let css;
5662
- const cssPath = join4(resolved, "app.css");
5663
- if (existsSync4(cssPath)) {
5567
+ const cssPath = join4(resolved, "app", "globals.css");
5568
+ if (existsSync3(cssPath)) {
5664
5569
  css = await compileTailwindCss(cssPath, resolved);
5665
5570
  }
5666
5571
  for (const target of targets) {
@@ -5684,40 +5589,334 @@ function liveReload(dir) {
5684
5589
  broadcastReload();
5685
5590
  }
5686
5591
  } else if (/\.css$/i.test(filePath)) {
5687
- const cssPath = join4(resolved, "app.css");
5688
- if (existsSync4(cssPath)) {
5592
+ const cssPath = join4(resolved, "app", "globals.css");
5593
+ if (existsSync3(cssPath)) {
5689
5594
  const css = await compileTailwindCss(cssPath, resolved);
5690
5595
  if (css) broadcastCss(css);
5691
5596
  }
5692
5597
  }
5693
5598
  });
5694
- r.close = () => {
5695
- watcher.close();
5696
- clients.clear();
5599
+ return {
5600
+ close: () => {
5601
+ watcher.close();
5602
+ clients.clear();
5603
+ }
5697
5604
  };
5698
- return r;
5699
5605
  }
5700
5606
 
5701
- // root-layout.ts
5702
- function rootLayout(dir) {
5703
- const r = new Router();
5704
- const resolved = resolve7(dir);
5705
- const isDev2 = process.env.NODE_ENV !== "production";
5706
- const layoutPath = join5(resolved, "layout.tsx");
5707
- r.use(async (req, ctx, next) => {
5708
- const mod = await compile(layoutPath);
5709
- if (mod?.default) ctx.layoutStack = [{ path: layoutPath, component: mod.default }];
5710
- ctx.rootLayoutBase = (ctx.mountPath || "").replace(/\/$/, "");
5607
+ // layout.ts
5608
+ function layout(path2) {
5609
+ return async (req, ctx, next) => {
5610
+ const mod = await compile(path2);
5611
+ const Component = mod.default;
5612
+ if (!Component) throw new Error(`Layout ${path2} has no default export`);
5613
+ ctx.layoutStack = [...ctx.layoutStack || [], { path: path2, component: Component }];
5711
5614
  return next(req, ctx);
5615
+ };
5616
+ }
5617
+
5618
+ // error-boundary.ts
5619
+ import { createElement as createElement2 } from "react";
5620
+
5621
+ // html-shell.ts
5622
+ import { createElement } from "react";
5623
+ function buildHtmlShell(title, bodyElement, layoutComponents) {
5624
+ if (layoutComponents.length === 0) {
5625
+ return createElement(
5626
+ "html",
5627
+ { lang: "en" },
5628
+ createElement(
5629
+ "head",
5630
+ null,
5631
+ createElement("meta", { charSet: "utf-8" }),
5632
+ createElement("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }),
5633
+ createElement("title", null, title)
5634
+ ),
5635
+ createElement("body", null, bodyElement)
5636
+ );
5637
+ }
5638
+ let element = bodyElement;
5639
+ for (const L of layoutComponents.toReversed()) {
5640
+ element = createElement(L, { children: element });
5641
+ }
5642
+ return element;
5643
+ }
5644
+
5645
+ // error-boundary.ts
5646
+ function errorBoundary(errorPath) {
5647
+ return async (req, ctx, next) => {
5648
+ try {
5649
+ return await next(req, ctx);
5650
+ } catch (err) {
5651
+ const mod = await compile(errorPath);
5652
+ const ErrorComponent = mod.default;
5653
+ if (!ErrorComponent) throw err;
5654
+ const layouts = (ctx.layoutStack || []).map((l) => l.component);
5655
+ const base = (ctx.mountPath || "").replace(/\/$/, "");
5656
+ let element = createElement2(ErrorComponent, {
5657
+ error: err instanceof Error ? err : new Error(String(err)),
5658
+ reset: () => {
5659
+ }
5660
+ });
5661
+ element = buildHtmlShell("500", element, layouts);
5662
+ const { renderToReadableStream } = await import("react-dom/server");
5663
+ const stream = await renderToReadableStream(element);
5664
+ return streamResponse(stream, {
5665
+ ctx,
5666
+ base,
5667
+ isDev: isDev(),
5668
+ compiledTailwindCss: ctx.compiledTailwindCss,
5669
+ status: 500
5670
+ });
5671
+ }
5672
+ };
5673
+ }
5674
+
5675
+ // ssr.ts
5676
+ var isDev2 = isDev();
5677
+ var als = new AsyncLocalStorage();
5678
+ __registerAls(() => als.getStore());
5679
+ var bundleCache = /* @__PURE__ */ new Map();
5680
+ var _bundleDirty = false;
5681
+ function markClientBundleDirty() {
5682
+ _bundleDirty = true;
5683
+ }
5684
+ function getBundle(key) {
5685
+ if (_bundleDirty) {
5686
+ bundleCache.clear();
5687
+ _bundleDirty = false;
5688
+ }
5689
+ return bundleCache.get(key);
5690
+ }
5691
+ function setBundle(key, buf) {
5692
+ if (_bundleDirty) {
5693
+ bundleCache.clear();
5694
+ _bundleDirty = false;
5695
+ }
5696
+ bundleCache.set(key, buf);
5697
+ }
5698
+ function id2(s) {
5699
+ return createHash3("md5").update(s).digest("hex").slice(0, 8);
5700
+ }
5701
+ function serializeLoaderData(ctx) {
5702
+ const ld = ctx.loaderData;
5703
+ return ld && typeof ld === "object" ? ld : {};
5704
+ }
5705
+ async function buildClientBundle(entryPath, layoutPaths) {
5706
+ try {
5707
+ const absEntry = resolve6(entryPath);
5708
+ const absLayouts = layoutPaths.map((p) => resolve6(p));
5709
+ const layoutImports = absLayouts.map((p) => `import${JSON.stringify(p)};`).join("");
5710
+ const _sc = `(function(){var k='__WEIFUWU_CTX_STORE';var s=typeof globalThis!='undefined'&&globalThis[k];if(!s)return function(){};return function(v){s._ctx={...s._ctx,...v};s._snapshot={params:s._ctx.params,query:s._ctx.query,user:s._ctx.user,parsed:s._ctx.parsed,prefs:s._ctx.prefs,env:s._ctx.env};s._listeners.forEach(function(fn){fn()})}})()`;
5711
+ const code = [
5712
+ layoutImports,
5713
+ `${isDev2 ? "import{createRoot}from'react-dom/client';" : "import{hydrateRoot}from'react-dom/client';"}`,
5714
+ `import{createElement}from'react';`,
5715
+ `import{TsxContext}from'weifuwu/react';`,
5716
+ `import P from${JSON.stringify(absEntry)};`,
5717
+ `var setCtx=${_sc};`,
5718
+ `const c=document.getElementById('__weifuwu_root');`,
5719
+ `if(window.__WEIFUWU_PROPS)setCtx({loaderData:window.__WEIFUWU_PROPS});`,
5720
+ isDev2 ? `const _W=function(props){return(_W._fn||P)(props)};_W._fn=P;const _P=function(props){return createElement(_W,props)};` : "",
5721
+ isDev2 ? `window.__WFW_ENTRY=${JSON.stringify(id2(absEntry))};window.__WFW_REFRESH=function(n){_W._fn=n;window.__WFW_ROOT.render(createElement(App))};` : "",
5722
+ `function App(){`,
5723
+ `const ctx=window.__WEIFUWU_CTX||{};`,
5724
+ `return createElement(TsxContext.Provider,{value:ctx},`,
5725
+ isDev2 ? `createElement(_P,null))` : `createElement(P,null))`,
5726
+ `}`,
5727
+ isDev2 ? `window.__WFW_ROOT=createRoot(c);window.__WFW_ROOT.render(createElement(App));` : `hydrateRoot(c,createElement(App));`
5728
+ ].filter(Boolean).join("");
5729
+ const { default: esbuild2 } = await import("esbuild");
5730
+ const result = await esbuild2.build({
5731
+ stdin: { contents: code, loader: "tsx", resolveDir: dirname3(absEntry) },
5732
+ bundle: true,
5733
+ format: "esm",
5734
+ jsx: "automatic",
5735
+ jsxImportSource: "react",
5736
+ banner: { js: "self.process={env:{}};" },
5737
+ loader: { ".node": "empty" },
5738
+ external: isDev2 ? ["react", "react-dom", "react-dom/client", "react/jsx-runtime", "weifuwu", "weifuwu/react"] : void 0,
5739
+ write: false,
5740
+ minify: !isDev2
5741
+ });
5742
+ return result.outputFiles[0].contents;
5743
+ } catch (err) {
5744
+ console.error("hydration bundle failed:", err);
5745
+ return null;
5746
+ }
5747
+ }
5748
+ function resolveFileSync(ssrDir, segments) {
5749
+ const appDir = join5(ssrDir, "app");
5750
+ let dir = appDir;
5751
+ const paramNames = [];
5752
+ const paramValues = [];
5753
+ let catchAll = null;
5754
+ let segIdx = 0;
5755
+ for (; segIdx < segments.length; segIdx++) {
5756
+ const seg = segments[segIdx];
5757
+ const literal = join5(dir, seg);
5758
+ if (existsSync4(literal) && statSync(literal).isDirectory()) {
5759
+ dir = literal;
5760
+ continue;
5761
+ }
5762
+ const entries = readdirSync(dir, { withFileTypes: true });
5763
+ const paramDir = entries.find(
5764
+ (e) => e.isDirectory() && e.name.startsWith("[") && e.name.endsWith("]") && !e.name.startsWith("[...")
5765
+ );
5766
+ if (paramDir) {
5767
+ paramNames.push(paramDir.name.slice(1, -1));
5768
+ paramValues.push(seg);
5769
+ dir = join5(dir, paramDir.name);
5770
+ continue;
5771
+ }
5772
+ const catchAllDir = entries.find(
5773
+ (e) => e.isDirectory() && e.name.startsWith("[...") && e.name.endsWith("]")
5774
+ );
5775
+ if (catchAllDir) {
5776
+ catchAll = segments.slice(segIdx).join("/");
5777
+ dir = join5(dir, catchAllDir.name);
5778
+ break;
5779
+ }
5780
+ return null;
5781
+ }
5782
+ const pageFile = join5(dir, "page.tsx");
5783
+ if (!existsSync4(pageFile)) return null;
5784
+ let pi = 0;
5785
+ const consumed = catchAll !== null ? segIdx : segments.length;
5786
+ const routeParams = [];
5787
+ for (let i = 0; i < consumed; i++) {
5788
+ routeParams.push(segments[i]);
5789
+ }
5790
+ const layoutFiles = [];
5791
+ let d = dir;
5792
+ while (d.startsWith(appDir)) {
5793
+ const lf = join5(d, "layout.tsx");
5794
+ if (existsSync4(lf)) layoutFiles.unshift(lf);
5795
+ if (d === appDir) break;
5796
+ d = dirname3(d);
5797
+ }
5798
+ const errorFiles = [];
5799
+ d = dir;
5800
+ while (d.startsWith(appDir)) {
5801
+ const ef = join5(d, "error.tsx");
5802
+ if (existsSync4(ef)) errorFiles.unshift(ef);
5803
+ if (d === appDir) break;
5804
+ d = dirname3(d);
5805
+ }
5806
+ let notFoundFile = null;
5807
+ d = dir;
5808
+ while (d.startsWith(appDir)) {
5809
+ const nf = join5(d, "not-found.tsx");
5810
+ if (existsSync4(nf)) {
5811
+ notFoundFile = nf;
5812
+ break;
5813
+ }
5814
+ if (d === appDir) break;
5815
+ d = dirname3(d);
5816
+ }
5817
+ return { routePath: "/" + routeParams.join("/"), pageFile, layoutFiles, errorFiles, notFoundFile };
5818
+ }
5819
+ function renderPage(pageFile) {
5820
+ const absPath = resolve6(pageFile);
5821
+ const entryId = id2(absPath);
5822
+ ssrEntries.set(entryId, { path: absPath });
5823
+ const bundleKey = `/__ssr/${entryId}.js`;
5824
+ return async (req, ctx) => {
5825
+ const pageMod = await compile(absPath);
5826
+ const Component = pageMod.default;
5827
+ if (!Component) return new Response("", { status: 500 });
5828
+ const layouts = ctx.layoutStack || [];
5829
+ const layoutComponents = layouts.map((l) => l.component);
5830
+ const layoutPaths = layouts.map((l) => l.path);
5831
+ const base = (ctx.mountPath || "").replace(/\/$/, "");
5832
+ const loaderData = serializeLoaderData(ctx);
5833
+ const ctxValue = {
5834
+ params: ctx.params,
5835
+ query: ctx.query,
5836
+ user: ctx.user ?? {},
5837
+ parsed: ctx.parsed ?? {},
5838
+ prefs: ctx.prefs ?? {},
5839
+ loaderData,
5840
+ env: ctx.env ?? {}
5841
+ };
5842
+ return als.run(ctxValue, async () => {
5843
+ setCtx(ctxValue);
5844
+ if (ctxValue.parsed?.__localeData) {
5845
+ globalThis.__LOCALE_DATA__ = ctxValue.parsed.__localeData;
5846
+ }
5847
+ let element = createElement3(
5848
+ "div",
5849
+ { id: "__weifuwu_root" },
5850
+ createElement3(
5851
+ TsxContext.Provider,
5852
+ { value: ctxValue },
5853
+ createElement3(Component, null)
5854
+ )
5855
+ );
5856
+ element = buildHtmlShell("weifuwu", element, layoutComponents);
5857
+ let bundle = null;
5858
+ if (!getBundle(bundleKey)) {
5859
+ const buf = await buildClientBundle(absPath, layoutPaths);
5860
+ if (buf) setBundle(bundleKey, buf);
5861
+ }
5862
+ if (getBundle(bundleKey)) {
5863
+ bundle = { url: bundleKey };
5864
+ }
5865
+ const { renderToReadableStream } = await import("react-dom/server");
5866
+ const stream = await renderToReadableStream(element);
5867
+ return streamResponse(stream, {
5868
+ ctx,
5869
+ base,
5870
+ isDev: isDev2,
5871
+ bundle,
5872
+ loaderData,
5873
+ compiledTailwindCss: ctx.compiledTailwindCss
5874
+ });
5875
+ });
5876
+ };
5877
+ }
5878
+ function runChain(mws, handler, req, ctx) {
5879
+ let idx = 0;
5880
+ const dispatch = (r, c) => {
5881
+ if (idx < mws.length) return mws[idx++](r, c, dispatch);
5882
+ return handler(r, c);
5883
+ };
5884
+ return Promise.resolve(dispatch(req, ctx));
5885
+ }
5886
+ function ssr(opts) {
5887
+ const r = new Router();
5888
+ const dir = resolve6(opts.dir);
5889
+ r.get("/__ssr/:path", (req, ctx) => {
5890
+ const buf = getBundle("/__ssr/" + ctx.params.path);
5891
+ if (!buf) return new Response("", { status: 404 });
5892
+ return new Response(buf, {
5893
+ headers: { "content-type": "application/javascript; charset=utf-8" }
5894
+ });
5712
5895
  });
5713
- if (existsSync5(join5(resolved, "app.css"))) {
5714
- r.use(tailwind(resolved));
5896
+ if (existsSync4(join5(dir, "app", "globals.css"))) {
5897
+ r.use("/", tailwindRouter(dir));
5715
5898
  }
5716
5899
  if (isDev2) {
5717
- const lr = liveReload(resolved);
5718
- r.use(lr);
5719
- r.close = lr.close;
5720
- }
5900
+ r.use("/", liveRouter(dir));
5901
+ r.ws("/__weifuwu/livereload", liveWs());
5902
+ const watcher = liveWatcher(dir);
5903
+ r.close = watcher.close;
5904
+ }
5905
+ r.all("/*", async (req, ctx) => {
5906
+ const prefix = ctx.mountPath || "";
5907
+ const pathname = new URL(req.url).pathname;
5908
+ const relativePath = pathname.replace(prefix, "") || "/";
5909
+ const segments = relativePath.split("/").filter(Boolean);
5910
+ const resolved = resolveFileSync(dir, segments);
5911
+ if (!resolved) return new Response("Not Found", { status: 404 });
5912
+ const mws = [
5913
+ ...resolved.errorFiles.map((f) => errorBoundary(f)),
5914
+ ...resolved.layoutFiles.map((f) => layout(f)),
5915
+ tailwindContext(dir)
5916
+ ];
5917
+ const handler = (req2, ctx2) => renderPage(resolved.pageFile)(req2, ctx2);
5918
+ return runChain(mws, handler, req, ctx);
5919
+ });
5721
5920
  return r;
5722
5921
  }
5723
5922
 
@@ -5957,10 +6156,10 @@ function createBashTool(ctx) {
5957
6156
  return { stdout: "", stderr: "Command denied: potentially dangerous command", exitCode: 1 };
5958
6157
  }
5959
6158
  const cwd = workdir ? `${ctx.workspace}/${workdir}` : ctx.workspace;
5960
- return new Promise((resolve15) => {
6159
+ return new Promise((resolve14) => {
5961
6160
  const child = exec(command, { cwd, timeout: timeout * 1e3, maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => {
5962
6161
  const truncated = stdout.length > 1e6 || stderr.length > 1e6;
5963
- resolve15({
6162
+ resolve14({
5964
6163
  stdout: stdout.slice(0, 1e6),
5965
6164
  stderr: stderr.slice(0, 1e6),
5966
6165
  exitCode: error?.code ?? 0,
@@ -5977,7 +6176,7 @@ function createBashTool(ctx) {
5977
6176
  import { tool as tool4 } from "ai";
5978
6177
  import { z as z6 } from "zod";
5979
6178
  import { readFileSync as readFileSync4 } from "node:fs";
5980
- import { resolve as resolve8 } from "node:path";
6179
+ import { resolve as resolve7 } from "node:path";
5981
6180
  function createReadTool(ctx) {
5982
6181
  return tool4({
5983
6182
  description: "Read file contents. Supports offset and limit for reading specific line ranges.",
@@ -5987,7 +6186,7 @@ function createReadTool(ctx) {
5987
6186
  limit: z6.number().optional().describe("Number of lines to read")
5988
6187
  }),
5989
6188
  execute: async ({ path: path2, offset, limit }) => {
5990
- const resolved = resolve8(ctx.workspace, path2);
6189
+ const resolved = resolve7(ctx.workspace, path2);
5991
6190
  if (!isPathAllowed(resolved, ctx.workspace, ctx.permissions)) {
5992
6191
  return { error: "Path not allowed", content: null, totalLines: 0 };
5993
6192
  }
@@ -6019,7 +6218,7 @@ function createReadTool(ctx) {
6019
6218
  import { tool as tool5 } from "ai";
6020
6219
  import { z as z7 } from "zod";
6021
6220
  import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync3 } from "node:fs";
6022
- import { resolve as resolve9, dirname as dirname4 } from "node:path";
6221
+ import { resolve as resolve8, dirname as dirname4 } from "node:path";
6023
6222
  function createWriteTool(ctx) {
6024
6223
  return tool5({
6025
6224
  description: "Create or overwrite a file. Parent directories are created automatically.",
@@ -6028,7 +6227,7 @@ function createWriteTool(ctx) {
6028
6227
  content: z7.string().describe("File content")
6029
6228
  }),
6030
6229
  execute: async ({ path: path2, content }) => {
6031
- const resolved = resolve9(ctx.workspace, path2);
6230
+ const resolved = resolve8(ctx.workspace, path2);
6032
6231
  if (!isPathAllowed(resolved, ctx.workspace, ctx.permissions)) {
6033
6232
  return { error: "Path not allowed" };
6034
6233
  }
@@ -6043,7 +6242,7 @@ function createWriteTool(ctx) {
6043
6242
  import { tool as tool6 } from "ai";
6044
6243
  import { z as z8 } from "zod";
6045
6244
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
6046
- import { resolve as resolve10 } from "node:path";
6245
+ import { resolve as resolve9 } from "node:path";
6047
6246
  function createEditTool(ctx) {
6048
6247
  return tool6({
6049
6248
  description: "Perform exact string replacements in a file. If oldString appears multiple times, provide more surrounding context.",
@@ -6054,7 +6253,7 @@ function createEditTool(ctx) {
6054
6253
  replaceAll: z8.boolean().default(false).describe("Replace all occurrences")
6055
6254
  }),
6056
6255
  execute: async ({ path: path2, oldString, newString, replaceAll }) => {
6057
- const resolved = resolve10(ctx.workspace, path2);
6256
+ const resolved = resolve9(ctx.workspace, path2);
6058
6257
  if (!isPathAllowed(resolved, ctx.workspace, ctx.permissions)) {
6059
6258
  return { error: "Path not allowed" };
6060
6259
  }
@@ -6087,8 +6286,8 @@ function createEditTool(ctx) {
6087
6286
  import { tool as tool7 } from "ai";
6088
6287
  import { z as z9 } from "zod";
6089
6288
  import { execFileSync } from "node:child_process";
6090
- import { resolve as resolve11 } from "node:path";
6091
- import { existsSync as existsSync6 } from "node:fs";
6289
+ import { resolve as resolve10 } from "node:path";
6290
+ import { existsSync as existsSync5 } from "node:fs";
6092
6291
  function createGrepTool(ctx) {
6093
6292
  return tool7({
6094
6293
  description: "Search file contents using regex. Supports file type filtering and context lines.",
@@ -6099,10 +6298,10 @@ function createGrepTool(ctx) {
6099
6298
  context: z9.number().default(0).describe("Number of context lines before and after each match")
6100
6299
  }),
6101
6300
  execute: async ({ pattern, include, path: path2, context }) => {
6102
- const searchDir = path2 ? resolve11(ctx.workspace, path2) : ctx.workspace;
6301
+ const searchDir = path2 ? resolve10(ctx.workspace, path2) : ctx.workspace;
6103
6302
  try {
6104
6303
  let stdout;
6105
- if (existsSync6("/usr/bin/rg") || existsSync6("/usr/local/bin/rg")) {
6304
+ if (existsSync5("/usr/bin/rg") || existsSync5("/usr/local/bin/rg")) {
6106
6305
  const args = ["-n"];
6107
6306
  if (context > 0) args.push("-C", String(context));
6108
6307
  if (include) args.push("-g", include);
@@ -6131,7 +6330,7 @@ function createGrepTool(ctx) {
6131
6330
  import { tool as tool8 } from "ai";
6132
6331
  import { z as z10 } from "zod";
6133
6332
  import { execFileSync as execFileSync2 } from "node:child_process";
6134
- import { resolve as resolve12 } from "node:path";
6333
+ import { resolve as resolve11 } from "node:path";
6135
6334
  function createGlobTool(ctx) {
6136
6335
  return tool8({
6137
6336
  description: "Find files matching a glob pattern.",
@@ -6140,7 +6339,7 @@ function createGlobTool(ctx) {
6140
6339
  path: z10.string().optional().describe("Subdirectory relative to workspace")
6141
6340
  }),
6142
6341
  execute: async ({ pattern, path: path2 }) => {
6143
- const searchDir = path2 ? resolve12(ctx.workspace, path2) : ctx.workspace;
6342
+ const searchDir = path2 ? resolve11(ctx.workspace, path2) : ctx.workspace;
6144
6343
  try {
6145
6344
  const stdout = execFileSync2("find", [
6146
6345
  searchDir,
@@ -6201,7 +6400,7 @@ function createQuestionTool(ctx) {
6201
6400
  options: z12.array(z12.string()).optional().describe("Optional multiple choice options")
6202
6401
  }),
6203
6402
  execute: async ({ question, options }, { toolCallId }) => {
6204
- return new Promise((resolve15, reject) => {
6403
+ return new Promise((resolve14, reject) => {
6205
6404
  const timeout = setTimeout(() => {
6206
6405
  ctx.pendingQuestions.delete(toolCallId);
6207
6406
  reject(new Error("Question timed out"));
@@ -6209,7 +6408,7 @@ function createQuestionTool(ctx) {
6209
6408
  ctx.pendingQuestions.set(toolCallId, {
6210
6409
  resolve: (answer) => {
6211
6410
  clearTimeout(timeout);
6212
- resolve15(answer);
6411
+ resolve14(answer);
6213
6412
  },
6214
6413
  reject: (err) => {
6215
6414
  clearTimeout(timeout);
@@ -6356,8 +6555,7 @@ async function buildRouter4(deps) {
6356
6555
  });
6357
6556
  try {
6358
6557
  const uiDir = new URL("../opencode/ui/", import.meta.url).pathname;
6359
- router.use(rootLayout(uiDir));
6360
- router.get("/", ssr(join7(uiDir, "page.tsx")));
6558
+ router.use("/", ssr({ dir: uiDir }));
6361
6559
  } catch (e) {
6362
6560
  console.warn("[opencode] UI not available:", e);
6363
6561
  }
@@ -6492,7 +6690,7 @@ function createWSHandler2(deps) {
6492
6690
  import { readFile } from "node:fs/promises";
6493
6691
  import { glob } from "node:fs/promises";
6494
6692
  import { homedir } from "node:os";
6495
- import { resolve as resolve13 } from "node:path";
6693
+ import { resolve as resolve12 } from "node:path";
6496
6694
  import { parse as parseYaml } from "yaml";
6497
6695
  var SEARCH_DIRS = [
6498
6696
  (ws) => `${ws}/.opencode/skills`,
@@ -6530,7 +6728,7 @@ async function scanDir(dir) {
6530
6728
  try {
6531
6729
  const files = [];
6532
6730
  for await (const entry of glob("*/SKILL.md", { cwd: dir })) {
6533
- const skill = await parseSkillFile(resolve13(dir, entry));
6731
+ const skill = await parseSkillFile(resolve12(dir, entry));
6534
6732
  if (skill) files.push(skill);
6535
6733
  }
6536
6734
  return files;
@@ -6627,7 +6825,7 @@ async function opencode(options) {
6627
6825
 
6628
6826
  // health.ts
6629
6827
  function health(options) {
6630
- const path2 = options?.path ?? "/";
6828
+ const path2 = options?.path ?? "/__health";
6631
6829
  const r = new Router();
6632
6830
  const handler = async () => {
6633
6831
  try {
@@ -6649,15 +6847,31 @@ var MemStore = class {
6649
6847
  days = /* @__PURE__ */ new Map();
6650
6848
  pages = /* @__PURE__ */ new Map();
6651
6849
  refs = /* @__PURE__ */ new Map();
6652
- evict() {
6850
+ timer = null;
6851
+ evict(forceAll = false) {
6653
6852
  if (this.days.size <= MAX_MEM_ENTRIES) return;
6654
- const sorted = [...this.days.keys()].sort();
6655
- const toDelete = sorted.slice(0, this.days.size - MAX_MEM_ENTRIES);
6853
+ const target = forceAll ? 0 : MAX_MEM_ENTRIES;
6854
+ const toDelete = [];
6855
+ for (const d of this.days.keys()) {
6856
+ toDelete.push(d);
6857
+ if (!forceAll && this.days.size - toDelete.length <= target) break;
6858
+ }
6656
6859
  for (const d of toDelete) {
6657
6860
  this.days.delete(d);
6658
6861
  this.refs.delete(d);
6659
6862
  }
6660
6863
  }
6864
+ startCleanup(interval) {
6865
+ if (this.timer) return;
6866
+ this.timer = setInterval(() => this.evict(true), interval);
6867
+ if (this.timer.unref) this.timer.unref();
6868
+ }
6869
+ stopCleanup() {
6870
+ if (this.timer) {
6871
+ clearInterval(this.timer);
6872
+ this.timer = null;
6873
+ }
6874
+ }
6661
6875
  record(path2, date, refDomain, mobile) {
6662
6876
  let day = this.days.get(date);
6663
6877
  if (!day) {
@@ -6769,17 +6983,20 @@ async function queryPg(sql2, days) {
6769
6983
  devices: { mobile: Math.round(t.total_mobile / denom * 1e3) / 10, desktop: Math.round(t.total_desktop / denom * 1e3) / 10 }
6770
6984
  };
6771
6985
  }
6986
+ function escapeHtml(s) {
6987
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
6988
+ }
6772
6989
  function renderDashboard(days, data) {
6773
6990
  const { total_pv, total_uv, top_pages, referrers } = data;
6774
6991
  const maxPv = Math.max(...data.daily.map((d) => d.pv), 1);
6775
6992
  const bars = data.daily.map(
6776
- (d) => `<div class="bar-wrap"><div class="bar" style="height:${d.pv / maxPv * 100}%"></div><span class="bar-label">${d.date.slice(5)}</span></div>`
6993
+ (d) => `<div class="bar-wrap"><div class="bar" style="height:${d.pv / maxPv * 100}%"></div><span class="bar-label">${escapeHtml(d.date.slice(5))}</span></div>`
6777
6994
  ).join("");
6778
6995
  const rows = top_pages.map(
6779
- (p, i) => `<tr><td class="num">${i + 1}</td><td class="path">${p.path}</td><td class="num">${p.pv}</td></tr>`
6996
+ (p, i) => `<tr><td class="num">${i + 1}</td><td class="path">${escapeHtml(p.path)}</td><td class="num">${p.pv}</td></tr>`
6780
6997
  ).join("");
6781
6998
  const refRows = referrers.map(
6782
- (r) => `<tr><td>${r.domain}</td><td class="num">${r.count}</td></tr>`
6999
+ (r) => `<tr><td>${escapeHtml(r.domain)}</td><td class="num">${r.count}</td></tr>`
6783
7000
  ).join("");
6784
7001
  return `<!DOCTYPE html><html lang="en">
6785
7002
  <head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Analytics - weifuwu</title>
@@ -6821,6 +7038,7 @@ function analytics(options) {
6821
7038
  const excluded = options?.excluded ?? DEFAULT_EXCLUDED;
6822
7039
  const pg = options?.pg;
6823
7040
  const store2 = pg ? null : new MemStore();
7041
+ if (store2) store2.startCleanup(6e4);
6824
7042
  const middleware = () => {
6825
7043
  const m = async (req, ctx, next) => {
6826
7044
  const path2 = new URL(req.url).pathname;
@@ -6832,7 +7050,13 @@ function analytics(options) {
6832
7050
  await recordPg(pg.sql, path2, date, mobile);
6833
7051
  } else {
6834
7052
  const ref = req.headers.get("referer") || "";
6835
- const refDomain = ref ? new URL(ref).hostname.replace(/^www\./, "") : "";
7053
+ let refDomain = "";
7054
+ if (ref) {
7055
+ try {
7056
+ refDomain = new URL(ref).hostname.replace(/^www\./, "");
7057
+ } catch {
7058
+ }
7059
+ }
6836
7060
  store2.record(path2, date, refDomain, mobile);
6837
7061
  }
6838
7062
  return next(req, ctx);
@@ -6855,6 +7079,7 @@ function analytics(options) {
6855
7079
  if (pg) await migratePg(pg.sql, pg.table);
6856
7080
  };
6857
7081
  const close = async () => {
7082
+ if (store2) store2.stopCleanup();
6858
7083
  };
6859
7084
  const mod = r;
6860
7085
  mod.middleware = middleware;
@@ -6864,9 +7089,8 @@ function analytics(options) {
6864
7089
  }
6865
7090
 
6866
7091
  // preferences.ts
6867
- import { readFile as readFile2 } from "node:fs/promises";
6868
- import { existsSync as existsSync7 } from "node:fs";
6869
- import { join as join8, resolve as resolve14 } from "node:path";
7092
+ import { readFile as readFile2, stat } from "node:fs/promises";
7093
+ import { join as join7, resolve as resolve13 } from "node:path";
6870
7094
  var defaults = {
6871
7095
  locale: { default: "en", cookie: "locale", fromAcceptLanguage: true },
6872
7096
  theme: { default: "system", cookie: "theme" }
@@ -6881,15 +7105,6 @@ function translate(msgs, key, params, fallback) {
6881
7105
  }
6882
7106
  return result;
6883
7107
  }
6884
- function extractCookie(req, name) {
6885
- const cookie = req.headers.get("cookie");
6886
- if (!cookie) return null;
6887
- for (const part of cookie.split(";")) {
6888
- const [k, v] = part.trim().split("=");
6889
- if (k === name && v) return decodeURIComponent(v);
6890
- }
6891
- return null;
6892
- }
6893
7108
  function prefCookie(name, value) {
6894
7109
  return `${name}=${encodeURIComponent(value)}; Path=/; SameSite=Lax`;
6895
7110
  }
@@ -6915,7 +7130,7 @@ async function handlePrefSwitch(req, value, cookieName, load) {
6915
7130
  });
6916
7131
  }
6917
7132
  function preferences(options) {
6918
- const dir = options.dir ? resolve14(options.dir) : void 0;
7133
+ const dir = options.dir ? resolve13(options.dir) : void 0;
6919
7134
  const localeOpts = { ...defaults.locale, ...options.locale };
6920
7135
  const themeOpts = { ...defaults.theme, ...options.theme };
6921
7136
  const cache2 = /* @__PURE__ */ new Map();
@@ -6927,23 +7142,24 @@ function preferences(options) {
6927
7142
  if (!validLocale(locale)) return {};
6928
7143
  const cached = cache2.get(locale);
6929
7144
  if (cached) return cached;
6930
- const filePath = join8(dir, `${locale}.json`);
6931
- if (existsSync7(filePath)) {
6932
- try {
6933
- const content = await readFile2(filePath, "utf-8");
6934
- const data = JSON.parse(content);
6935
- cache2.set(locale, data);
6936
- return data;
6937
- } catch {
6938
- return {};
6939
- }
7145
+ const filePath = join7(dir, `${locale}.json`);
7146
+ let data = null;
7147
+ try {
7148
+ await stat(filePath);
7149
+ const content = await readFile2(filePath, "utf-8");
7150
+ data = JSON.parse(content);
7151
+ cache2.set(locale, data);
7152
+ return data;
7153
+ } catch {
6940
7154
  }
6941
- const short = locale.split("-")[0];
6942
- if (short !== locale) {
6943
- const fallback = cache2.get(short) || await load(short);
6944
- if (fallback && Object.keys(fallback).length > 0) {
6945
- cache2.set(locale, fallback);
6946
- return fallback;
7155
+ if (!data) {
7156
+ const short = locale.split("-")[0];
7157
+ if (short !== locale) {
7158
+ const fallback = cache2.get(short) || await load(short);
7159
+ if (fallback && Object.keys(fallback).length > 0) {
7160
+ cache2.set(locale, fallback);
7161
+ return fallback;
7162
+ }
6947
7163
  }
6948
7164
  }
6949
7165
  return {};
@@ -6978,7 +7194,7 @@ function preferences(options) {
6978
7194
  }
6979
7195
  });
6980
7196
  };
6981
- const flashVal = extractCookie(req, "flash");
7197
+ const flashVal = getCookies(req)["flash"] ?? null;
6982
7198
  if (flashVal) {
6983
7199
  try {
6984
7200
  ctx.prefs.flash = JSON.parse(flashVal);
@@ -6997,7 +7213,7 @@ function preferences(options) {
6997
7213
  }
6998
7214
  function detectLocale(req, opts) {
6999
7215
  if (opts.cookie) {
7000
- const fromCookie = extractCookie(req, opts.cookie);
7216
+ const fromCookie = getCookies(req)[opts.cookie];
7001
7217
  if (fromCookie) return fromCookie;
7002
7218
  }
7003
7219
  if (opts.fromAcceptLanguage) {
@@ -7008,7 +7224,7 @@ function detectLocale(req, opts) {
7008
7224
  }
7009
7225
  function detectTheme(req, opts) {
7010
7226
  if (opts.cookie) {
7011
- const fromCookie = extractCookie(req, opts.cookie);
7227
+ const fromCookie = getCookies(req)[opts.cookie];
7012
7228
  if (fromCookie) return fromCookie;
7013
7229
  }
7014
7230
  return opts.default;
@@ -7217,12 +7433,13 @@ function csrf(options) {
7217
7433
  return res;
7218
7434
  }
7219
7435
  const cookieToken = getCookies(req)[cookieName];
7220
- let headerToken = req.headers.get(headerName) ?? req.headers.get("x-xsrf-token") ?? "";
7436
+ let headerToken = req.headers.get(headerName) ?? "";
7221
7437
  if (!headerToken && (req.method === "POST" || req.method === "PUT" || req.method === "PATCH" || req.method === "DELETE")) {
7222
7438
  try {
7223
7439
  const body = await req.clone().json();
7224
7440
  headerToken = body[bodyKey] ?? "";
7225
- } catch {
7441
+ } catch (e) {
7442
+ return new Response("Invalid request body", { status: 400 });
7226
7443
  }
7227
7444
  }
7228
7445
  if (!cookieToken || !headerToken || cookieToken !== headerToken) {
@@ -7964,12 +8181,12 @@ function iii(opts = {}) {
7964
8181
  const handler = async (payload) => {
7965
8182
  if (!worker.ws) throw new Error(`Worker "${worker.name}" disconnected`);
7966
8183
  const invocationId = crypto6.randomUUID();
7967
- return new Promise((resolve15, reject) => {
8184
+ return new Promise((resolve14, reject) => {
7968
8185
  const timer = setTimeout(() => {
7969
8186
  pending.delete(invocationId);
7970
8187
  reject(new Error(`Invocation timed out for "${id3}"`));
7971
8188
  }, 3e4);
7972
- pending.set(invocationId, { resolve: resolve15, reject, timer });
8189
+ pending.set(invocationId, { resolve: resolve14, reject, timer });
7973
8190
  worker.ws.send(JSON.stringify({
7974
8191
  type: "invoke",
7975
8192
  invocation_id: invocationId,
@@ -8210,8 +8427,8 @@ function registerWorker(url) {
8210
8427
  function connect() {
8211
8428
  if (intentionalClose) return;
8212
8429
  ws = new WebSocket(url);
8213
- ready = new Promise((resolve15) => {
8214
- resolveReady = resolve15;
8430
+ ready = new Promise((resolve14) => {
8431
+ resolveReady = resolve14;
8215
8432
  });
8216
8433
  ws.onopen = () => {
8217
8434
  reconnectAttempt = 0;
@@ -8331,13 +8548,13 @@ function registerWorker(url) {
8331
8548
  }
8332
8549
  return Promise.resolve(fn(request.payload, ctx));
8333
8550
  }
8334
- return new Promise((resolve15, reject) => {
8551
+ return new Promise((resolve14, reject) => {
8335
8552
  const invocationId = genId();
8336
8553
  const timer = setTimeout(() => {
8337
8554
  pendingInvocations.delete(invocationId);
8338
8555
  reject(new Error(`Invocation timed out for "${request.function_id}"`));
8339
8556
  }, request.timeout_ms || 3e4);
8340
- pendingInvocations.set(invocationId, { resolve: resolve15, reject, timer });
8557
+ pendingInvocations.set(invocationId, { resolve: resolve14, reject, timer });
8341
8558
  send({
8342
8559
  type: "invoke",
8343
8560
  invocation_id: invocationId,
@@ -8357,116 +8574,6 @@ function registerWorker(url) {
8357
8574
  }
8358
8575
  };
8359
8576
  }
8360
-
8361
- // layout.ts
8362
- function layout(path2) {
8363
- return async (req, ctx, next) => {
8364
- const mod = await compile(path2);
8365
- const Component = mod.default;
8366
- if (!Component) throw new Error(`Layout ${path2} has no default export`);
8367
- ctx.layoutStack = [...ctx.layoutStack || [], { path: path2, component: Component }];
8368
- return next(req, ctx);
8369
- };
8370
- }
8371
-
8372
- // not-found.ts
8373
- import { createElement as createElement2 } from "react";
8374
- function notFound(path2) {
8375
- return async (req, ctx) => {
8376
- if (!path2) return new Response("Not Found", { status: 404 });
8377
- let Component;
8378
- try {
8379
- const mod = await compile(path2);
8380
- Component = mod?.default;
8381
- } catch {
8382
- return new Response("Not Found", { status: 404 });
8383
- }
8384
- if (!Component) return new Response("Not Found", { status: 404 });
8385
- const layouts = ctx.layoutStack || [];
8386
- const layoutComponents = layouts.map((l) => l.component);
8387
- const base = (ctx.mountPath || "").replace(/\/$/, "");
8388
- let element = createElement2(
8389
- "div",
8390
- { id: "__weifuwu_root" },
8391
- createElement2(Component, null)
8392
- );
8393
- if (layoutComponents.length === 0) {
8394
- element = createElement2(
8395
- "html",
8396
- { lang: "en" },
8397
- createElement2(
8398
- "head",
8399
- null,
8400
- createElement2("meta", { charSet: "utf-8" }),
8401
- createElement2("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }),
8402
- createElement2("title", null, "404")
8403
- ),
8404
- createElement2("body", null, element)
8405
- );
8406
- } else {
8407
- for (const L of layoutComponents.toReversed()) {
8408
- element = createElement2(L, { children: element });
8409
- }
8410
- }
8411
- const { renderToReadableStream } = await import("react-dom/server");
8412
- const stream = await renderToReadableStream(element);
8413
- return streamResponse(stream, {
8414
- ctx,
8415
- base,
8416
- isDev: process.env.NODE_ENV !== "production",
8417
- compiledTailwindCss: ctx.compiledTailwindCss,
8418
- status: 404
8419
- });
8420
- };
8421
- }
8422
-
8423
- // error-boundary.ts
8424
- import { createElement as createElement3 } from "react";
8425
- function errorBoundary(errorPath) {
8426
- return async (req, ctx, next) => {
8427
- try {
8428
- return await next(req, ctx);
8429
- } catch (err) {
8430
- const mod = await compile(errorPath);
8431
- const ErrorComponent = mod.default;
8432
- if (!ErrorComponent) throw err;
8433
- const layouts = (ctx.layoutStack || []).map((l) => l.component);
8434
- const base = (ctx.mountPath || "").replace(/\/$/, "");
8435
- let element = createElement3(ErrorComponent, {
8436
- error: err instanceof Error ? err : new Error(String(err)),
8437
- reset: () => {
8438
- }
8439
- });
8440
- if (layouts.length === 0) {
8441
- element = createElement3(
8442
- "html",
8443
- { lang: "en" },
8444
- createElement3(
8445
- "head",
8446
- null,
8447
- createElement3("meta", { charSet: "utf-8" }),
8448
- createElement3("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }),
8449
- createElement3("title", null, "500")
8450
- ),
8451
- createElement3("body", null, element)
8452
- );
8453
- } else {
8454
- for (const L of layouts.toReversed()) {
8455
- element = createElement3(L, { children: element });
8456
- }
8457
- }
8458
- const { renderToReadableStream } = await import("react-dom/server");
8459
- const stream = await renderToReadableStream(element);
8460
- return streamResponse(stream, {
8461
- ctx,
8462
- base,
8463
- isDev: process.env.NODE_ENV !== "production",
8464
- compiledTailwindCss: ctx.compiledTailwindCss,
8465
- status: 500
8466
- });
8467
- }
8468
- };
8469
- }
8470
8577
  export {
8471
8578
  Router,
8472
8579
  TsxContext,
@@ -8474,7 +8581,6 @@ export {
8474
8581
  aiStream,
8475
8582
  analytics,
8476
8583
  auth,
8477
- clearCompileCache,
8478
8584
  compress,
8479
8585
  cors,
8480
8586
  createHub,
@@ -8488,7 +8594,6 @@ export {
8488
8594
  deploy,
8489
8595
  embed,
8490
8596
  embedMany,
8491
- errorBoundary,
8492
8597
  formatSSE,
8493
8598
  formatSSEData,
8494
8599
  generateObject,
@@ -8498,13 +8603,12 @@ export {
8498
8603
  health,
8499
8604
  helmet,
8500
8605
  iii,
8501
- layout,
8606
+ isDev,
8502
8607
  loadEnv,
8503
8608
  logdb,
8504
8609
  logger,
8505
8610
  mailer,
8506
8611
  messager,
8507
- notFound,
8508
8612
  openai,
8509
8613
  opencode,
8510
8614
  postgres,
@@ -8514,7 +8618,6 @@ export {
8514
8618
  redis,
8515
8619
  registerWorker,
8516
8620
  requestId,
8517
- rootLayout,
8518
8621
  runWorkflow,
8519
8622
  seo,
8520
8623
  seoMiddleware,
@@ -8524,7 +8627,6 @@ export {
8524
8627
  setCookie,
8525
8628
  smoothStream,
8526
8629
  ssr,
8527
- ssrEntries,
8528
8630
  streamObject,
8529
8631
  streamText,
8530
8632
  tenant,