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/README.md +351 -174
- package/cli/template/app.ts +2 -3
- package/cli/template/index.ts +1 -5
- package/cli.ts +1 -2
- package/dist/cli.js +1 -1
- package/dist/deploy/types.d.ts +5 -14
- package/dist/index.d.ts +1 -2
- package/dist/index.js +326 -366
- package/dist/live.d.ts +1 -3
- package/dist/root-layout.d.ts +4 -0
- package/package.json +1 -1
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((
|
|
379
|
+
return await new Promise((resolve15) => {
|
|
380
380
|
try {
|
|
381
381
|
upgradeSocket(router.wss, req, socket, head, match.handler, ctx2);
|
|
382
|
-
|
|
382
|
+
resolve15(new Response(null, { status: 101 }));
|
|
383
383
|
} catch {
|
|
384
384
|
socket.destroy();
|
|
385
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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((
|
|
4700
|
+
return new Promise((resolve15) => {
|
|
4700
4701
|
const timer = setTimeout(() => {
|
|
4701
4702
|
mp.child.kill("SIGKILL");
|
|
4702
|
-
|
|
4703
|
+
resolve15();
|
|
4703
4704
|
}, timeout);
|
|
4704
4705
|
mp.child.on("exit", () => {
|
|
4705
4706
|
clearTimeout(timer);
|
|
4706
|
-
|
|
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
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
if (!
|
|
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
|
-
|
|
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(
|
|
4893
|
-
else onLog(
|
|
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.
|
|
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
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
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: {
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
5510
|
+
await compileTailwindCss(cssPath, cssDir);
|
|
5615
5511
|
}
|
|
5616
|
-
|
|
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
|
-
|
|
5520
|
+
await compileTailwindCss(cssPath, cssDir);
|
|
5622
5521
|
}
|
|
5623
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
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
|
|
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
|
|
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((
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
6032
|
-
import { existsSync as
|
|
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 ?
|
|
6069
|
+
const searchDir = path2 ? resolve11(ctx.workspace, path2) : ctx.workspace;
|
|
6044
6070
|
try {
|
|
6045
6071
|
let stdout;
|
|
6046
|
-
if (
|
|
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
|
|
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 ?
|
|
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((
|
|
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
|
-
|
|
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(
|
|
6301
|
-
router.
|
|
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
|
|
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
|
-
|
|
6342
|
+
clients2.set(ws, { userId, mountPath });
|
|
6318
6343
|
},
|
|
6319
6344
|
async message(ws, ctx, data) {
|
|
6320
|
-
const client =
|
|
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 =
|
|
6442
|
+
const client = clients2.get(ws);
|
|
6418
6443
|
if (client) {
|
|
6419
6444
|
client.abortController?.abort();
|
|
6420
|
-
|
|
6445
|
+
clients2.delete(ws);
|
|
6421
6446
|
}
|
|
6422
6447
|
},
|
|
6423
6448
|
error(ws, _ctx, _err) {
|
|
6424
|
-
const client =
|
|
6449
|
+
const client = clients2.get(ws);
|
|
6425
6450
|
if (client) {
|
|
6426
6451
|
client.abortController?.abort();
|
|
6427
|
-
|
|
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
|
|
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(
|
|
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
|
|
6811
|
-
import { join as
|
|
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 ?
|
|
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 =
|
|
6873
|
-
if (
|
|
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((
|
|
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:
|
|
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((
|
|
8156
|
-
resolveReady =
|
|
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((
|
|
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:
|
|
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
|
-
//
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
|
|
8308
|
-
|
|
8309
|
-
|
|
8310
|
-
|
|
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
|
-
|
|
8427
|
-
|
|
8428
|
-
|
|
8429
|
-
|
|
8430
|
-
|
|
8431
|
-
|
|
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
|
|
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
|
|
8449
|
-
|
|
8450
|
-
|
|
8451
|
-
|
|
8452
|
-
|
|
8453
|
-
|
|
8454
|
-
)
|
|
8455
|
-
|
|
8456
|
-
|
|
8457
|
-
|
|
8458
|
-
|
|
8459
|
-
|
|
8460
|
-
|
|
8461
|
-
|
|
8462
|
-
|
|
8463
|
-
|
|
8464
|
-
|
|
8465
|
-
|
|
8466
|
-
|
|
8467
|
-
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
|
|
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,
|