weifuwu 0.19.4 → 0.19.6

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/dist/index.js CHANGED
@@ -376,13 +376,13 @@ var Router = class _Router {
376
376
  const mw = match.middlewares[index++];
377
377
  return mw(innerReq, ctx2, dispatch);
378
378
  }
379
- return await new Promise((resolve14) => {
379
+ return await new Promise((resolve15) => {
380
380
  try {
381
381
  upgradeSocket(router.wss, req, socket, head, match.handler, ctx2);
382
- resolve14(new Response(null, { status: 101 }));
382
+ resolve15(new Response(null, { status: 101 }));
383
383
  } catch {
384
384
  socket.destroy();
385
- resolve14(new Response("WebSocket upgrade failed", { status: 500 }));
385
+ resolve15(new Response("WebSocket upgrade failed", { status: 500 }));
386
386
  }
387
387
  });
388
388
  };
@@ -2845,7 +2845,7 @@ function createHub(opts) {
2845
2845
  }
2846
2846
  });
2847
2847
  }
2848
- function join8(key, ws) {
2848
+ function join9(key, ws) {
2849
2849
  if (!channels.has(key)) {
2850
2850
  channels.set(key, /* @__PURE__ */ new Set());
2851
2851
  redisSub?.subscribe(`${prefix}${key}`);
@@ -2886,7 +2886,7 @@ function createHub(opts) {
2886
2886
  await redisSub.quit();
2887
2887
  }
2888
2888
  }
2889
- return { join: join8, leave, broadcast, close };
2889
+ return { join: join9, leave, broadcast, close };
2890
2890
  }
2891
2891
 
2892
2892
  // queue/index.ts
@@ -4584,16 +4584,8 @@ import path from "node:path";
4584
4584
 
4585
4585
  // deploy/gateway.ts
4586
4586
  import WebSocket2, { WebSocketServer as WebSocketServer2 } from "ws";
4587
- function isBareDomain(host, domain) {
4588
- return host === domain || host === `www.${domain}`;
4589
- }
4590
4587
  function matchApp(config, getPort, host, pathname) {
4591
- for (const [name, ac] of Object.entries(config.apps)) {
4592
- if (ac.subdomain && host === `${ac.subdomain}.${config.domain}`) {
4593
- const port = getPort(name);
4594
- if (port) return { name, port };
4595
- }
4596
- }
4588
+ const domain = config.domain || "localhost";
4597
4589
  const pathApps = Object.entries(config.apps).filter(([, ac]) => ac.path).sort(([, a], [, b]) => (b.path?.length ?? 0) - (a.path?.length ?? 0));
4598
4590
  for (const [name, ac] of pathApps) {
4599
4591
  if (ac.path && pathname.startsWith(ac.path)) {
@@ -4601,7 +4593,16 @@ function matchApp(config, getPort, host, pathname) {
4601
4593
  if (port) return { name, port, stripPath: ac.path };
4602
4594
  }
4603
4595
  }
4604
- if (config.defaultApp && isBareDomain(host, config.domain)) {
4596
+ for (const [name, ac] of Object.entries(config.apps)) {
4597
+ if (ac.path) continue;
4598
+ const hostMatch = host === `${name}.${domain}` || host === name;
4599
+ const pathMatch = pathname === `/${name}` || pathname.startsWith(`/${name}/`);
4600
+ if (hostMatch || pathMatch) {
4601
+ const port = getPort(name);
4602
+ if (port) return { name, port };
4603
+ }
4604
+ }
4605
+ if (config.defaultApp) {
4605
4606
  const port = getPort(config.defaultApp);
4606
4607
  if (port) return { name: config.defaultApp, port };
4607
4608
  }
@@ -4696,14 +4697,14 @@ function forkApp(opts) {
4696
4697
  return { child, port: opts.port };
4697
4698
  }
4698
4699
  function stopProcess(mp, timeout = 1e4) {
4699
- return new Promise((resolve14) => {
4700
+ return new Promise((resolve15) => {
4700
4701
  const timer = setTimeout(() => {
4701
4702
  mp.child.kill("SIGKILL");
4702
- resolve14();
4703
+ resolve15();
4703
4704
  }, timeout);
4704
4705
  mp.child.on("exit", () => {
4705
4706
  clearTimeout(timer);
4706
- resolve14();
4707
+ resolve15();
4707
4708
  });
4708
4709
  mp.child.kill("SIGTERM");
4709
4710
  });
@@ -4814,83 +4815,36 @@ function createManager(config, apps, manager) {
4814
4815
  }
4815
4816
  });
4816
4817
  });
4817
- router.post("/reload", auth2, async () => {
4818
- try {
4819
- await manager.reloadConfig();
4820
- return Response.json({ success: true });
4821
- } catch (err) {
4822
- const msg = err instanceof Error ? err.message : String(err);
4823
- return Response.json({ error: msg }, { status: 400 });
4824
- }
4825
- });
4826
- router.post("/webhook", async (req) => {
4827
- const rawBody = await req.text();
4828
- if (config.webhookSecret) {
4829
- const sig = req.headers.get("x-hub-signature-256") ?? "";
4830
- const expected = `sha256=${crypto5.createHmac("sha256", config.webhookSecret).update(rawBody).digest("hex")}`;
4831
- const sigBuf = Buffer.from(sig);
4832
- const expectedBuf = Buffer.from(expected);
4833
- if (sigBuf.length !== expectedBuf.length || !crypto5.timingSafeEqual(sigBuf, expectedBuf)) {
4834
- return Response.json({ error: "invalid signature" }, { status: 401 });
4835
- }
4836
- }
4837
- let payload;
4838
- try {
4839
- payload = JSON.parse(rawBody);
4840
- } catch {
4841
- payload = {};
4842
- }
4843
- const repoUrl = payload?.repository?.clone_url ?? payload?.repository?.html_url ?? "";
4844
- if (!repoUrl) return Response.json({ deployed: [] });
4845
- const deployed = [];
4846
- for (const [name] of apps) {
4847
- const ac = apps.get(name)?.config;
4848
- if (!ac) continue;
4849
- const repoNorm = ac.repo.replace(/\.git$/, "").replace(/https:\/\/[^@]+@/, "https://");
4850
- const payloadNorm = repoUrl.replace(/\.git$/, "").replace(/https:\/\/[^@]+@/, "https://");
4851
- if (payloadNorm.includes(repoNorm)) {
4852
- await manager.deployApp(name);
4853
- deployed.push(name);
4854
- }
4855
- }
4856
- return Response.json({ deployed });
4857
- });
4858
4818
  return router;
4859
4819
  }
4860
4820
 
4861
4821
  // deploy/config.ts
4862
4822
  function defineConfig(config) {
4863
- if (!config.domain) throw new Error("deploy: domain is required");
4864
- if (!config.apps || Object.keys(config.apps).length === 0) {
4865
- throw new Error("deploy: at least one app is required");
4866
- }
4867
- for (const [name, app] of Object.entries(config.apps)) {
4868
- if (!app.repo) throw new Error(`deploy: app "${name}" has no repo`);
4869
- if (!app.entry) throw new Error(`deploy: app "${name}" has no entry`);
4870
- if (!app.port) throw new Error(`deploy: app "${name}" has no port`);
4823
+ const domain = config.domain || "localhost";
4824
+ const port = config.port ?? 3e3;
4825
+ let nextPort = 3001;
4826
+ for (const [name, ac] of Object.entries(config.apps)) {
4827
+ ac.dir ??= name;
4828
+ ac.entry ??= "index.ts";
4829
+ ac.port ??= nextPort++;
4830
+ if (!ac.path && domain === "localhost") {
4831
+ ac.path = `/${name}`;
4832
+ }
4871
4833
  }
4872
- return {
4873
- port: config.port ?? 80,
4874
- appsDir: config.appsDir ?? "/opt/weifuwu/apps",
4875
- ...config
4876
- };
4834
+ return { ...config, domain, port };
4877
4835
  }
4878
4836
 
4879
4837
  // deploy/index.ts
4880
4838
  async function deploy(config) {
4881
- const appsDir = config.appsDir ?? "/opt/weifuwu/apps";
4882
4839
  const apps = /* @__PURE__ */ new Map();
4883
4840
  let httpServer;
4884
- if (!fs.existsSync(appsDir)) {
4885
- fs.mkdirSync(appsDir, { recursive: true });
4886
- }
4887
- async function forkAndCheck(cwd, entry, port, env, onLog, healthEndpoint) {
4841
+ async function forkAndCheck(name, cwd, entry, port, env, onLog, healthEndpoint) {
4888
4842
  try {
4889
4843
  const mp = forkApp({ cwd, entry, port, env, onLog });
4890
- onLog(`[deploy] forked pid ${mp.child.pid} on port ${mp.port}`);
4844
+ onLog(`[deploy] forked ${name} (pid ${mp.child.pid}) on port ${mp.port}`);
4891
4845
  const healthy = await healthCheck(port, healthEndpoint ?? "/");
4892
- if (healthy) onLog("[deploy] health check passed");
4893
- else onLog("[deploy] health check failed");
4846
+ if (healthy) onLog(`[deploy] health check passed`);
4847
+ else onLog(`[deploy] health check failed`);
4894
4848
  return mp;
4895
4849
  } catch (err) {
4896
4850
  const msg = err instanceof Error ? err.message : String(err);
@@ -4912,49 +4866,15 @@ async function deploy(config) {
4912
4866
  clearTimeout(old.restartTimer);
4913
4867
  old.restartTimer = void 0;
4914
4868
  }
4915
- const appDir = path.join(appsDir, name);
4869
+ const appDir = path.resolve(ac.dir);
4916
4870
  const logs = [];
4917
4871
  const log = (line) => {
4918
4872
  logs.push(line);
4919
4873
  if (logs.length > 1e3) logs.splice(0, logs.length - 1e3);
4920
4874
  };
4921
- try {
4922
- if (typeof ac.repo !== "string" || !/^https?:\/\/[^\s"']+\/[^\s"']+/.test(ac.repo) && !/^git@[^\s"']+:[^\s"']+/.test(ac.repo)) {
4923
- throw new Error(`Invalid repo URL: ${ac.repo}`);
4924
- }
4925
- if (ac.branch && typeof ac.branch === "string" && !/^[\w.\-/]+$/.test(ac.branch)) {
4926
- throw new Error(`Invalid branch name: ${ac.branch}`);
4927
- }
4928
- if (fs.existsSync(path.join(appDir, ".git"))) {
4929
- execSync("git pull", { cwd: appDir, stdio: "pipe", timeout: 12e4 });
4930
- log("[deploy] git pull done");
4931
- } else {
4932
- if (fs.existsSync(appDir)) {
4933
- fs.rmSync(appDir, { recursive: true });
4934
- }
4935
- execSync(`git clone --depth=1 ${ac.repo} ${appDir}`, { stdio: "pipe", timeout: 12e4 });
4936
- log("[deploy] git clone done");
4937
- if (ac.branch) {
4938
- execSync(`git checkout ${ac.branch}`, { cwd: appDir, stdio: "pipe", timeout: 3e4 });
4939
- log(`[deploy] switched to branch ${ac.branch}`);
4940
- }
4941
- }
4942
- } catch (err) {
4943
- const msg = err instanceof Error ? err.message : String(err);
4944
- setAppRuntime(name, ac, logs, { status: "error", port: ac.port, error: msg });
4945
- log(`[deploy] git error: ${msg}`);
4946
- if (old?.process) {
4947
- apps.set(name, old);
4948
- }
4949
- return;
4950
- }
4951
- try {
4952
- execSync("npm install", { cwd: appDir, stdio: "pipe", timeout: 12e4 });
4953
- log("[deploy] npm install done");
4954
- } catch (err) {
4955
- const msg = err instanceof Error ? err.message : String(err);
4956
- setAppRuntime(name, ac, logs, { status: "error", port: ac.port, error: msg });
4957
- log(`[deploy] npm install error: ${msg}`);
4875
+ if (!fs.existsSync(appDir)) {
4876
+ setAppRuntime(name, ac, logs, { status: "error", error: `dir not found: ${appDir}` });
4877
+ log(`[deploy] dir not found: ${appDir}`);
4958
4878
  if (old?.process) apps.set(name, old);
4959
4879
  return;
4960
4880
  }
@@ -4978,7 +4898,7 @@ async function deploy(config) {
4978
4898
  if (ac.ports && old?.process) {
4979
4899
  targetPort = old.currentPort === ac.ports[0] ? ac.ports[1] : ac.ports[0];
4980
4900
  }
4981
- const mp = await forkAndCheck(appDir, ac.entry, targetPort, ac.env, log, ac.healthEndpoint);
4901
+ const mp = await forkAndCheck(name, appDir, ac.entry, targetPort, ac.env, log, ac.healthEndpoint);
4982
4902
  if (!mp) {
4983
4903
  log("[deploy] new process failed to start, keeping old running");
4984
4904
  if (old?.process) apps.set(name, old);
@@ -4989,7 +4909,13 @@ async function deploy(config) {
4989
4909
  }
4990
4910
  const runtime = {
4991
4911
  config: ac,
4992
- status: { name, status: "running", port: targetPort, subdomain: ac.subdomain, path: ac.path, pid: mp.child.pid ?? void 0 },
4912
+ status: {
4913
+ name,
4914
+ status: "running",
4915
+ port: targetPort,
4916
+ path: ac.path,
4917
+ pid: mp.child.pid ?? void 0
4918
+ },
4993
4919
  logs,
4994
4920
  process: mp.child,
4995
4921
  currentPort: targetPort,
@@ -5023,7 +4949,7 @@ async function deploy(config) {
5023
4949
  status: { name, ...overrides },
5024
4950
  logs,
5025
4951
  process: null,
5026
- currentPort: overrides.port ?? ac.port,
4952
+ currentPort: overrides.port ?? ac.port ?? 0,
5027
4953
  startedAt: null,
5028
4954
  restartCount: 0,
5029
4955
  restartTimer: void 0
@@ -5052,14 +4978,10 @@ async function deploy(config) {
5052
4978
  }
5053
4979
  return gw.handler(req, ctx);
5054
4980
  };
5055
- if (config.ssl) {
5056
- ensureCertificates(config);
5057
- }
5058
4981
  httpServer = serve(fullHandler, {
5059
4982
  port: config.port,
5060
4983
  websocket: gw.wsHandler
5061
4984
  });
5062
- const portSuffix = config.port !== 80 ? `:${config.port}` : "";
5063
4985
  return {
5064
4986
  close: async () => {
5065
4987
  for (const [, app] of apps) {
@@ -5071,7 +4993,7 @@ async function deploy(config) {
5071
4993
  httpServer?.stop();
5072
4994
  },
5073
4995
  ready: httpServer.ready,
5074
- url: `http://${config.domain}${portSuffix}`,
4996
+ url: `http://localhost:${config.port}/`,
5075
4997
  apps: {
5076
4998
  list: () => Array.from(apps.values()).map((a) => a.status),
5077
4999
  status: (name) => apps.get(name)?.status,
@@ -5096,44 +5018,12 @@ async function deploy(config) {
5096
5018
  }
5097
5019
  };
5098
5020
  }
5099
- function ensureCertificates(config) {
5100
- const { domain, ssl } = config;
5101
- if (!ssl) return;
5102
- const certDir = "/etc/weifuwu/ssl";
5103
- const certPath = path.join(certDir, `${domain}.pem`);
5104
- const keyPath = path.join(certDir, `${domain}-key.pem`);
5105
- if (fs.existsSync(certPath) && fs.existsSync(keyPath)) return;
5106
- if (!fs.existsSync(certDir)) {
5107
- fs.mkdirSync(certDir, { recursive: true });
5108
- }
5109
- const acmeHome = path.join(certDir, ".acme.sh");
5110
- try {
5111
- execSync("which acme.sh", { stdio: "pipe" });
5112
- } catch {
5113
- execSync(
5114
- `curl -s https://get.acme.sh | sh -s email=${ssl.email}`,
5115
- { stdio: "pipe", timeout: 6e4 }
5116
- );
5117
- }
5118
- const subdomains = Object.values(config.apps).filter((a) => a.subdomain).map((a) => `${a.subdomain}.${domain}`).join(",");
5119
- const allDomains = subdomains ? `${domain},${subdomains}` : domain;
5120
- const acmeSh = path.join(acmeHome, "acme.sh");
5121
- const staging = ssl.staging ? " --staging" : "";
5122
- execSync(
5123
- `${acmeSh} --issue -d ${allDomains} --standalone${staging} --cert-file ${certPath} --key-file ${keyPath}`,
5124
- { stdio: "pipe", timeout: 12e4 }
5125
- );
5126
- execSync(
5127
- `${acmeSh} --install-cronjob`,
5128
- { stdio: "pipe", timeout: 3e4 }
5129
- );
5130
- }
5131
5021
 
5132
5022
  // opencode/client.ts
5133
5023
  import { createOpenAI as createOpenAI3 } from "@ai-sdk/openai";
5134
5024
 
5135
5025
  // opencode/rest.ts
5136
- import { join as join5 } from "node:path";
5026
+ import { join as join7 } from "node:path";
5137
5027
 
5138
5028
  // ssr.ts
5139
5029
  import { createElement } from "react";
@@ -5341,7 +5231,8 @@ function buildHeadPayload(opts) {
5341
5231
  `;
5342
5232
  }
5343
5233
  if (compiledTailwindCss) {
5344
- result += `<link rel="stylesheet" href="${base}/__wfw/style.css" />
5234
+ const cssUrl = ctx.tailwindCssUrl || "/__wfw/style.css";
5235
+ result += `<link rel="stylesheet" href="${cssUrl}" />
5345
5236
  `;
5346
5237
  }
5347
5238
  const localeData = ctx.parsed?.__localeData ?? globalThis.__LOCALE_DATA__;
@@ -5506,7 +5397,7 @@ async function buildClientBundle(entryPath, layoutPaths) {
5506
5397
  jsxImportSource: "react",
5507
5398
  banner: { js: "self.process={env:{}};" },
5508
5399
  loader: { ".node": "empty" },
5509
- external: isDev ? ["react", "react-dom", "react-dom/client", "react/jsx-runtime", "weifuwu/react"] : void 0,
5400
+ external: isDev ? ["react", "react-dom", "react-dom/client", "react/jsx-runtime", "weifuwu", "weifuwu/react"] : void 0,
5510
5401
  write: false,
5511
5402
  minify: !isDev
5512
5403
  });
@@ -5600,7 +5491,12 @@ function ssr(path2) {
5600
5491
  return r;
5601
5492
  }
5602
5493
 
5494
+ // root-layout.ts
5495
+ import { existsSync as existsSync5 } from "node:fs";
5496
+ import { join as join5, resolve as resolve7 } from "node:path";
5497
+
5603
5498
  // tailwind.ts
5499
+ import { createHash as createHash4 } from "node:crypto";
5604
5500
  import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "node:fs";
5605
5501
  import { join as join3, relative, resolve as resolve5 } from "node:path";
5606
5502
  var extraSources = /* @__PURE__ */ new Set();
@@ -5611,16 +5507,21 @@ function tailwind(dir) {
5611
5507
  const r = new Router();
5612
5508
  r.use(async (req, ctx, next) => {
5613
5509
  if (!cssCache.has(cssPath)) {
5614
- cssCache.set(cssPath, await compileTailwindCss(cssPath, cssDir));
5510
+ await compileTailwindCss(cssPath, cssDir);
5615
5511
  }
5616
- ctx.compiledTailwindCss = cssCache.get(cssPath);
5512
+ const entry = cssCache.get(cssPath);
5513
+ ctx.compiledTailwindCss = entry.css;
5514
+ const base = (ctx.mountPath || "").replace(/\/$/, "");
5515
+ ctx.tailwindCssUrl = base ? `${base}/__wfw/style/${entry.hash}.css` : `/__wfw/style/${entry.hash}.css`;
5617
5516
  return next(req, ctx);
5618
5517
  });
5619
- r.get("/__wfw/style.css", async (req, ctx) => {
5518
+ r.get("/__wfw/style/:hash.css", async (req, ctx) => {
5620
5519
  if (!cssCache.has(cssPath)) {
5621
- cssCache.set(cssPath, await compileTailwindCss(cssPath, cssDir));
5520
+ await compileTailwindCss(cssPath, cssDir);
5622
5521
  }
5623
- return new Response(cssCache.get(cssPath) || "", {
5522
+ const entry = cssCache.get(cssPath);
5523
+ if (!entry) return new Response("", { status: 404 });
5524
+ return new Response(entry.css, {
5624
5525
  headers: { "content-type": "text/css; charset=utf-8" }
5625
5526
  });
5626
5527
  });
@@ -5643,7 +5544,8 @@ ${src}`;
5643
5544
  ${src}`;
5644
5545
  }
5645
5546
  const result = await postcss([tailwindPlugin()]).process(src, { from: cssPath });
5646
- cssCache.set(cssPath, result.css);
5547
+ const hash = createHash4("md5").update(result.css).digest("hex").slice(0, 8);
5548
+ cssCache.set(cssPath, { css: result.css, hash });
5647
5549
  return result.css;
5648
5550
  } catch (err) {
5649
5551
  console.warn("Tailwind CSS processing failed:", err.message);
@@ -5651,20 +5553,144 @@ ${src}`;
5651
5553
  }
5652
5554
  }
5653
5555
 
5654
- // layout.ts
5655
- function layout(path2) {
5656
- return async (req, ctx, next) => {
5657
- const mod = await compile(path2);
5658
- const Component = mod.default;
5659
- if (!Component) throw new Error(`Layout ${path2} has no default export`);
5660
- ctx.layoutStack = [...ctx.layoutStack || [], { path: path2, component: Component }];
5661
- return next(req, ctx);
5556
+ // live.ts
5557
+ import chokidar from "chokidar";
5558
+ import { existsSync as existsSync4 } from "node:fs";
5559
+ import { join as join4, resolve as resolve6 } from "node:path";
5560
+ var clients = /* @__PURE__ */ new Set();
5561
+ var hotBundleCache = /* @__PURE__ */ new Map();
5562
+ var hotKeys = [];
5563
+ var MAX_HOT = 10;
5564
+ function setHot(hash, code) {
5565
+ if (!hotBundleCache.has(hash)) {
5566
+ hotKeys.push(hash);
5567
+ if (hotKeys.length > MAX_HOT) {
5568
+ const old = hotKeys.shift();
5569
+ hotBundleCache.delete(old);
5570
+ }
5571
+ }
5572
+ hotBundleCache.set(hash, code);
5573
+ }
5574
+ function broadcastReload() {
5575
+ for (const ws of clients) {
5576
+ try {
5577
+ ws.send("reload");
5578
+ } catch {
5579
+ clients.delete(ws);
5580
+ }
5581
+ }
5582
+ }
5583
+ function broadcastCss(css) {
5584
+ const msg = JSON.stringify({ type: "css", css });
5585
+ for (const ws of clients) {
5586
+ try {
5587
+ ws.send(msg);
5588
+ } catch {
5589
+ clients.delete(ws);
5590
+ }
5591
+ }
5592
+ }
5593
+ function liveReload(dir) {
5594
+ const r = new Router();
5595
+ const resolved = resolve6(dir);
5596
+ const entryPath = join4(resolved, "page.tsx");
5597
+ r.get("/__wfw/v/bundle", async (req, ctx) => {
5598
+ const code = await compileVendorBundle();
5599
+ return new Response(code, {
5600
+ headers: { "content-type": "application/javascript; charset=utf-8" }
5601
+ });
5602
+ });
5603
+ r.get("/__wfw/h/:hash", async (req, ctx) => {
5604
+ const hash = ctx.params.hash.replace(/\.js$/i, "");
5605
+ const code = hotBundleCache.get(hash);
5606
+ if (!code) return new Response("", { status: 404 });
5607
+ return new Response(code, {
5608
+ headers: { "content-type": "application/javascript; charset=utf-8" }
5609
+ });
5610
+ });
5611
+ r.ws("/__weifuwu/livereload", {
5612
+ open(ws) {
5613
+ clients.add(ws);
5614
+ ws.on("close", () => clients.delete(ws));
5615
+ ws.on("error", () => clients.delete(ws));
5616
+ }
5617
+ });
5618
+ const watcher = chokidar.watch(dir, {
5619
+ ignored: /(^|[/\\])\.|node_modules|[/\\]\.weifuwu[/\\]/,
5620
+ ignoreInitial: true
5621
+ });
5622
+ watcher.on("change", async (filePath) => {
5623
+ if (/\.tsx?$/i.test(filePath)) {
5624
+ if (filePath.endsWith("layout.tsx")) {
5625
+ return broadcastReload();
5626
+ }
5627
+ clearCompileCache();
5628
+ markClientBundleDirty();
5629
+ try {
5630
+ const target = existsSync4(entryPath) ? entryPath : filePath;
5631
+ await compileTsxDev(target);
5632
+ const { hash, code } = await compileHotComponent(target);
5633
+ setHot(hash, code);
5634
+ let css;
5635
+ const cssPath = join4(resolved, "app.css");
5636
+ if (existsSync4(cssPath)) {
5637
+ css = await compileTailwindCss(cssPath, resolved);
5638
+ }
5639
+ const entry = id(entryPath);
5640
+ const msg = { type: "component", hash, entry };
5641
+ if (css) msg.css = css;
5642
+ const str = JSON.stringify(msg);
5643
+ for (const ws of clients) {
5644
+ try {
5645
+ ws.send(str);
5646
+ } catch {
5647
+ clients.delete(ws);
5648
+ }
5649
+ }
5650
+ } catch (e) {
5651
+ console.error("live reload failed, fallback to full reload:", e);
5652
+ broadcastReload();
5653
+ }
5654
+ } else if (/\.css$/i.test(filePath)) {
5655
+ const cssPath = join4(resolved, "app.css");
5656
+ if (existsSync4(cssPath)) {
5657
+ const css = await compileTailwindCss(cssPath, resolved);
5658
+ if (css) broadcastCss(css);
5659
+ }
5660
+ }
5661
+ });
5662
+ r.close = () => {
5663
+ watcher.close();
5664
+ clients.clear();
5662
5665
  };
5666
+ return r;
5667
+ }
5668
+
5669
+ // root-layout.ts
5670
+ function rootLayout(dir) {
5671
+ const r = new Router();
5672
+ const resolved = resolve7(dir);
5673
+ const isDev2 = process.env.NODE_ENV !== "production";
5674
+ const layoutPath = join5(resolved, "layout.tsx");
5675
+ r.use(async (req, ctx, next) => {
5676
+ const mod = await compile(layoutPath);
5677
+ if (mod?.default) ctx.layoutStack = [{ path: layoutPath, component: mod.default }];
5678
+ return next(req, ctx);
5679
+ });
5680
+ if (existsSync5(join5(resolved, "app.css"))) {
5681
+ r.use(tailwind(resolved));
5682
+ }
5683
+ if (isDev2) {
5684
+ const lr = liveReload(resolved);
5685
+ r.use(lr);
5686
+ r.close = lr.close;
5687
+ }
5688
+ return r;
5663
5689
  }
5664
5690
 
5665
5691
  // opencode/session.ts
5666
5692
  import { randomUUID as randomUUID2 } from "node:crypto";
5667
- import { join as join4 } from "node:path";
5693
+ import { join as join6 } from "node:path";
5668
5694
  import { mkdir as mkdir2 } from "node:fs/promises";
5669
5695
  var sessions = pgTable("_opencode_sessions", {
5670
5696
  id: uuid("id"),
@@ -5701,7 +5727,7 @@ async function createSession(sql2, opts, cwd, mountPath) {
5701
5727
  }
5702
5728
  function computeSessionWorkspace(cwd, mountPath, sessionId) {
5703
5729
  const name = !mountPath || mountPath === "/" ? "default" : mountPath.replace(/^\//, "");
5704
- return join4(cwd, ".sessions", name, sessionId);
5730
+ return join6(cwd, ".sessions", name, sessionId);
5705
5731
  }
5706
5732
  async function getSession(sql2, id3) {
5707
5733
  const { data: rows } = await sessions.readMany(sql2, { id: id3, active: true });
@@ -5898,10 +5924,10 @@ function createBashTool(ctx) {
5898
5924
  return { stdout: "", stderr: "Command denied: potentially dangerous command", exitCode: 1 };
5899
5925
  }
5900
5926
  const cwd = workdir ? `${ctx.workspace}/${workdir}` : ctx.workspace;
5901
- return new Promise((resolve14) => {
5927
+ return new Promise((resolve15) => {
5902
5928
  const child = exec(command, { cwd, timeout: timeout * 1e3, maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => {
5903
5929
  const truncated = stdout.length > 1e6 || stderr.length > 1e6;
5904
- resolve14({
5930
+ resolve15({
5905
5931
  stdout: stdout.slice(0, 1e6),
5906
5932
  stderr: stderr.slice(0, 1e6),
5907
5933
  exitCode: error?.code ?? 0,
@@ -5918,7 +5944,7 @@ function createBashTool(ctx) {
5918
5944
  import { tool as tool4 } from "ai";
5919
5945
  import { z as z6 } from "zod";
5920
5946
  import { readFileSync as readFileSync4 } from "node:fs";
5921
- import { resolve as resolve6 } from "node:path";
5947
+ import { resolve as resolve8 } from "node:path";
5922
5948
  function createReadTool(ctx) {
5923
5949
  return tool4({
5924
5950
  description: "Read file contents. Supports offset and limit for reading specific line ranges.",
@@ -5928,7 +5954,7 @@ function createReadTool(ctx) {
5928
5954
  limit: z6.number().optional().describe("Number of lines to read")
5929
5955
  }),
5930
5956
  execute: async ({ path: path2, offset, limit }) => {
5931
- const resolved = resolve6(ctx.workspace, path2);
5957
+ const resolved = resolve8(ctx.workspace, path2);
5932
5958
  if (!isPathAllowed(resolved, ctx.workspace, ctx.permissions)) {
5933
5959
  return { error: "Path not allowed", content: null, totalLines: 0 };
5934
5960
  }
@@ -5960,7 +5986,7 @@ function createReadTool(ctx) {
5960
5986
  import { tool as tool5 } from "ai";
5961
5987
  import { z as z7 } from "zod";
5962
5988
  import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync3 } from "node:fs";
5963
- import { resolve as resolve7, dirname as dirname3 } from "node:path";
5989
+ import { resolve as resolve9, dirname as dirname3 } from "node:path";
5964
5990
  function createWriteTool(ctx) {
5965
5991
  return tool5({
5966
5992
  description: "Create or overwrite a file. Parent directories are created automatically.",
@@ -5969,7 +5995,7 @@ function createWriteTool(ctx) {
5969
5995
  content: z7.string().describe("File content")
5970
5996
  }),
5971
5997
  execute: async ({ path: path2, content }) => {
5972
- const resolved = resolve7(ctx.workspace, path2);
5998
+ const resolved = resolve9(ctx.workspace, path2);
5973
5999
  if (!isPathAllowed(resolved, ctx.workspace, ctx.permissions)) {
5974
6000
  return { error: "Path not allowed" };
5975
6001
  }
@@ -5984,7 +6010,7 @@ function createWriteTool(ctx) {
5984
6010
  import { tool as tool6 } from "ai";
5985
6011
  import { z as z8 } from "zod";
5986
6012
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
5987
- import { resolve as resolve8 } from "node:path";
6013
+ import { resolve as resolve10 } from "node:path";
5988
6014
  function createEditTool(ctx) {
5989
6015
  return tool6({
5990
6016
  description: "Perform exact string replacements in a file. If oldString appears multiple times, provide more surrounding context.",
@@ -5995,7 +6021,7 @@ function createEditTool(ctx) {
5995
6021
  replaceAll: z8.boolean().default(false).describe("Replace all occurrences")
5996
6022
  }),
5997
6023
  execute: async ({ path: path2, oldString, newString, replaceAll }) => {
5998
- const resolved = resolve8(ctx.workspace, path2);
6024
+ const resolved = resolve10(ctx.workspace, path2);
5999
6025
  if (!isPathAllowed(resolved, ctx.workspace, ctx.permissions)) {
6000
6026
  return { error: "Path not allowed" };
6001
6027
  }
@@ -6028,8 +6054,8 @@ function createEditTool(ctx) {
6028
6054
  import { tool as tool7 } from "ai";
6029
6055
  import { z as z9 } from "zod";
6030
6056
  import { execFileSync } from "node:child_process";
6031
- import { resolve as resolve9 } from "node:path";
6032
- import { existsSync as existsSync4 } from "node:fs";
6057
+ import { resolve as resolve11 } from "node:path";
6058
+ import { existsSync as existsSync6 } from "node:fs";
6033
6059
  function createGrepTool(ctx) {
6034
6060
  return tool7({
6035
6061
  description: "Search file contents using regex. Supports file type filtering and context lines.",
@@ -6040,10 +6066,10 @@ function createGrepTool(ctx) {
6040
6066
  context: z9.number().default(0).describe("Number of context lines before and after each match")
6041
6067
  }),
6042
6068
  execute: async ({ pattern, include, path: path2, context }) => {
6043
- const searchDir = path2 ? resolve9(ctx.workspace, path2) : ctx.workspace;
6069
+ const searchDir = path2 ? resolve11(ctx.workspace, path2) : ctx.workspace;
6044
6070
  try {
6045
6071
  let stdout;
6046
- if (existsSync4("/usr/bin/rg") || existsSync4("/usr/local/bin/rg")) {
6072
+ if (existsSync6("/usr/bin/rg") || existsSync6("/usr/local/bin/rg")) {
6047
6073
  const args = ["-n"];
6048
6074
  if (context > 0) args.push("-C", String(context));
6049
6075
  if (include) args.push("-g", include);
@@ -6072,7 +6098,7 @@ function createGrepTool(ctx) {
6072
6098
  import { tool as tool8 } from "ai";
6073
6099
  import { z as z10 } from "zod";
6074
6100
  import { execFileSync as execFileSync2 } from "node:child_process";
6075
- import { resolve as resolve10 } from "node:path";
6101
+ import { resolve as resolve12 } from "node:path";
6076
6102
  function createGlobTool(ctx) {
6077
6103
  return tool8({
6078
6104
  description: "Find files matching a glob pattern.",
@@ -6081,7 +6107,7 @@ function createGlobTool(ctx) {
6081
6107
  path: z10.string().optional().describe("Subdirectory relative to workspace")
6082
6108
  }),
6083
6109
  execute: async ({ pattern, path: path2 }) => {
6084
- const searchDir = path2 ? resolve10(ctx.workspace, path2) : ctx.workspace;
6110
+ const searchDir = path2 ? resolve12(ctx.workspace, path2) : ctx.workspace;
6085
6111
  try {
6086
6112
  const stdout = execFileSync2("find", [
6087
6113
  searchDir,
@@ -6142,7 +6168,7 @@ function createQuestionTool(ctx) {
6142
6168
  options: z12.array(z12.string()).optional().describe("Optional multiple choice options")
6143
6169
  }),
6144
6170
  execute: async ({ question, options }, { toolCallId }) => {
6145
- return new Promise((resolve14, reject) => {
6171
+ return new Promise((resolve15, reject) => {
6146
6172
  const timeout = setTimeout(() => {
6147
6173
  ctx.pendingQuestions.delete(toolCallId);
6148
6174
  reject(new Error("Question timed out"));
@@ -6150,7 +6176,7 @@ function createQuestionTool(ctx) {
6150
6176
  ctx.pendingQuestions.set(toolCallId, {
6151
6177
  resolve: (answer) => {
6152
6178
  clearTimeout(timeout);
6153
- resolve14(answer);
6179
+ resolve15(answer);
6154
6180
  },
6155
6181
  reject: (err) => {
6156
6182
  clearTimeout(timeout);
@@ -6297,9 +6323,8 @@ async function buildRouter4(deps) {
6297
6323
  });
6298
6324
  try {
6299
6325
  const uiDir = new URL("../opencode/ui/", import.meta.url).pathname;
6300
- router.use(tailwind(uiDir));
6301
- router.use(layout(join5(uiDir, "layout.tsx")));
6302
- router.get("/", ssr(join5(uiDir, "page.tsx")));
6326
+ router.use(rootLayout(uiDir));
6327
+ router.get("/", ssr(join7(uiDir, "page.tsx")));
6303
6328
  } catch (e) {
6304
6329
  console.warn("[opencode] UI not available:", e);
6305
6330
  }
@@ -6307,17 +6332,17 @@ async function buildRouter4(deps) {
6307
6332
  }
6308
6333
 
6309
6334
  // opencode/ws.ts
6310
- var clients = /* @__PURE__ */ new WeakMap();
6335
+ var clients2 = /* @__PURE__ */ new WeakMap();
6311
6336
  function createWSHandler2(deps) {
6312
6337
  const { sql: sql2, model, workspace, systemPrompt, skills, skillsRegistry, permissions, pendingQuestions } = deps;
6313
6338
  return {
6314
6339
  open(ws, ctx) {
6315
6340
  const userId = ctx.user?.id ?? 0;
6316
6341
  const mountPath = ctx.mountPath ?? "";
6317
- clients.set(ws, { userId, mountPath });
6342
+ clients2.set(ws, { userId, mountPath });
6318
6343
  },
6319
6344
  async message(ws, ctx, data) {
6320
- const client = clients.get(ws);
6345
+ const client = clients2.get(ws);
6321
6346
  if (!client) return;
6322
6347
  let msg;
6323
6348
  try {
@@ -6414,17 +6439,17 @@ function createWSHandler2(deps) {
6414
6439
  }
6415
6440
  },
6416
6441
  close(ws) {
6417
- const client = clients.get(ws);
6442
+ const client = clients2.get(ws);
6418
6443
  if (client) {
6419
6444
  client.abortController?.abort();
6420
- clients.delete(ws);
6445
+ clients2.delete(ws);
6421
6446
  }
6422
6447
  },
6423
6448
  error(ws, _ctx, _err) {
6424
- const client = clients.get(ws);
6449
+ const client = clients2.get(ws);
6425
6450
  if (client) {
6426
6451
  client.abortController?.abort();
6427
- clients.delete(ws);
6452
+ clients2.delete(ws);
6428
6453
  }
6429
6454
  }
6430
6455
  };
@@ -6434,7 +6459,7 @@ function createWSHandler2(deps) {
6434
6459
  import { readFile } from "node:fs/promises";
6435
6460
  import { glob } from "node:fs/promises";
6436
6461
  import { homedir } from "node:os";
6437
- import { resolve as resolve11 } from "node:path";
6462
+ import { resolve as resolve13 } from "node:path";
6438
6463
  import { parse as parseYaml } from "yaml";
6439
6464
  var SEARCH_DIRS = [
6440
6465
  (ws) => `${ws}/.opencode/skills`,
@@ -6472,7 +6497,7 @@ async function scanDir(dir) {
6472
6497
  try {
6473
6498
  const files = [];
6474
6499
  for await (const entry of glob("*/SKILL.md", { cwd: dir })) {
6475
- const skill = await parseSkillFile(resolve11(dir, entry));
6500
+ const skill = await parseSkillFile(resolve13(dir, entry));
6476
6501
  if (skill) files.push(skill);
6477
6502
  }
6478
6503
  return files;
@@ -6807,8 +6832,8 @@ function analytics(options) {
6807
6832
 
6808
6833
  // preferences.ts
6809
6834
  import { readFile as readFile2 } from "node:fs/promises";
6810
- import { existsSync as existsSync5 } from "node:fs";
6811
- import { join as join6, resolve as resolve12 } from "node:path";
6835
+ import { existsSync as existsSync7 } from "node:fs";
6836
+ import { join as join8, resolve as resolve14 } from "node:path";
6812
6837
  var defaults = {
6813
6838
  locale: { default: "en", cookie: "locale", fromAcceptLanguage: true },
6814
6839
  theme: { default: "system", cookie: "theme" }
@@ -6857,7 +6882,7 @@ async function handlePrefSwitch(req, value, cookieName, load) {
6857
6882
  });
6858
6883
  }
6859
6884
  function preferences(options) {
6860
- const dir = options.dir ? resolve12(options.dir) : void 0;
6885
+ const dir = options.dir ? resolve14(options.dir) : void 0;
6861
6886
  const localeOpts = { ...defaults.locale, ...options.locale };
6862
6887
  const themeOpts = { ...defaults.theme, ...options.theme };
6863
6888
  const cache2 = /* @__PURE__ */ new Map();
@@ -6869,8 +6894,8 @@ function preferences(options) {
6869
6894
  if (!validLocale(locale)) return {};
6870
6895
  const cached = cache2.get(locale);
6871
6896
  if (cached) return cached;
6872
- const filePath = join6(dir, `${locale}.json`);
6873
- if (existsSync5(filePath)) {
6897
+ const filePath = join8(dir, `${locale}.json`);
6898
+ if (existsSync7(filePath)) {
6874
6899
  try {
6875
6900
  const content = await readFile2(filePath, "utf-8");
6876
6901
  const data = JSON.parse(content);
@@ -7906,12 +7931,12 @@ function iii(opts = {}) {
7906
7931
  const handler = async (payload) => {
7907
7932
  if (!worker.ws) throw new Error(`Worker "${worker.name}" disconnected`);
7908
7933
  const invocationId = crypto6.randomUUID();
7909
- return new Promise((resolve14, reject) => {
7934
+ return new Promise((resolve15, reject) => {
7910
7935
  const timer = setTimeout(() => {
7911
7936
  pending.delete(invocationId);
7912
7937
  reject(new Error(`Invocation timed out for "${id3}"`));
7913
7938
  }, 3e4);
7914
- pending.set(invocationId, { resolve: resolve14, reject, timer });
7939
+ pending.set(invocationId, { resolve: resolve15, reject, timer });
7915
7940
  worker.ws.send(JSON.stringify({
7916
7941
  type: "invoke",
7917
7942
  invocation_id: invocationId,
@@ -8152,8 +8177,8 @@ function registerWorker(url) {
8152
8177
  function connect() {
8153
8178
  if (intentionalClose) return;
8154
8179
  ws = new WebSocket(url);
8155
- ready = new Promise((resolve14) => {
8156
- resolveReady = resolve14;
8180
+ ready = new Promise((resolve15) => {
8181
+ resolveReady = resolve15;
8157
8182
  });
8158
8183
  ws.onopen = () => {
8159
8184
  reconnectAttempt = 0;
@@ -8273,13 +8298,13 @@ function registerWorker(url) {
8273
8298
  }
8274
8299
  return Promise.resolve(fn(request.payload, ctx));
8275
8300
  }
8276
- return new Promise((resolve14, reject) => {
8301
+ return new Promise((resolve15, reject) => {
8277
8302
  const invocationId = genId();
8278
8303
  const timer = setTimeout(() => {
8279
8304
  pendingInvocations.delete(invocationId);
8280
8305
  reject(new Error(`Invocation timed out for "${request.function_id}"`));
8281
8306
  }, request.timeout_ms || 3e4);
8282
- pendingInvocations.set(invocationId, { resolve: resolve14, reject, timer });
8307
+ pendingInvocations.set(invocationId, { resolve: resolve15, reject, timer });
8283
8308
  send({
8284
8309
  type: "invoke",
8285
8310
  invocation_id: invocationId,
@@ -8300,142 +8325,70 @@ function registerWorker(url) {
8300
8325
  };
8301
8326
  }
8302
8327
 
8303
- // live.ts
8304
- import chokidar from "chokidar";
8305
- import { existsSync as existsSync6 } from "node:fs";
8306
- import { join as join7, resolve as resolve13 } from "node:path";
8307
- var clients2 = /* @__PURE__ */ new Set();
8308
- var hotBundleCache = /* @__PURE__ */ new Map();
8309
- var hotKeys = [];
8310
- var MAX_HOT = 10;
8311
- function setHot(hash, code) {
8312
- if (!hotBundleCache.has(hash)) {
8313
- hotKeys.push(hash);
8314
- if (hotKeys.length > MAX_HOT) {
8315
- const old = hotKeys.shift();
8316
- hotBundleCache.delete(old);
8317
- }
8318
- }
8319
- hotBundleCache.set(hash, code);
8320
- }
8321
- function broadcastReload() {
8322
- for (const ws of clients2) {
8323
- try {
8324
- ws.send("reload");
8325
- } catch {
8326
- clients2.delete(ws);
8327
- }
8328
- }
8329
- }
8330
- function broadcastCss(css) {
8331
- const msg = JSON.stringify({ type: "css", css });
8332
- for (const ws of clients2) {
8333
- try {
8334
- ws.send(msg);
8335
- } catch {
8336
- clients2.delete(ws);
8337
- }
8338
- }
8339
- }
8340
- function liveReload(opts) {
8341
- const r = new Router();
8342
- const entryPath = (() => {
8343
- for (const dir of opts.dirs) {
8344
- const p = join7(resolve13(dir), "page.tsx");
8345
- if (existsSync6(p)) return p;
8346
- }
8347
- return "";
8348
- })();
8349
- r.get("/__wfw/v/bundle", async (req, ctx) => {
8350
- const code = await compileVendorBundle();
8351
- return new Response(code, {
8352
- headers: { "content-type": "application/javascript; charset=utf-8" }
8353
- });
8354
- });
8355
- r.get("/__wfw/h/:hash", async (req, ctx) => {
8356
- const hash = ctx.params.hash.replace(/\.js$/i, "");
8357
- const code = hotBundleCache.get(hash);
8358
- if (!code) return new Response("", { status: 404 });
8359
- return new Response(code, {
8360
- headers: { "content-type": "application/javascript; charset=utf-8" }
8361
- });
8362
- });
8363
- r.ws("/__weifuwu/livereload", {
8364
- open(ws) {
8365
- clients2.add(ws);
8366
- ws.on("close", () => clients2.delete(ws));
8367
- ws.on("error", () => clients2.delete(ws));
8368
- }
8369
- });
8370
- const watcher = chokidar.watch(opts.dirs, {
8371
- ignored: /(^|[/\\])\.|node_modules|[/\\]\.weifuwu[/\\]/,
8372
- ignoreInitial: true
8373
- });
8374
- watcher.on("change", async (filePath) => {
8375
- if (/\.tsx?$/i.test(filePath)) {
8376
- clearCompileCache();
8377
- markClientBundleDirty();
8378
- try {
8379
- const target = entryPath || filePath;
8380
- await compileTsxDev(target);
8381
- const { hash, code } = await compileHotComponent(target);
8382
- setHot(hash, code);
8383
- let css;
8384
- for (const dir of opts.dirs) {
8385
- const cssPath = join7(resolve13(dir), "app.css");
8386
- if (existsSync6(cssPath)) {
8387
- css = await compileTailwindCss(cssPath, resolve13(dir));
8388
- }
8389
- }
8390
- const entry = entryPath ? id(entryPath) : "";
8391
- const msg = { type: "component", hash, entry };
8392
- if (css) msg.css = css;
8393
- const str = JSON.stringify(msg);
8394
- for (const ws of clients2) {
8395
- try {
8396
- ws.send(str);
8397
- } catch {
8398
- clients2.delete(ws);
8399
- }
8400
- }
8401
- } catch (e) {
8402
- console.error("live reload failed, fallback to full reload:", e);
8403
- broadcastReload();
8404
- }
8405
- } else if (/\.css$/i.test(filePath)) {
8406
- for (const dir of opts.dirs) {
8407
- const cssPath = join7(resolve13(dir), "app.css");
8408
- if (existsSync6(cssPath)) {
8409
- const css = await compileTailwindCss(cssPath, resolve13(dir));
8410
- if (css) broadcastCss(css);
8411
- }
8412
- }
8413
- }
8414
- });
8415
- r.close = () => {
8416
- watcher.close();
8417
- clients2.clear();
8328
+ // layout.ts
8329
+ function layout(path2) {
8330
+ return async (req, ctx, next) => {
8331
+ const mod = await compile(path2);
8332
+ const Component = mod.default;
8333
+ if (!Component) throw new Error(`Layout ${path2} has no default export`);
8334
+ ctx.layoutStack = [...ctx.layoutStack || [], { path: path2, component: Component }];
8335
+ return next(req, ctx);
8418
8336
  };
8419
- return r;
8420
8337
  }
8421
8338
 
8422
8339
  // not-found.ts
8340
+ import { createElement as createElement2 } from "react";
8423
8341
  function notFound(path2) {
8424
8342
  return async (req, ctx) => {
8425
8343
  if (!path2) return new Response("Not Found", { status: 404 });
8426
- const mod = await compile(path2);
8427
- const Component = mod?.default;
8428
- const body = Component ? "404 - Not Found" : "404 - Not Found";
8429
- return new Response(body, {
8430
- status: 404,
8431
- headers: { "content-type": "text/html; charset=utf-8" }
8344
+ let Component;
8345
+ try {
8346
+ const mod = await compile(path2);
8347
+ Component = mod?.default;
8348
+ } catch {
8349
+ return new Response("Not Found", { status: 404 });
8350
+ }
8351
+ if (!Component) return new Response("Not Found", { status: 404 });
8352
+ const layouts = ctx.layoutStack || [];
8353
+ const layoutComponents = layouts.map((l) => l.component);
8354
+ const base = (ctx.mountPath || "").replace(/\/$/, "");
8355
+ let element = createElement2(
8356
+ "div",
8357
+ { id: "__weifuwu_root" },
8358
+ createElement2(Component, null)
8359
+ );
8360
+ if (layoutComponents.length === 0) {
8361
+ element = createElement2(
8362
+ "html",
8363
+ { lang: "en" },
8364
+ createElement2(
8365
+ "head",
8366
+ null,
8367
+ createElement2("meta", { charSet: "utf-8" }),
8368
+ createElement2("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }),
8369
+ createElement2("title", null, "404")
8370
+ ),
8371
+ createElement2("body", null, element)
8372
+ );
8373
+ } else {
8374
+ for (const L of layoutComponents.toReversed()) {
8375
+ element = createElement2(L, { children: element });
8376
+ }
8377
+ }
8378
+ const { renderToReadableStream } = await import("react-dom/server");
8379
+ const stream = await renderToReadableStream(element);
8380
+ return streamResponse(stream, {
8381
+ ctx,
8382
+ base,
8383
+ isDev: process.env.NODE_ENV !== "production",
8384
+ compiledTailwindCss: ctx.compiledTailwindCss,
8385
+ status: 404
8432
8386
  });
8433
8387
  };
8434
8388
  }
8435
8389
 
8436
8390
  // error-boundary.ts
8437
- import { createElement as createElement2 } from "react";
8438
- import { TextEncoder as TextEncoder3 } from "node:util";
8391
+ import { createElement as createElement3 } from "react";
8439
8392
  function errorBoundary(errorPath) {
8440
8393
  return async (req, ctx, next) => {
8441
8394
  try {
@@ -8445,30 +8398,38 @@ function errorBoundary(errorPath) {
8445
8398
  const ErrorComponent = mod.default;
8446
8399
  if (!ErrorComponent) throw err;
8447
8400
  const layouts = (ctx.layoutStack || []).map((l) => l.component);
8448
- const stream = await import("react-dom/server").then((m) => m.renderToReadableStream(
8449
- createElement2(ErrorComponent, {
8450
- error: err instanceof Error ? err : new Error(String(err)),
8451
- reset: () => {
8452
- }
8453
- })
8454
- ));
8455
- const reader = stream.getReader();
8456
- const chunks = [];
8457
- while (true) {
8458
- const { done, value } = await reader.read();
8459
- if (done) break;
8460
- chunks.push(value);
8461
- }
8462
- const encoder2 = new TextEncoder3();
8463
- const body = chunks.reduce((acc, c) => {
8464
- const merged = new Uint8Array(acc.length + c.length);
8465
- merged.set(acc);
8466
- merged.set(c, acc.length);
8467
- return merged;
8468
- }, new Uint8Array(0));
8469
- return new Response(body, {
8470
- status: 500,
8471
- headers: { "content-type": "text/html; charset=utf-8" }
8401
+ const base = (ctx.mountPath || "").replace(/\/$/, "");
8402
+ let element = createElement3(ErrorComponent, {
8403
+ error: err instanceof Error ? err : new Error(String(err)),
8404
+ reset: () => {
8405
+ }
8406
+ });
8407
+ if (layouts.length === 0) {
8408
+ element = createElement3(
8409
+ "html",
8410
+ { lang: "en" },
8411
+ createElement3(
8412
+ "head",
8413
+ null,
8414
+ createElement3("meta", { charSet: "utf-8" }),
8415
+ createElement3("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }),
8416
+ createElement3("title", null, "500")
8417
+ ),
8418
+ createElement3("body", null, element)
8419
+ );
8420
+ } else {
8421
+ for (const L of layouts.toReversed()) {
8422
+ element = createElement3(L, { children: element });
8423
+ }
8424
+ }
8425
+ const { renderToReadableStream } = await import("react-dom/server");
8426
+ const stream = await renderToReadableStream(element);
8427
+ return streamResponse(stream, {
8428
+ ctx,
8429
+ base,
8430
+ isDev: process.env.NODE_ENV !== "production",
8431
+ compiledTailwindCss: ctx.compiledTailwindCss,
8432
+ status: 500
8472
8433
  });
8473
8434
  }
8474
8435
  };
@@ -8505,7 +8466,6 @@ export {
8505
8466
  helmet,
8506
8467
  iii,
8507
8468
  layout,
8508
- liveReload,
8509
8469
  loadEnv,
8510
8470
  logdb,
8511
8471
  logger,
@@ -8521,6 +8481,7 @@ export {
8521
8481
  redis,
8522
8482
  registerWorker,
8523
8483
  requestId,
8484
+ rootLayout,
8524
8485
  runWorkflow,
8525
8486
  seo,
8526
8487
  seoMiddleware,
@@ -8532,7 +8493,6 @@ export {
8532
8493
  ssr,
8533
8494
  streamObject,
8534
8495
  streamText,
8535
- tailwind,
8536
8496
  tenant,
8537
8497
  tool2 as tool,
8538
8498
  upload,