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.
- package/README.md +63 -70
- package/cli/template/app.ts +3 -8
- package/cli/template/ui/{page.tsx → app/page.tsx} +2 -2
- package/cli/template/ui/components/Greeting.tsx +1 -1
- package/cli.ts +4 -11
- package/dist/auth.d.ts +8 -0
- package/dist/cli.js +5 -5
- package/dist/compile.d.ts +2 -0
- package/dist/cors.d.ts +10 -0
- package/dist/env.d.ts +2 -0
- package/dist/html-shell.d.ts +1 -0
- package/dist/index.d.ts +7 -10
- package/dist/index.js +823 -721
- package/dist/live.d.ts +4 -1
- package/dist/logger.d.ts +5 -0
- package/dist/rate-limit.d.ts +2 -2
- package/dist/router.d.ts +5 -3
- package/dist/ssr.d.ts +5 -1
- package/dist/stream.d.ts +0 -1
- package/dist/tailwind.d.ts +3 -1
- package/package.json +3 -3
- package/dist/agent/migrate.d.ts +0 -6
- package/dist/dist/agent/client.d.ts +0 -2
- package/dist/dist/agent/index.d.ts +0 -2
- package/dist/dist/agent/migrate.d.ts +0 -6
- package/dist/dist/agent/rest.d.ts +0 -13
- package/dist/dist/agent/run.d.ts +0 -17
- package/dist/dist/agent/types.d.ts +0 -51
- package/dist/dist/ai/workflow.d.ts +0 -14
- package/dist/dist/analytics.d.ts +0 -15
- package/dist/dist/client-locale.d.ts +0 -5
- package/dist/dist/client-pref.d.ts +0 -3
- package/dist/dist/client-state.d.ts +0 -22
- package/dist/dist/client-theme.d.ts +0 -7
- package/dist/dist/compress.d.ts +0 -6
- package/dist/dist/cookie.d.ts +0 -12
- package/dist/dist/deploy/config.d.ts +0 -2
- package/dist/dist/deploy/gateway.d.ts +0 -2
- package/dist/dist/deploy/index.d.ts +0 -4
- package/dist/dist/deploy/manager.d.ts +0 -16
- package/dist/dist/deploy/process.d.ts +0 -14
- package/dist/dist/deploy/types.d.ts +0 -62
- package/dist/dist/head.d.ts +0 -6
- package/dist/dist/helmet.d.ts +0 -18
- package/dist/dist/iii/client.d.ts +0 -2
- package/dist/dist/iii/index.d.ts +0 -4
- package/dist/dist/iii/register-worker.d.ts +0 -10
- package/dist/dist/iii/rest.d.ts +0 -3
- package/dist/dist/iii/stream.d.ts +0 -82
- package/dist/dist/iii/types.d.ts +0 -133
- package/dist/dist/iii/worker.d.ts +0 -2
- package/dist/dist/iii/ws.d.ts +0 -29
- package/dist/dist/index.js +0 -8180
- package/dist/dist/messager/agent.d.ts +0 -6
- package/dist/dist/messager/client.d.ts +0 -2
- package/dist/dist/messager/index.d.ts +0 -2
- package/dist/dist/messager/migrate.d.ts +0 -2
- package/dist/dist/messager/rest.d.ts +0 -15
- package/dist/dist/messager/types.d.ts +0 -56
- package/dist/dist/messager/ws.d.ts +0 -14
- package/dist/dist/preferences.d.ts +0 -14
- package/dist/dist/react.d.ts +0 -12
- package/dist/dist/react.js +0 -637
- package/dist/dist/request-id.d.ts +0 -6
- package/dist/dist/seo.d.ts +0 -39
- package/dist/dist/ssr/compile.d.ts +0 -2
- package/dist/dist/ssr/error-boundary.d.ts +0 -2
- package/dist/dist/ssr/index.d.ts +0 -7
- package/dist/dist/ssr/index.js +0 -933
- package/dist/dist/ssr/layout.d.ts +0 -2
- package/dist/dist/ssr/live.d.ts +0 -6
- package/dist/dist/ssr/not-found.d.ts +0 -2
- package/dist/dist/ssr/ssr.d.ts +0 -2
- package/dist/dist/ssr/stream.d.ts +0 -14
- package/dist/dist/ssr/tailwind.d.ts +0 -2
- package/dist/dist/tenant/client.d.ts +0 -2
- package/dist/dist/tenant/graphql.d.ts +0 -3
- package/dist/dist/tenant/index.d.ts +0 -2
- package/dist/dist/tenant/migrate.d.ts +0 -6
- package/dist/dist/tenant/rest.d.ts +0 -3
- package/dist/dist/tenant/schema.d.ts +0 -5
- package/dist/dist/tenant/types.d.ts +0 -48
- package/dist/dist/tenant/utils.d.ts +0 -10
- package/dist/dist/types.d.ts +0 -19
- package/dist/dist/use-flash-message.d.ts +0 -1
- package/dist/i18n.d.ts +0 -6
- package/dist/logdb/migrate.d.ts +0 -5
- package/dist/messager/migrate.d.ts +0 -2
- package/dist/middleware.d.ts +0 -21
- package/dist/opencode/migrate.d.ts +0 -2
- package/dist/postgres/migrate.d.ts +0 -3
- package/dist/postgres/table.d.ts +0 -4
- package/dist/root-layout.d.ts +0 -4
- package/dist/tenant/migrate.d.ts +0 -6
- package/dist/tsx-instance.d.ts +0 -43
- package/dist/tsx.d.ts +0 -8
- package/dist/user/migrate.d.ts +0 -6
- package/dist/workflow/engine.d.ts +0 -7
- package/dist/workflow/index.d.ts +0 -8
- package/dist/workflow/llm.d.ts +0 -10
- package/dist/workflow/nodes.d.ts +0 -10
- package/dist/workflow/reference.d.ts +0 -3
- package/dist/workflow/route.d.ts +0 -11
- package/dist/workflow/sse.d.ts +0 -2
- package/dist/workflow/tool.d.ts +0 -8
- package/dist/workflow/types.d.ts +0 -86
- /package/cli/template/ui/{app.css → app/globals.css} +0 -0
- /package/cli/template/ui/{layout.tsx → app/layout.tsx} +0 -0
- /package/opencode/ui/{app.css → app/globals.css} +0 -0
- /package/opencode/ui/{layout.tsx → app/layout.tsx} +0 -0
- /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
|
|
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
|
-
|
|
7
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
|
221
|
-
}
|
|
222
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
329
|
+
node = getOrCreateChild(node, segment, createWsNode, true);
|
|
342
330
|
}
|
|
343
331
|
node.handler = handler;
|
|
344
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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 =
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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
|
-
|
|
432
|
-
|
|
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
|
-
|
|
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
|
-
|
|
439
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
529
|
-
|
|
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
|
-
|
|
566
|
-
|
|
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
|
-
//
|
|
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
|
|
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 (
|
|
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
|
-
|
|
794
|
-
if (!
|
|
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 = `"${
|
|
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 &&
|
|
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(
|
|
825
|
+
"Content-Length": String(stat2.size),
|
|
814
826
|
"ETag": etag,
|
|
815
|
-
"Last-Modified":
|
|
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 (
|
|
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
|
-
|
|
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 ?? ((
|
|
1112
|
-
const forwarded =
|
|
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 =
|
|
1127
|
+
const realIp = _req.headers.get("x-real-ip");
|
|
1115
1128
|
if (realIp) return realIp;
|
|
1116
|
-
const cfIp =
|
|
1129
|
+
const cfIp = _req.headers.get("cf-connecting-ip");
|
|
1117
1130
|
if (cfIp) return cfIp;
|
|
1118
|
-
return
|
|
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 =
|
|
1130
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
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",
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
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:
|
|
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
|
|
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((
|
|
4753
|
+
return new Promise((resolve14) => {
|
|
4701
4754
|
const timer = setTimeout(() => {
|
|
4702
4755
|
mp.child.kill("SIGKILL");
|
|
4703
|
-
|
|
4756
|
+
resolve14();
|
|
4704
4757
|
}, timeout);
|
|
4705
4758
|
mp.child.on("exit", () => {
|
|
4706
4759
|
clearTimeout(timer);
|
|
4707
|
-
|
|
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 {
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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
|
-
|
|
5144
|
+
const outDir = resolve3(OUT_DIR);
|
|
5145
|
+
mkdirSync(outDir, { recursive: true });
|
|
5094
5146
|
const hash = id(absPath);
|
|
5095
|
-
const outPath = join2(
|
|
5147
|
+
const outPath = join2(outDir, hash + ".js");
|
|
5096
5148
|
await esbuild.build({
|
|
5097
5149
|
entryPoints: { [hash]: absPath },
|
|
5098
|
-
outdir:
|
|
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
|
|
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:
|
|
5216
|
-
const rb = opts.rootBase || "";
|
|
5267
|
+
const { ctx, base, compiledTailwindCss, isDev: isDev3 } = opts;
|
|
5217
5268
|
let result = "";
|
|
5218
|
-
if (
|
|
5219
|
-
const vUrl = `${
|
|
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
|
-
|
|
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
|
|
5319
|
-
const
|
|
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
|
-
//
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
var
|
|
5348
|
-
var
|
|
5349
|
-
function
|
|
5350
|
-
|
|
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
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
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
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
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
|
-
|
|
5483
|
+
hotBundleCache.set(hash, code);
|
|
5365
5484
|
}
|
|
5366
|
-
function
|
|
5367
|
-
|
|
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
|
|
5370
|
-
const
|
|
5371
|
-
|
|
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
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
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
|
|
5513
|
+
function liveRouter(dir) {
|
|
5605
5514
|
const r = new Router();
|
|
5606
|
-
|
|
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
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
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 =
|
|
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 =
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5599
|
+
return {
|
|
5600
|
+
close: () => {
|
|
5601
|
+
watcher.close();
|
|
5602
|
+
clients.clear();
|
|
5603
|
+
}
|
|
5697
5604
|
};
|
|
5698
|
-
return r;
|
|
5699
5605
|
}
|
|
5700
5606
|
|
|
5701
|
-
//
|
|
5702
|
-
function
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
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 (
|
|
5714
|
-
r.use(
|
|
5896
|
+
if (existsSync4(join5(dir, "app", "globals.css"))) {
|
|
5897
|
+
r.use("/", tailwindRouter(dir));
|
|
5715
5898
|
}
|
|
5716
5899
|
if (isDev2) {
|
|
5717
|
-
|
|
5718
|
-
r.
|
|
5719
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
6091
|
-
import { existsSync as
|
|
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 ?
|
|
6301
|
+
const searchDir = path2 ? resolve10(ctx.workspace, path2) : ctx.workspace;
|
|
6103
6302
|
try {
|
|
6104
6303
|
let stdout;
|
|
6105
|
-
if (
|
|
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
|
|
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 ?
|
|
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((
|
|
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
|
-
|
|
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(
|
|
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
|
|
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(
|
|
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
|
-
|
|
6850
|
+
timer = null;
|
|
6851
|
+
evict(forceAll = false) {
|
|
6653
6852
|
if (this.days.size <= MAX_MEM_ENTRIES) return;
|
|
6654
|
-
const
|
|
6655
|
-
const toDelete =
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
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
|
-
|
|
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 {
|
|
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 ?
|
|
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 =
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
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
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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) ??
|
|
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((
|
|
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:
|
|
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((
|
|
8214
|
-
resolveReady =
|
|
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((
|
|
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:
|
|
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
|
-
|
|
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,
|