weifuwu 0.19.2 → 0.19.5
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/compile.d.ts +2 -0
- package/dist/deploy/types.d.ts +5 -14
- package/dist/index.d.ts +1 -2
- package/dist/index.js +340 -374
- package/dist/live.d.ts +1 -3
- package/dist/root-layout.d.ts +4 -0
- package/package.json +1 -1
- package/cli/template/.weifuwu/ssr/96f5704e.js +0 -481
- package/cli/template/.weifuwu/ssr/fae6ecbe.js +0 -14
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";
|
|
@@ -5248,6 +5138,9 @@ async function compileTsxDev(path2) {
|
|
|
5248
5138
|
cache.set(absPath, mod);
|
|
5249
5139
|
return mod;
|
|
5250
5140
|
}
|
|
5141
|
+
function compile(path2) {
|
|
5142
|
+
return process.env.NODE_ENV !== "production" ? compileTsxDev(path2) : compileTsx(path2);
|
|
5143
|
+
}
|
|
5251
5144
|
var vendorBundle = null;
|
|
5252
5145
|
async function compileVendorBundle() {
|
|
5253
5146
|
if (vendorBundle) return vendorBundle;
|
|
@@ -5322,13 +5215,14 @@ function buildHeadPayload(opts) {
|
|
|
5322
5215
|
const { ctx, base, compiledTailwindCss, isDev: isDev2 } = opts;
|
|
5323
5216
|
let result = "";
|
|
5324
5217
|
if (isDev2) {
|
|
5218
|
+
const vUrl = `${base}/__wfw/v/bundle`;
|
|
5325
5219
|
result += `<script type="importmap">{
|
|
5326
5220
|
"imports": {
|
|
5327
|
-
"react": "
|
|
5328
|
-
"react-dom": "
|
|
5329
|
-
"react-dom/client": "
|
|
5330
|
-
"react/jsx-runtime": "
|
|
5331
|
-
"weifuwu/react": "
|
|
5221
|
+
"react": "${vUrl}",
|
|
5222
|
+
"react-dom": "${vUrl}",
|
|
5223
|
+
"react-dom/client": "${vUrl}",
|
|
5224
|
+
"react/jsx-runtime": "${vUrl}",
|
|
5225
|
+
"weifuwu/react": "${vUrl}"
|
|
5332
5226
|
}
|
|
5333
5227
|
}</script>
|
|
5334
5228
|
`;
|
|
@@ -5338,7 +5232,8 @@ function buildHeadPayload(opts) {
|
|
|
5338
5232
|
`;
|
|
5339
5233
|
}
|
|
5340
5234
|
if (compiledTailwindCss) {
|
|
5341
|
-
|
|
5235
|
+
const cssUrl = ctx.tailwindCssUrl || "/__wfw/style.css";
|
|
5236
|
+
result += `<link rel="stylesheet" href="${cssUrl}" />
|
|
5342
5237
|
`;
|
|
5343
5238
|
}
|
|
5344
5239
|
const localeData = ctx.parsed?.__localeData ?? globalThis.__LOCALE_DATA__;
|
|
@@ -5419,9 +5314,11 @@ function streamResponse(reactStream, opts) {
|
|
|
5419
5314
|
const body = buildBodyScripts(opts);
|
|
5420
5315
|
if (body) controller.enqueue(encoder2.encode("\n" + body));
|
|
5421
5316
|
if (opts.isDev) {
|
|
5317
|
+
const wsUrl = `${opts.base}/__weifuwu/livereload`;
|
|
5318
|
+
const hbUrl = `${opts.base}/__wfw/h/`;
|
|
5422
5319
|
controller.enqueue(encoder2.encode(
|
|
5423
5320
|
`
|
|
5424
|
-
<script>(function(){var ws=new WebSocket((location.protocol==='https:'?'wss:':'ws:')+'//'+location.host+'
|
|
5321
|
+
<script>(function(){var ws=new WebSocket((location.protocol==='https:'?'wss:':'ws:')+'//'+location.host+'${wsUrl}');var t=0;ws.onmessage=function(e){try{var m=JSON.parse(e.data);if(m.type==='component'){if(m.entry&&m.entry!==window.__WFW_ENTRY)return;import('${hbUrl}'+m.hash+'?'+Date.now()).catch(function(){location.reload()});if(m.css){var s=document.querySelector('style[data-lr]')||function(){var x=document.createElement('style');x.setAttribute('data-lr','');document.head.appendChild(x);return x}();s.textContent=m.css}return}if(m.type==='css'){var s=document.querySelector('style[data-lr]')||function(){var x=document.createElement('style');x.setAttribute('data-lr','');document.head.appendChild(x);return x}();s.textContent=m.css;return}}catch(_){}if(e.data==='reload'&&Date.now()-t>1e3){t=Date.now();location.reload()}};ws.onclose=function(){if(Date.now()-t>1e3){t=Date.now();setTimeout(function(){location.reload()},500)}}})()</script>`
|
|
5425
5322
|
));
|
|
5426
5323
|
}
|
|
5427
5324
|
} catch {
|
|
@@ -5503,7 +5400,7 @@ async function buildClientBundle(entryPath, layoutPaths) {
|
|
|
5503
5400
|
jsxImportSource: "react",
|
|
5504
5401
|
banner: { js: "self.process={env:{}};" },
|
|
5505
5402
|
loader: { ".node": "empty" },
|
|
5506
|
-
external: isDev ? ["react", "react-dom", "react-dom/client", "react/jsx-runtime", "weifuwu/react"] : void 0,
|
|
5403
|
+
external: isDev ? ["react", "react-dom", "react-dom/client", "react/jsx-runtime", "weifuwu", "weifuwu/react"] : void 0,
|
|
5507
5404
|
write: false,
|
|
5508
5405
|
minify: !isDev
|
|
5509
5406
|
});
|
|
@@ -5524,7 +5421,7 @@ function ssr(path2) {
|
|
|
5524
5421
|
}) : new Response("", { status: 404 });
|
|
5525
5422
|
});
|
|
5526
5423
|
r.get("/", async (req, ctx) => {
|
|
5527
|
-
const pageMod = await
|
|
5424
|
+
const pageMod = await compile(path2);
|
|
5528
5425
|
const Component = pageMod.default;
|
|
5529
5426
|
if (!Component) return new Response("", { status: 500 });
|
|
5530
5427
|
const layouts = ctx.layoutStack || [];
|
|
@@ -5597,7 +5494,12 @@ function ssr(path2) {
|
|
|
5597
5494
|
return r;
|
|
5598
5495
|
}
|
|
5599
5496
|
|
|
5497
|
+
// root-layout.ts
|
|
5498
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
5499
|
+
import { join as join5, resolve as resolve7 } from "node:path";
|
|
5500
|
+
|
|
5600
5501
|
// tailwind.ts
|
|
5502
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
5601
5503
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "node:fs";
|
|
5602
5504
|
import { join as join3, relative, resolve as resolve5 } from "node:path";
|
|
5603
5505
|
var extraSources = /* @__PURE__ */ new Set();
|
|
@@ -5608,16 +5510,21 @@ function tailwind(dir) {
|
|
|
5608
5510
|
const r = new Router();
|
|
5609
5511
|
r.use(async (req, ctx, next) => {
|
|
5610
5512
|
if (!cssCache.has(cssPath)) {
|
|
5611
|
-
|
|
5513
|
+
await compileTailwindCss(cssPath, cssDir);
|
|
5612
5514
|
}
|
|
5613
|
-
|
|
5515
|
+
const entry = cssCache.get(cssPath);
|
|
5516
|
+
ctx.compiledTailwindCss = entry.css;
|
|
5517
|
+
const base = (ctx.mountPath || "").replace(/\/$/, "");
|
|
5518
|
+
ctx.tailwindCssUrl = base ? `${base}/__wfw/style/${entry.hash}.css` : `/__wfw/style/${entry.hash}.css`;
|
|
5614
5519
|
return next(req, ctx);
|
|
5615
5520
|
});
|
|
5616
|
-
r.get("/__wfw/style.css", async (req, ctx) => {
|
|
5521
|
+
r.get("/__wfw/style/:hash.css", async (req, ctx) => {
|
|
5617
5522
|
if (!cssCache.has(cssPath)) {
|
|
5618
|
-
|
|
5523
|
+
await compileTailwindCss(cssPath, cssDir);
|
|
5619
5524
|
}
|
|
5620
|
-
|
|
5525
|
+
const entry = cssCache.get(cssPath);
|
|
5526
|
+
if (!entry) return new Response("", { status: 404 });
|
|
5527
|
+
return new Response(entry.css, {
|
|
5621
5528
|
headers: { "content-type": "text/css; charset=utf-8" }
|
|
5622
5529
|
});
|
|
5623
5530
|
});
|
|
@@ -5640,7 +5547,8 @@ ${src}`;
|
|
|
5640
5547
|
${src}`;
|
|
5641
5548
|
}
|
|
5642
5549
|
const result = await postcss([tailwindPlugin()]).process(src, { from: cssPath });
|
|
5643
|
-
|
|
5550
|
+
const hash = createHash4("md5").update(result.css).digest("hex").slice(0, 8);
|
|
5551
|
+
cssCache.set(cssPath, { css: result.css, hash });
|
|
5644
5552
|
return result.css;
|
|
5645
5553
|
} catch (err) {
|
|
5646
5554
|
console.warn("Tailwind CSS processing failed:", err.message);
|
|
@@ -5648,20 +5556,144 @@ ${src}`;
|
|
|
5648
5556
|
}
|
|
5649
5557
|
}
|
|
5650
5558
|
|
|
5651
|
-
//
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5559
|
+
// live.ts
|
|
5560
|
+
import chokidar from "chokidar";
|
|
5561
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
5562
|
+
import { join as join4, resolve as resolve6 } from "node:path";
|
|
5563
|
+
var clients = /* @__PURE__ */ new Set();
|
|
5564
|
+
var hotBundleCache = /* @__PURE__ */ new Map();
|
|
5565
|
+
var hotKeys = [];
|
|
5566
|
+
var MAX_HOT = 10;
|
|
5567
|
+
function setHot(hash, code) {
|
|
5568
|
+
if (!hotBundleCache.has(hash)) {
|
|
5569
|
+
hotKeys.push(hash);
|
|
5570
|
+
if (hotKeys.length > MAX_HOT) {
|
|
5571
|
+
const old = hotKeys.shift();
|
|
5572
|
+
hotBundleCache.delete(old);
|
|
5573
|
+
}
|
|
5574
|
+
}
|
|
5575
|
+
hotBundleCache.set(hash, code);
|
|
5576
|
+
}
|
|
5577
|
+
function broadcastReload() {
|
|
5578
|
+
for (const ws of clients) {
|
|
5579
|
+
try {
|
|
5580
|
+
ws.send("reload");
|
|
5581
|
+
} catch {
|
|
5582
|
+
clients.delete(ws);
|
|
5583
|
+
}
|
|
5584
|
+
}
|
|
5585
|
+
}
|
|
5586
|
+
function broadcastCss(css) {
|
|
5587
|
+
const msg = JSON.stringify({ type: "css", css });
|
|
5588
|
+
for (const ws of clients) {
|
|
5589
|
+
try {
|
|
5590
|
+
ws.send(msg);
|
|
5591
|
+
} catch {
|
|
5592
|
+
clients.delete(ws);
|
|
5593
|
+
}
|
|
5594
|
+
}
|
|
5595
|
+
}
|
|
5596
|
+
function liveReload(dir) {
|
|
5597
|
+
const r = new Router();
|
|
5598
|
+
const resolved = resolve6(dir);
|
|
5599
|
+
const entryPath = join4(resolved, "page.tsx");
|
|
5600
|
+
r.get("/__wfw/v/bundle", async (req, ctx) => {
|
|
5601
|
+
const code = await compileVendorBundle();
|
|
5602
|
+
return new Response(code, {
|
|
5603
|
+
headers: { "content-type": "application/javascript; charset=utf-8" }
|
|
5604
|
+
});
|
|
5605
|
+
});
|
|
5606
|
+
r.get("/__wfw/h/:hash", async (req, ctx) => {
|
|
5607
|
+
const hash = ctx.params.hash.replace(/\.js$/i, "");
|
|
5608
|
+
const code = hotBundleCache.get(hash);
|
|
5609
|
+
if (!code) return new Response("", { status: 404 });
|
|
5610
|
+
return new Response(code, {
|
|
5611
|
+
headers: { "content-type": "application/javascript; charset=utf-8" }
|
|
5612
|
+
});
|
|
5613
|
+
});
|
|
5614
|
+
r.ws("/__weifuwu/livereload", {
|
|
5615
|
+
open(ws) {
|
|
5616
|
+
clients.add(ws);
|
|
5617
|
+
ws.on("close", () => clients.delete(ws));
|
|
5618
|
+
ws.on("error", () => clients.delete(ws));
|
|
5619
|
+
}
|
|
5620
|
+
});
|
|
5621
|
+
const watcher = chokidar.watch(dir, {
|
|
5622
|
+
ignored: /(^|[/\\])\.|node_modules|[/\\]\.weifuwu[/\\]/,
|
|
5623
|
+
ignoreInitial: true
|
|
5624
|
+
});
|
|
5625
|
+
watcher.on("change", async (filePath) => {
|
|
5626
|
+
if (/\.tsx?$/i.test(filePath)) {
|
|
5627
|
+
if (filePath.endsWith("layout.tsx")) {
|
|
5628
|
+
return broadcastReload();
|
|
5629
|
+
}
|
|
5630
|
+
clearCompileCache();
|
|
5631
|
+
markClientBundleDirty();
|
|
5632
|
+
try {
|
|
5633
|
+
const target = existsSync4(entryPath) ? entryPath : filePath;
|
|
5634
|
+
await compileTsxDev(target);
|
|
5635
|
+
const { hash, code } = await compileHotComponent(target);
|
|
5636
|
+
setHot(hash, code);
|
|
5637
|
+
let css;
|
|
5638
|
+
const cssPath = join4(resolved, "app.css");
|
|
5639
|
+
if (existsSync4(cssPath)) {
|
|
5640
|
+
css = await compileTailwindCss(cssPath, resolved);
|
|
5641
|
+
}
|
|
5642
|
+
const entry = id(entryPath);
|
|
5643
|
+
const msg = { type: "component", hash, entry };
|
|
5644
|
+
if (css) msg.css = css;
|
|
5645
|
+
const str = JSON.stringify(msg);
|
|
5646
|
+
for (const ws of clients) {
|
|
5647
|
+
try {
|
|
5648
|
+
ws.send(str);
|
|
5649
|
+
} catch {
|
|
5650
|
+
clients.delete(ws);
|
|
5651
|
+
}
|
|
5652
|
+
}
|
|
5653
|
+
} catch (e) {
|
|
5654
|
+
console.error("live reload failed, fallback to full reload:", e);
|
|
5655
|
+
broadcastReload();
|
|
5656
|
+
}
|
|
5657
|
+
} else if (/\.css$/i.test(filePath)) {
|
|
5658
|
+
const cssPath = join4(resolved, "app.css");
|
|
5659
|
+
if (existsSync4(cssPath)) {
|
|
5660
|
+
const css = await compileTailwindCss(cssPath, resolved);
|
|
5661
|
+
if (css) broadcastCss(css);
|
|
5662
|
+
}
|
|
5663
|
+
}
|
|
5664
|
+
});
|
|
5665
|
+
r.close = () => {
|
|
5666
|
+
watcher.close();
|
|
5667
|
+
clients.clear();
|
|
5659
5668
|
};
|
|
5669
|
+
return r;
|
|
5670
|
+
}
|
|
5671
|
+
|
|
5672
|
+
// root-layout.ts
|
|
5673
|
+
function rootLayout(dir) {
|
|
5674
|
+
const r = new Router();
|
|
5675
|
+
const resolved = resolve7(dir);
|
|
5676
|
+
const isDev2 = process.env.NODE_ENV !== "production";
|
|
5677
|
+
const layoutPath = join5(resolved, "layout.tsx");
|
|
5678
|
+
r.use(async (req, ctx, next) => {
|
|
5679
|
+
const mod = await compile(layoutPath);
|
|
5680
|
+
if (mod?.default) ctx.layoutStack = [{ path: layoutPath, component: mod.default }];
|
|
5681
|
+
return next(req, ctx);
|
|
5682
|
+
});
|
|
5683
|
+
if (existsSync5(join5(resolved, "app.css"))) {
|
|
5684
|
+
r.use(tailwind(resolved));
|
|
5685
|
+
}
|
|
5686
|
+
if (isDev2) {
|
|
5687
|
+
const lr = liveReload(resolved);
|
|
5688
|
+
r.use(lr);
|
|
5689
|
+
r.close = lr.close;
|
|
5690
|
+
}
|
|
5691
|
+
return r;
|
|
5660
5692
|
}
|
|
5661
5693
|
|
|
5662
5694
|
// opencode/session.ts
|
|
5663
5695
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
5664
|
-
import { join as
|
|
5696
|
+
import { join as join6 } from "node:path";
|
|
5665
5697
|
import { mkdir as mkdir2 } from "node:fs/promises";
|
|
5666
5698
|
var sessions = pgTable("_opencode_sessions", {
|
|
5667
5699
|
id: uuid("id"),
|
|
@@ -5698,7 +5730,7 @@ async function createSession(sql2, opts, cwd, mountPath) {
|
|
|
5698
5730
|
}
|
|
5699
5731
|
function computeSessionWorkspace(cwd, mountPath, sessionId) {
|
|
5700
5732
|
const name = !mountPath || mountPath === "/" ? "default" : mountPath.replace(/^\//, "");
|
|
5701
|
-
return
|
|
5733
|
+
return join6(cwd, ".sessions", name, sessionId);
|
|
5702
5734
|
}
|
|
5703
5735
|
async function getSession(sql2, id3) {
|
|
5704
5736
|
const { data: rows } = await sessions.readMany(sql2, { id: id3, active: true });
|
|
@@ -5895,10 +5927,10 @@ function createBashTool(ctx) {
|
|
|
5895
5927
|
return { stdout: "", stderr: "Command denied: potentially dangerous command", exitCode: 1 };
|
|
5896
5928
|
}
|
|
5897
5929
|
const cwd = workdir ? `${ctx.workspace}/${workdir}` : ctx.workspace;
|
|
5898
|
-
return new Promise((
|
|
5930
|
+
return new Promise((resolve15) => {
|
|
5899
5931
|
const child = exec(command, { cwd, timeout: timeout * 1e3, maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => {
|
|
5900
5932
|
const truncated = stdout.length > 1e6 || stderr.length > 1e6;
|
|
5901
|
-
|
|
5933
|
+
resolve15({
|
|
5902
5934
|
stdout: stdout.slice(0, 1e6),
|
|
5903
5935
|
stderr: stderr.slice(0, 1e6),
|
|
5904
5936
|
exitCode: error?.code ?? 0,
|
|
@@ -5915,7 +5947,7 @@ function createBashTool(ctx) {
|
|
|
5915
5947
|
import { tool as tool4 } from "ai";
|
|
5916
5948
|
import { z as z6 } from "zod";
|
|
5917
5949
|
import { readFileSync as readFileSync4 } from "node:fs";
|
|
5918
|
-
import { resolve as
|
|
5950
|
+
import { resolve as resolve8 } from "node:path";
|
|
5919
5951
|
function createReadTool(ctx) {
|
|
5920
5952
|
return tool4({
|
|
5921
5953
|
description: "Read file contents. Supports offset and limit for reading specific line ranges.",
|
|
@@ -5925,7 +5957,7 @@ function createReadTool(ctx) {
|
|
|
5925
5957
|
limit: z6.number().optional().describe("Number of lines to read")
|
|
5926
5958
|
}),
|
|
5927
5959
|
execute: async ({ path: path2, offset, limit }) => {
|
|
5928
|
-
const resolved =
|
|
5960
|
+
const resolved = resolve8(ctx.workspace, path2);
|
|
5929
5961
|
if (!isPathAllowed(resolved, ctx.workspace, ctx.permissions)) {
|
|
5930
5962
|
return { error: "Path not allowed", content: null, totalLines: 0 };
|
|
5931
5963
|
}
|
|
@@ -5957,7 +5989,7 @@ function createReadTool(ctx) {
|
|
|
5957
5989
|
import { tool as tool5 } from "ai";
|
|
5958
5990
|
import { z as z7 } from "zod";
|
|
5959
5991
|
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync3 } from "node:fs";
|
|
5960
|
-
import { resolve as
|
|
5992
|
+
import { resolve as resolve9, dirname as dirname3 } from "node:path";
|
|
5961
5993
|
function createWriteTool(ctx) {
|
|
5962
5994
|
return tool5({
|
|
5963
5995
|
description: "Create or overwrite a file. Parent directories are created automatically.",
|
|
@@ -5966,7 +5998,7 @@ function createWriteTool(ctx) {
|
|
|
5966
5998
|
content: z7.string().describe("File content")
|
|
5967
5999
|
}),
|
|
5968
6000
|
execute: async ({ path: path2, content }) => {
|
|
5969
|
-
const resolved =
|
|
6001
|
+
const resolved = resolve9(ctx.workspace, path2);
|
|
5970
6002
|
if (!isPathAllowed(resolved, ctx.workspace, ctx.permissions)) {
|
|
5971
6003
|
return { error: "Path not allowed" };
|
|
5972
6004
|
}
|
|
@@ -5981,7 +6013,7 @@ function createWriteTool(ctx) {
|
|
|
5981
6013
|
import { tool as tool6 } from "ai";
|
|
5982
6014
|
import { z as z8 } from "zod";
|
|
5983
6015
|
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
5984
|
-
import { resolve as
|
|
6016
|
+
import { resolve as resolve10 } from "node:path";
|
|
5985
6017
|
function createEditTool(ctx) {
|
|
5986
6018
|
return tool6({
|
|
5987
6019
|
description: "Perform exact string replacements in a file. If oldString appears multiple times, provide more surrounding context.",
|
|
@@ -5992,7 +6024,7 @@ function createEditTool(ctx) {
|
|
|
5992
6024
|
replaceAll: z8.boolean().default(false).describe("Replace all occurrences")
|
|
5993
6025
|
}),
|
|
5994
6026
|
execute: async ({ path: path2, oldString, newString, replaceAll }) => {
|
|
5995
|
-
const resolved =
|
|
6027
|
+
const resolved = resolve10(ctx.workspace, path2);
|
|
5996
6028
|
if (!isPathAllowed(resolved, ctx.workspace, ctx.permissions)) {
|
|
5997
6029
|
return { error: "Path not allowed" };
|
|
5998
6030
|
}
|
|
@@ -6025,8 +6057,8 @@ function createEditTool(ctx) {
|
|
|
6025
6057
|
import { tool as tool7 } from "ai";
|
|
6026
6058
|
import { z as z9 } from "zod";
|
|
6027
6059
|
import { execFileSync } from "node:child_process";
|
|
6028
|
-
import { resolve as
|
|
6029
|
-
import { existsSync as
|
|
6060
|
+
import { resolve as resolve11 } from "node:path";
|
|
6061
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
6030
6062
|
function createGrepTool(ctx) {
|
|
6031
6063
|
return tool7({
|
|
6032
6064
|
description: "Search file contents using regex. Supports file type filtering and context lines.",
|
|
@@ -6037,10 +6069,10 @@ function createGrepTool(ctx) {
|
|
|
6037
6069
|
context: z9.number().default(0).describe("Number of context lines before and after each match")
|
|
6038
6070
|
}),
|
|
6039
6071
|
execute: async ({ pattern, include, path: path2, context }) => {
|
|
6040
|
-
const searchDir = path2 ?
|
|
6072
|
+
const searchDir = path2 ? resolve11(ctx.workspace, path2) : ctx.workspace;
|
|
6041
6073
|
try {
|
|
6042
6074
|
let stdout;
|
|
6043
|
-
if (
|
|
6075
|
+
if (existsSync6("/usr/bin/rg") || existsSync6("/usr/local/bin/rg")) {
|
|
6044
6076
|
const args = ["-n"];
|
|
6045
6077
|
if (context > 0) args.push("-C", String(context));
|
|
6046
6078
|
if (include) args.push("-g", include);
|
|
@@ -6069,7 +6101,7 @@ function createGrepTool(ctx) {
|
|
|
6069
6101
|
import { tool as tool8 } from "ai";
|
|
6070
6102
|
import { z as z10 } from "zod";
|
|
6071
6103
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
6072
|
-
import { resolve as
|
|
6104
|
+
import { resolve as resolve12 } from "node:path";
|
|
6073
6105
|
function createGlobTool(ctx) {
|
|
6074
6106
|
return tool8({
|
|
6075
6107
|
description: "Find files matching a glob pattern.",
|
|
@@ -6078,7 +6110,7 @@ function createGlobTool(ctx) {
|
|
|
6078
6110
|
path: z10.string().optional().describe("Subdirectory relative to workspace")
|
|
6079
6111
|
}),
|
|
6080
6112
|
execute: async ({ pattern, path: path2 }) => {
|
|
6081
|
-
const searchDir = path2 ?
|
|
6113
|
+
const searchDir = path2 ? resolve12(ctx.workspace, path2) : ctx.workspace;
|
|
6082
6114
|
try {
|
|
6083
6115
|
const stdout = execFileSync2("find", [
|
|
6084
6116
|
searchDir,
|
|
@@ -6139,7 +6171,7 @@ function createQuestionTool(ctx) {
|
|
|
6139
6171
|
options: z12.array(z12.string()).optional().describe("Optional multiple choice options")
|
|
6140
6172
|
}),
|
|
6141
6173
|
execute: async ({ question, options }, { toolCallId }) => {
|
|
6142
|
-
return new Promise((
|
|
6174
|
+
return new Promise((resolve15, reject) => {
|
|
6143
6175
|
const timeout = setTimeout(() => {
|
|
6144
6176
|
ctx.pendingQuestions.delete(toolCallId);
|
|
6145
6177
|
reject(new Error("Question timed out"));
|
|
@@ -6147,7 +6179,7 @@ function createQuestionTool(ctx) {
|
|
|
6147
6179
|
ctx.pendingQuestions.set(toolCallId, {
|
|
6148
6180
|
resolve: (answer) => {
|
|
6149
6181
|
clearTimeout(timeout);
|
|
6150
|
-
|
|
6182
|
+
resolve15(answer);
|
|
6151
6183
|
},
|
|
6152
6184
|
reject: (err) => {
|
|
6153
6185
|
clearTimeout(timeout);
|
|
@@ -6294,9 +6326,8 @@ async function buildRouter4(deps) {
|
|
|
6294
6326
|
});
|
|
6295
6327
|
try {
|
|
6296
6328
|
const uiDir = new URL("../opencode/ui/", import.meta.url).pathname;
|
|
6297
|
-
router.use(
|
|
6298
|
-
router.
|
|
6299
|
-
router.get("/", ssr(join5(uiDir, "page.tsx")));
|
|
6329
|
+
router.use(rootLayout(uiDir));
|
|
6330
|
+
router.get("/", ssr(join7(uiDir, "page.tsx")));
|
|
6300
6331
|
} catch (e) {
|
|
6301
6332
|
console.warn("[opencode] UI not available:", e);
|
|
6302
6333
|
}
|
|
@@ -6304,17 +6335,17 @@ async function buildRouter4(deps) {
|
|
|
6304
6335
|
}
|
|
6305
6336
|
|
|
6306
6337
|
// opencode/ws.ts
|
|
6307
|
-
var
|
|
6338
|
+
var clients2 = /* @__PURE__ */ new WeakMap();
|
|
6308
6339
|
function createWSHandler2(deps) {
|
|
6309
6340
|
const { sql: sql2, model, workspace, systemPrompt, skills, skillsRegistry, permissions, pendingQuestions } = deps;
|
|
6310
6341
|
return {
|
|
6311
6342
|
open(ws, ctx) {
|
|
6312
6343
|
const userId = ctx.user?.id ?? 0;
|
|
6313
6344
|
const mountPath = ctx.mountPath ?? "";
|
|
6314
|
-
|
|
6345
|
+
clients2.set(ws, { userId, mountPath });
|
|
6315
6346
|
},
|
|
6316
6347
|
async message(ws, ctx, data) {
|
|
6317
|
-
const client =
|
|
6348
|
+
const client = clients2.get(ws);
|
|
6318
6349
|
if (!client) return;
|
|
6319
6350
|
let msg;
|
|
6320
6351
|
try {
|
|
@@ -6411,17 +6442,17 @@ function createWSHandler2(deps) {
|
|
|
6411
6442
|
}
|
|
6412
6443
|
},
|
|
6413
6444
|
close(ws) {
|
|
6414
|
-
const client =
|
|
6445
|
+
const client = clients2.get(ws);
|
|
6415
6446
|
if (client) {
|
|
6416
6447
|
client.abortController?.abort();
|
|
6417
|
-
|
|
6448
|
+
clients2.delete(ws);
|
|
6418
6449
|
}
|
|
6419
6450
|
},
|
|
6420
6451
|
error(ws, _ctx, _err) {
|
|
6421
|
-
const client =
|
|
6452
|
+
const client = clients2.get(ws);
|
|
6422
6453
|
if (client) {
|
|
6423
6454
|
client.abortController?.abort();
|
|
6424
|
-
|
|
6455
|
+
clients2.delete(ws);
|
|
6425
6456
|
}
|
|
6426
6457
|
}
|
|
6427
6458
|
};
|
|
@@ -6431,7 +6462,7 @@ function createWSHandler2(deps) {
|
|
|
6431
6462
|
import { readFile } from "node:fs/promises";
|
|
6432
6463
|
import { glob } from "node:fs/promises";
|
|
6433
6464
|
import { homedir } from "node:os";
|
|
6434
|
-
import { resolve as
|
|
6465
|
+
import { resolve as resolve13 } from "node:path";
|
|
6435
6466
|
import { parse as parseYaml } from "yaml";
|
|
6436
6467
|
var SEARCH_DIRS = [
|
|
6437
6468
|
(ws) => `${ws}/.opencode/skills`,
|
|
@@ -6469,7 +6500,7 @@ async function scanDir(dir) {
|
|
|
6469
6500
|
try {
|
|
6470
6501
|
const files = [];
|
|
6471
6502
|
for await (const entry of glob("*/SKILL.md", { cwd: dir })) {
|
|
6472
|
-
const skill = await parseSkillFile(
|
|
6503
|
+
const skill = await parseSkillFile(resolve13(dir, entry));
|
|
6473
6504
|
if (skill) files.push(skill);
|
|
6474
6505
|
}
|
|
6475
6506
|
return files;
|
|
@@ -6804,8 +6835,8 @@ function analytics(options) {
|
|
|
6804
6835
|
|
|
6805
6836
|
// preferences.ts
|
|
6806
6837
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
6807
|
-
import { existsSync as
|
|
6808
|
-
import { join as
|
|
6838
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
6839
|
+
import { join as join8, resolve as resolve14 } from "node:path";
|
|
6809
6840
|
var defaults = {
|
|
6810
6841
|
locale: { default: "en", cookie: "locale", fromAcceptLanguage: true },
|
|
6811
6842
|
theme: { default: "system", cookie: "theme" }
|
|
@@ -6854,7 +6885,7 @@ async function handlePrefSwitch(req, value, cookieName, load) {
|
|
|
6854
6885
|
});
|
|
6855
6886
|
}
|
|
6856
6887
|
function preferences(options) {
|
|
6857
|
-
const dir = options.dir ?
|
|
6888
|
+
const dir = options.dir ? resolve14(options.dir) : void 0;
|
|
6858
6889
|
const localeOpts = { ...defaults.locale, ...options.locale };
|
|
6859
6890
|
const themeOpts = { ...defaults.theme, ...options.theme };
|
|
6860
6891
|
const cache2 = /* @__PURE__ */ new Map();
|
|
@@ -6866,8 +6897,8 @@ function preferences(options) {
|
|
|
6866
6897
|
if (!validLocale(locale)) return {};
|
|
6867
6898
|
const cached = cache2.get(locale);
|
|
6868
6899
|
if (cached) return cached;
|
|
6869
|
-
const filePath =
|
|
6870
|
-
if (
|
|
6900
|
+
const filePath = join8(dir, `${locale}.json`);
|
|
6901
|
+
if (existsSync7(filePath)) {
|
|
6871
6902
|
try {
|
|
6872
6903
|
const content = await readFile2(filePath, "utf-8");
|
|
6873
6904
|
const data = JSON.parse(content);
|
|
@@ -7903,12 +7934,12 @@ function iii(opts = {}) {
|
|
|
7903
7934
|
const handler = async (payload) => {
|
|
7904
7935
|
if (!worker.ws) throw new Error(`Worker "${worker.name}" disconnected`);
|
|
7905
7936
|
const invocationId = crypto6.randomUUID();
|
|
7906
|
-
return new Promise((
|
|
7937
|
+
return new Promise((resolve15, reject) => {
|
|
7907
7938
|
const timer = setTimeout(() => {
|
|
7908
7939
|
pending.delete(invocationId);
|
|
7909
7940
|
reject(new Error(`Invocation timed out for "${id3}"`));
|
|
7910
7941
|
}, 3e4);
|
|
7911
|
-
pending.set(invocationId, { resolve:
|
|
7942
|
+
pending.set(invocationId, { resolve: resolve15, reject, timer });
|
|
7912
7943
|
worker.ws.send(JSON.stringify({
|
|
7913
7944
|
type: "invoke",
|
|
7914
7945
|
invocation_id: invocationId,
|
|
@@ -8149,8 +8180,8 @@ function registerWorker(url) {
|
|
|
8149
8180
|
function connect() {
|
|
8150
8181
|
if (intentionalClose) return;
|
|
8151
8182
|
ws = new WebSocket(url);
|
|
8152
|
-
ready = new Promise((
|
|
8153
|
-
resolveReady =
|
|
8183
|
+
ready = new Promise((resolve15) => {
|
|
8184
|
+
resolveReady = resolve15;
|
|
8154
8185
|
});
|
|
8155
8186
|
ws.onopen = () => {
|
|
8156
8187
|
reconnectAttempt = 0;
|
|
@@ -8270,13 +8301,13 @@ function registerWorker(url) {
|
|
|
8270
8301
|
}
|
|
8271
8302
|
return Promise.resolve(fn(request.payload, ctx));
|
|
8272
8303
|
}
|
|
8273
|
-
return new Promise((
|
|
8304
|
+
return new Promise((resolve15, reject) => {
|
|
8274
8305
|
const invocationId = genId();
|
|
8275
8306
|
const timer = setTimeout(() => {
|
|
8276
8307
|
pendingInvocations.delete(invocationId);
|
|
8277
8308
|
reject(new Error(`Invocation timed out for "${request.function_id}"`));
|
|
8278
8309
|
}, request.timeout_ms || 3e4);
|
|
8279
|
-
pendingInvocations.set(invocationId, { resolve:
|
|
8310
|
+
pendingInvocations.set(invocationId, { resolve: resolve15, reject, timer });
|
|
8280
8311
|
send({
|
|
8281
8312
|
type: "invoke",
|
|
8282
8313
|
invocation_id: invocationId,
|
|
@@ -8297,175 +8328,111 @@ function registerWorker(url) {
|
|
|
8297
8328
|
};
|
|
8298
8329
|
}
|
|
8299
8330
|
|
|
8300
|
-
//
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
|
|
8308
|
-
function setHot(hash, code) {
|
|
8309
|
-
if (!hotBundleCache.has(hash)) {
|
|
8310
|
-
hotKeys.push(hash);
|
|
8311
|
-
if (hotKeys.length > MAX_HOT) {
|
|
8312
|
-
const old = hotKeys.shift();
|
|
8313
|
-
hotBundleCache.delete(old);
|
|
8314
|
-
}
|
|
8315
|
-
}
|
|
8316
|
-
hotBundleCache.set(hash, code);
|
|
8317
|
-
}
|
|
8318
|
-
function broadcastReload() {
|
|
8319
|
-
for (const ws of clients2) {
|
|
8320
|
-
try {
|
|
8321
|
-
ws.send("reload");
|
|
8322
|
-
} catch {
|
|
8323
|
-
clients2.delete(ws);
|
|
8324
|
-
}
|
|
8325
|
-
}
|
|
8326
|
-
}
|
|
8327
|
-
function broadcastCss(css) {
|
|
8328
|
-
const msg = JSON.stringify({ type: "css", css });
|
|
8329
|
-
for (const ws of clients2) {
|
|
8330
|
-
try {
|
|
8331
|
-
ws.send(msg);
|
|
8332
|
-
} catch {
|
|
8333
|
-
clients2.delete(ws);
|
|
8334
|
-
}
|
|
8335
|
-
}
|
|
8336
|
-
}
|
|
8337
|
-
function liveReload(opts) {
|
|
8338
|
-
const r = new Router();
|
|
8339
|
-
const entryPath = (() => {
|
|
8340
|
-
for (const dir of opts.dirs) {
|
|
8341
|
-
const p = join7(resolve13(dir), "page.tsx");
|
|
8342
|
-
if (existsSync6(p)) return p;
|
|
8343
|
-
}
|
|
8344
|
-
return "";
|
|
8345
|
-
})();
|
|
8346
|
-
r.get("/__wfw/v/bundle", async (req, ctx) => {
|
|
8347
|
-
const code = await compileVendorBundle();
|
|
8348
|
-
return new Response(code, {
|
|
8349
|
-
headers: { "content-type": "application/javascript; charset=utf-8" }
|
|
8350
|
-
});
|
|
8351
|
-
});
|
|
8352
|
-
r.get("/__wfw/h/:hash", async (req, ctx) => {
|
|
8353
|
-
const hash = ctx.params.hash.replace(/\.js$/i, "");
|
|
8354
|
-
const code = hotBundleCache.get(hash);
|
|
8355
|
-
if (!code) return new Response("", { status: 404 });
|
|
8356
|
-
return new Response(code, {
|
|
8357
|
-
headers: { "content-type": "application/javascript; charset=utf-8" }
|
|
8358
|
-
});
|
|
8359
|
-
});
|
|
8360
|
-
r.ws("/__weifuwu/livereload", {
|
|
8361
|
-
open(ws) {
|
|
8362
|
-
clients2.add(ws);
|
|
8363
|
-
ws.on("close", () => clients2.delete(ws));
|
|
8364
|
-
ws.on("error", () => clients2.delete(ws));
|
|
8365
|
-
}
|
|
8366
|
-
});
|
|
8367
|
-
const watcher = chokidar.watch(opts.dirs, {
|
|
8368
|
-
ignored: /(^|[/\\])\.|node_modules|[/\\]\.weifuwu[/\\]/,
|
|
8369
|
-
ignoreInitial: true
|
|
8370
|
-
});
|
|
8371
|
-
watcher.on("change", async (filePath) => {
|
|
8372
|
-
if (/\.tsx?$/i.test(filePath)) {
|
|
8373
|
-
clearCompileCache();
|
|
8374
|
-
markClientBundleDirty();
|
|
8375
|
-
try {
|
|
8376
|
-
const target = entryPath || filePath;
|
|
8377
|
-
await compileTsxDev(target);
|
|
8378
|
-
const { hash, code } = await compileHotComponent(target);
|
|
8379
|
-
setHot(hash, code);
|
|
8380
|
-
let css;
|
|
8381
|
-
for (const dir of opts.dirs) {
|
|
8382
|
-
const cssPath = join7(resolve13(dir), "app.css");
|
|
8383
|
-
if (existsSync6(cssPath)) {
|
|
8384
|
-
css = await compileTailwindCss(cssPath, resolve13(dir));
|
|
8385
|
-
}
|
|
8386
|
-
}
|
|
8387
|
-
const entry = entryPath ? id(entryPath) : "";
|
|
8388
|
-
const msg = { type: "component", hash, entry };
|
|
8389
|
-
if (css) msg.css = css;
|
|
8390
|
-
const str = JSON.stringify(msg);
|
|
8391
|
-
for (const ws of clients2) {
|
|
8392
|
-
try {
|
|
8393
|
-
ws.send(str);
|
|
8394
|
-
} catch {
|
|
8395
|
-
clients2.delete(ws);
|
|
8396
|
-
}
|
|
8397
|
-
}
|
|
8398
|
-
} catch (e) {
|
|
8399
|
-
console.error("live reload failed, fallback to full reload:", e);
|
|
8400
|
-
broadcastReload();
|
|
8401
|
-
}
|
|
8402
|
-
} else if (/\.css$/i.test(filePath)) {
|
|
8403
|
-
for (const dir of opts.dirs) {
|
|
8404
|
-
const cssPath = join7(resolve13(dir), "app.css");
|
|
8405
|
-
if (existsSync6(cssPath)) {
|
|
8406
|
-
const css = await compileTailwindCss(cssPath, resolve13(dir));
|
|
8407
|
-
if (css) broadcastCss(css);
|
|
8408
|
-
}
|
|
8409
|
-
}
|
|
8410
|
-
}
|
|
8411
|
-
});
|
|
8412
|
-
r.close = () => {
|
|
8413
|
-
watcher.close();
|
|
8414
|
-
clients2.clear();
|
|
8331
|
+
// layout.ts
|
|
8332
|
+
function layout(path2) {
|
|
8333
|
+
return async (req, ctx, next) => {
|
|
8334
|
+
const mod = await compile(path2);
|
|
8335
|
+
const Component = mod.default;
|
|
8336
|
+
if (!Component) throw new Error(`Layout ${path2} has no default export`);
|
|
8337
|
+
ctx.layoutStack = [...ctx.layoutStack || [], { path: path2, component: Component }];
|
|
8338
|
+
return next(req, ctx);
|
|
8415
8339
|
};
|
|
8416
|
-
return r;
|
|
8417
8340
|
}
|
|
8418
8341
|
|
|
8419
8342
|
// not-found.ts
|
|
8343
|
+
import { createElement as createElement2 } from "react";
|
|
8420
8344
|
function notFound(path2) {
|
|
8421
8345
|
return async (req, ctx) => {
|
|
8422
8346
|
if (!path2) return new Response("Not Found", { status: 404 });
|
|
8423
|
-
|
|
8424
|
-
|
|
8425
|
-
|
|
8426
|
-
|
|
8427
|
-
|
|
8428
|
-
|
|
8347
|
+
let Component;
|
|
8348
|
+
try {
|
|
8349
|
+
const mod = await compile(path2);
|
|
8350
|
+
Component = mod?.default;
|
|
8351
|
+
} catch {
|
|
8352
|
+
return new Response("Not Found", { status: 404 });
|
|
8353
|
+
}
|
|
8354
|
+
if (!Component) return new Response("Not Found", { status: 404 });
|
|
8355
|
+
const layouts = ctx.layoutStack || [];
|
|
8356
|
+
const layoutComponents = layouts.map((l) => l.component);
|
|
8357
|
+
const base = (ctx.mountPath || "").replace(/\/$/, "");
|
|
8358
|
+
let element = createElement2(
|
|
8359
|
+
"div",
|
|
8360
|
+
{ id: "__weifuwu_root" },
|
|
8361
|
+
createElement2(Component, null)
|
|
8362
|
+
);
|
|
8363
|
+
if (layoutComponents.length === 0) {
|
|
8364
|
+
element = createElement2(
|
|
8365
|
+
"html",
|
|
8366
|
+
{ lang: "en" },
|
|
8367
|
+
createElement2(
|
|
8368
|
+
"head",
|
|
8369
|
+
null,
|
|
8370
|
+
createElement2("meta", { charSet: "utf-8" }),
|
|
8371
|
+
createElement2("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }),
|
|
8372
|
+
createElement2("title", null, "404")
|
|
8373
|
+
),
|
|
8374
|
+
createElement2("body", null, element)
|
|
8375
|
+
);
|
|
8376
|
+
} else {
|
|
8377
|
+
for (const L of layoutComponents.toReversed()) {
|
|
8378
|
+
element = createElement2(L, { children: element });
|
|
8379
|
+
}
|
|
8380
|
+
}
|
|
8381
|
+
const { renderToReadableStream } = await import("react-dom/server");
|
|
8382
|
+
const stream = await renderToReadableStream(element);
|
|
8383
|
+
return streamResponse(stream, {
|
|
8384
|
+
ctx,
|
|
8385
|
+
base,
|
|
8386
|
+
isDev: process.env.NODE_ENV !== "production",
|
|
8387
|
+
compiledTailwindCss: ctx.compiledTailwindCss,
|
|
8388
|
+
status: 404
|
|
8429
8389
|
});
|
|
8430
8390
|
};
|
|
8431
8391
|
}
|
|
8432
8392
|
|
|
8433
8393
|
// error-boundary.ts
|
|
8434
|
-
import { createElement as
|
|
8435
|
-
import { TextEncoder as TextEncoder3 } from "node:util";
|
|
8394
|
+
import { createElement as createElement3 } from "react";
|
|
8436
8395
|
function errorBoundary(errorPath) {
|
|
8437
8396
|
return async (req, ctx, next) => {
|
|
8438
8397
|
try {
|
|
8439
8398
|
return await next(req, ctx);
|
|
8440
8399
|
} catch (err) {
|
|
8441
|
-
const mod = await
|
|
8400
|
+
const mod = await compile(errorPath);
|
|
8442
8401
|
const ErrorComponent = mod.default;
|
|
8443
8402
|
if (!ErrorComponent) throw err;
|
|
8444
8403
|
const layouts = (ctx.layoutStack || []).map((l) => l.component);
|
|
8445
|
-
const
|
|
8446
|
-
|
|
8447
|
-
|
|
8448
|
-
|
|
8449
|
-
|
|
8450
|
-
|
|
8451
|
-
)
|
|
8452
|
-
|
|
8453
|
-
|
|
8454
|
-
|
|
8455
|
-
|
|
8456
|
-
|
|
8457
|
-
|
|
8458
|
-
|
|
8459
|
-
|
|
8460
|
-
|
|
8461
|
-
|
|
8462
|
-
|
|
8463
|
-
|
|
8464
|
-
|
|
8465
|
-
|
|
8466
|
-
|
|
8467
|
-
|
|
8468
|
-
|
|
8404
|
+
const base = (ctx.mountPath || "").replace(/\/$/, "");
|
|
8405
|
+
let element = createElement3(ErrorComponent, {
|
|
8406
|
+
error: err instanceof Error ? err : new Error(String(err)),
|
|
8407
|
+
reset: () => {
|
|
8408
|
+
}
|
|
8409
|
+
});
|
|
8410
|
+
if (layouts.length === 0) {
|
|
8411
|
+
element = createElement3(
|
|
8412
|
+
"html",
|
|
8413
|
+
{ lang: "en" },
|
|
8414
|
+
createElement3(
|
|
8415
|
+
"head",
|
|
8416
|
+
null,
|
|
8417
|
+
createElement3("meta", { charSet: "utf-8" }),
|
|
8418
|
+
createElement3("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }),
|
|
8419
|
+
createElement3("title", null, "500")
|
|
8420
|
+
),
|
|
8421
|
+
createElement3("body", null, element)
|
|
8422
|
+
);
|
|
8423
|
+
} else {
|
|
8424
|
+
for (const L of layouts.toReversed()) {
|
|
8425
|
+
element = createElement3(L, { children: element });
|
|
8426
|
+
}
|
|
8427
|
+
}
|
|
8428
|
+
const { renderToReadableStream } = await import("react-dom/server");
|
|
8429
|
+
const stream = await renderToReadableStream(element);
|
|
8430
|
+
return streamResponse(stream, {
|
|
8431
|
+
ctx,
|
|
8432
|
+
base,
|
|
8433
|
+
isDev: process.env.NODE_ENV !== "production",
|
|
8434
|
+
compiledTailwindCss: ctx.compiledTailwindCss,
|
|
8435
|
+
status: 500
|
|
8469
8436
|
});
|
|
8470
8437
|
}
|
|
8471
8438
|
};
|
|
@@ -8502,7 +8469,6 @@ export {
|
|
|
8502
8469
|
helmet,
|
|
8503
8470
|
iii,
|
|
8504
8471
|
layout,
|
|
8505
|
-
liveReload,
|
|
8506
8472
|
loadEnv,
|
|
8507
8473
|
logdb,
|
|
8508
8474
|
logger,
|
|
@@ -8518,6 +8484,7 @@ export {
|
|
|
8518
8484
|
redis,
|
|
8519
8485
|
registerWorker,
|
|
8520
8486
|
requestId,
|
|
8487
|
+
rootLayout,
|
|
8521
8488
|
runWorkflow,
|
|
8522
8489
|
seo,
|
|
8523
8490
|
seoMiddleware,
|
|
@@ -8529,7 +8496,6 @@ export {
|
|
|
8529
8496
|
ssr,
|
|
8530
8497
|
streamObject,
|
|
8531
8498
|
streamText,
|
|
8532
|
-
tailwind,
|
|
8533
8499
|
tenant,
|
|
8534
8500
|
tool2 as tool,
|
|
8535
8501
|
upload,
|