weifuwu 0.19.4 → 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/deploy/types.d.ts +5 -14
- package/dist/index.d.ts +1 -2
- package/dist/index.js +335 -372
- 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";
|
|
@@ -5325,13 +5215,14 @@ function buildHeadPayload(opts) {
|
|
|
5325
5215
|
const { ctx, base, compiledTailwindCss, isDev: isDev2 } = opts;
|
|
5326
5216
|
let result = "";
|
|
5327
5217
|
if (isDev2) {
|
|
5218
|
+
const vUrl = `${base}/__wfw/v/bundle`;
|
|
5328
5219
|
result += `<script type="importmap">{
|
|
5329
5220
|
"imports": {
|
|
5330
|
-
"react": "
|
|
5331
|
-
"react-dom": "
|
|
5332
|
-
"react-dom/client": "
|
|
5333
|
-
"react/jsx-runtime": "
|
|
5334
|
-
"weifuwu/react": "
|
|
5221
|
+
"react": "${vUrl}",
|
|
5222
|
+
"react-dom": "${vUrl}",
|
|
5223
|
+
"react-dom/client": "${vUrl}",
|
|
5224
|
+
"react/jsx-runtime": "${vUrl}",
|
|
5225
|
+
"weifuwu/react": "${vUrl}"
|
|
5335
5226
|
}
|
|
5336
5227
|
}</script>
|
|
5337
5228
|
`;
|
|
@@ -5341,7 +5232,8 @@ function buildHeadPayload(opts) {
|
|
|
5341
5232
|
`;
|
|
5342
5233
|
}
|
|
5343
5234
|
if (compiledTailwindCss) {
|
|
5344
|
-
|
|
5235
|
+
const cssUrl = ctx.tailwindCssUrl || "/__wfw/style.css";
|
|
5236
|
+
result += `<link rel="stylesheet" href="${cssUrl}" />
|
|
5345
5237
|
`;
|
|
5346
5238
|
}
|
|
5347
5239
|
const localeData = ctx.parsed?.__localeData ?? globalThis.__LOCALE_DATA__;
|
|
@@ -5422,9 +5314,11 @@ function streamResponse(reactStream, opts) {
|
|
|
5422
5314
|
const body = buildBodyScripts(opts);
|
|
5423
5315
|
if (body) controller.enqueue(encoder2.encode("\n" + body));
|
|
5424
5316
|
if (opts.isDev) {
|
|
5317
|
+
const wsUrl = `${opts.base}/__weifuwu/livereload`;
|
|
5318
|
+
const hbUrl = `${opts.base}/__wfw/h/`;
|
|
5425
5319
|
controller.enqueue(encoder2.encode(
|
|
5426
5320
|
`
|
|
5427
|
-
<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>`
|
|
5428
5322
|
));
|
|
5429
5323
|
}
|
|
5430
5324
|
} catch {
|
|
@@ -5506,7 +5400,7 @@ async function buildClientBundle(entryPath, layoutPaths) {
|
|
|
5506
5400
|
jsxImportSource: "react",
|
|
5507
5401
|
banner: { js: "self.process={env:{}};" },
|
|
5508
5402
|
loader: { ".node": "empty" },
|
|
5509
|
-
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,
|
|
5510
5404
|
write: false,
|
|
5511
5405
|
minify: !isDev
|
|
5512
5406
|
});
|
|
@@ -5600,7 +5494,12 @@ function ssr(path2) {
|
|
|
5600
5494
|
return r;
|
|
5601
5495
|
}
|
|
5602
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
|
+
|
|
5603
5501
|
// tailwind.ts
|
|
5502
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
5604
5503
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "node:fs";
|
|
5605
5504
|
import { join as join3, relative, resolve as resolve5 } from "node:path";
|
|
5606
5505
|
var extraSources = /* @__PURE__ */ new Set();
|
|
@@ -5611,16 +5510,21 @@ function tailwind(dir) {
|
|
|
5611
5510
|
const r = new Router();
|
|
5612
5511
|
r.use(async (req, ctx, next) => {
|
|
5613
5512
|
if (!cssCache.has(cssPath)) {
|
|
5614
|
-
|
|
5513
|
+
await compileTailwindCss(cssPath, cssDir);
|
|
5615
5514
|
}
|
|
5616
|
-
|
|
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`;
|
|
5617
5519
|
return next(req, ctx);
|
|
5618
5520
|
});
|
|
5619
|
-
r.get("/__wfw/style.css", async (req, ctx) => {
|
|
5521
|
+
r.get("/__wfw/style/:hash.css", async (req, ctx) => {
|
|
5620
5522
|
if (!cssCache.has(cssPath)) {
|
|
5621
|
-
|
|
5523
|
+
await compileTailwindCss(cssPath, cssDir);
|
|
5622
5524
|
}
|
|
5623
|
-
|
|
5525
|
+
const entry = cssCache.get(cssPath);
|
|
5526
|
+
if (!entry) return new Response("", { status: 404 });
|
|
5527
|
+
return new Response(entry.css, {
|
|
5624
5528
|
headers: { "content-type": "text/css; charset=utf-8" }
|
|
5625
5529
|
});
|
|
5626
5530
|
});
|
|
@@ -5643,7 +5547,8 @@ ${src}`;
|
|
|
5643
5547
|
${src}`;
|
|
5644
5548
|
}
|
|
5645
5549
|
const result = await postcss([tailwindPlugin()]).process(src, { from: cssPath });
|
|
5646
|
-
|
|
5550
|
+
const hash = createHash4("md5").update(result.css).digest("hex").slice(0, 8);
|
|
5551
|
+
cssCache.set(cssPath, { css: result.css, hash });
|
|
5647
5552
|
return result.css;
|
|
5648
5553
|
} catch (err) {
|
|
5649
5554
|
console.warn("Tailwind CSS processing failed:", err.message);
|
|
@@ -5651,20 +5556,144 @@ ${src}`;
|
|
|
5651
5556
|
}
|
|
5652
5557
|
}
|
|
5653
5558
|
|
|
5654
|
-
//
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
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();
|
|
5662
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;
|
|
5663
5692
|
}
|
|
5664
5693
|
|
|
5665
5694
|
// opencode/session.ts
|
|
5666
5695
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
5667
|
-
import { join as
|
|
5696
|
+
import { join as join6 } from "node:path";
|
|
5668
5697
|
import { mkdir as mkdir2 } from "node:fs/promises";
|
|
5669
5698
|
var sessions = pgTable("_opencode_sessions", {
|
|
5670
5699
|
id: uuid("id"),
|
|
@@ -5701,7 +5730,7 @@ async function createSession(sql2, opts, cwd, mountPath) {
|
|
|
5701
5730
|
}
|
|
5702
5731
|
function computeSessionWorkspace(cwd, mountPath, sessionId) {
|
|
5703
5732
|
const name = !mountPath || mountPath === "/" ? "default" : mountPath.replace(/^\//, "");
|
|
5704
|
-
return
|
|
5733
|
+
return join6(cwd, ".sessions", name, sessionId);
|
|
5705
5734
|
}
|
|
5706
5735
|
async function getSession(sql2, id3) {
|
|
5707
5736
|
const { data: rows } = await sessions.readMany(sql2, { id: id3, active: true });
|
|
@@ -5898,10 +5927,10 @@ function createBashTool(ctx) {
|
|
|
5898
5927
|
return { stdout: "", stderr: "Command denied: potentially dangerous command", exitCode: 1 };
|
|
5899
5928
|
}
|
|
5900
5929
|
const cwd = workdir ? `${ctx.workspace}/${workdir}` : ctx.workspace;
|
|
5901
|
-
return new Promise((
|
|
5930
|
+
return new Promise((resolve15) => {
|
|
5902
5931
|
const child = exec(command, { cwd, timeout: timeout * 1e3, maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => {
|
|
5903
5932
|
const truncated = stdout.length > 1e6 || stderr.length > 1e6;
|
|
5904
|
-
|
|
5933
|
+
resolve15({
|
|
5905
5934
|
stdout: stdout.slice(0, 1e6),
|
|
5906
5935
|
stderr: stderr.slice(0, 1e6),
|
|
5907
5936
|
exitCode: error?.code ?? 0,
|
|
@@ -5918,7 +5947,7 @@ function createBashTool(ctx) {
|
|
|
5918
5947
|
import { tool as tool4 } from "ai";
|
|
5919
5948
|
import { z as z6 } from "zod";
|
|
5920
5949
|
import { readFileSync as readFileSync4 } from "node:fs";
|
|
5921
|
-
import { resolve as
|
|
5950
|
+
import { resolve as resolve8 } from "node:path";
|
|
5922
5951
|
function createReadTool(ctx) {
|
|
5923
5952
|
return tool4({
|
|
5924
5953
|
description: "Read file contents. Supports offset and limit for reading specific line ranges.",
|
|
@@ -5928,7 +5957,7 @@ function createReadTool(ctx) {
|
|
|
5928
5957
|
limit: z6.number().optional().describe("Number of lines to read")
|
|
5929
5958
|
}),
|
|
5930
5959
|
execute: async ({ path: path2, offset, limit }) => {
|
|
5931
|
-
const resolved =
|
|
5960
|
+
const resolved = resolve8(ctx.workspace, path2);
|
|
5932
5961
|
if (!isPathAllowed(resolved, ctx.workspace, ctx.permissions)) {
|
|
5933
5962
|
return { error: "Path not allowed", content: null, totalLines: 0 };
|
|
5934
5963
|
}
|
|
@@ -5960,7 +5989,7 @@ function createReadTool(ctx) {
|
|
|
5960
5989
|
import { tool as tool5 } from "ai";
|
|
5961
5990
|
import { z as z7 } from "zod";
|
|
5962
5991
|
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync3 } from "node:fs";
|
|
5963
|
-
import { resolve as
|
|
5992
|
+
import { resolve as resolve9, dirname as dirname3 } from "node:path";
|
|
5964
5993
|
function createWriteTool(ctx) {
|
|
5965
5994
|
return tool5({
|
|
5966
5995
|
description: "Create or overwrite a file. Parent directories are created automatically.",
|
|
@@ -5969,7 +5998,7 @@ function createWriteTool(ctx) {
|
|
|
5969
5998
|
content: z7.string().describe("File content")
|
|
5970
5999
|
}),
|
|
5971
6000
|
execute: async ({ path: path2, content }) => {
|
|
5972
|
-
const resolved =
|
|
6001
|
+
const resolved = resolve9(ctx.workspace, path2);
|
|
5973
6002
|
if (!isPathAllowed(resolved, ctx.workspace, ctx.permissions)) {
|
|
5974
6003
|
return { error: "Path not allowed" };
|
|
5975
6004
|
}
|
|
@@ -5984,7 +6013,7 @@ function createWriteTool(ctx) {
|
|
|
5984
6013
|
import { tool as tool6 } from "ai";
|
|
5985
6014
|
import { z as z8 } from "zod";
|
|
5986
6015
|
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
5987
|
-
import { resolve as
|
|
6016
|
+
import { resolve as resolve10 } from "node:path";
|
|
5988
6017
|
function createEditTool(ctx) {
|
|
5989
6018
|
return tool6({
|
|
5990
6019
|
description: "Perform exact string replacements in a file. If oldString appears multiple times, provide more surrounding context.",
|
|
@@ -5995,7 +6024,7 @@ function createEditTool(ctx) {
|
|
|
5995
6024
|
replaceAll: z8.boolean().default(false).describe("Replace all occurrences")
|
|
5996
6025
|
}),
|
|
5997
6026
|
execute: async ({ path: path2, oldString, newString, replaceAll }) => {
|
|
5998
|
-
const resolved =
|
|
6027
|
+
const resolved = resolve10(ctx.workspace, path2);
|
|
5999
6028
|
if (!isPathAllowed(resolved, ctx.workspace, ctx.permissions)) {
|
|
6000
6029
|
return { error: "Path not allowed" };
|
|
6001
6030
|
}
|
|
@@ -6028,8 +6057,8 @@ function createEditTool(ctx) {
|
|
|
6028
6057
|
import { tool as tool7 } from "ai";
|
|
6029
6058
|
import { z as z9 } from "zod";
|
|
6030
6059
|
import { execFileSync } from "node:child_process";
|
|
6031
|
-
import { resolve as
|
|
6032
|
-
import { existsSync as
|
|
6060
|
+
import { resolve as resolve11 } from "node:path";
|
|
6061
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
6033
6062
|
function createGrepTool(ctx) {
|
|
6034
6063
|
return tool7({
|
|
6035
6064
|
description: "Search file contents using regex. Supports file type filtering and context lines.",
|
|
@@ -6040,10 +6069,10 @@ function createGrepTool(ctx) {
|
|
|
6040
6069
|
context: z9.number().default(0).describe("Number of context lines before and after each match")
|
|
6041
6070
|
}),
|
|
6042
6071
|
execute: async ({ pattern, include, path: path2, context }) => {
|
|
6043
|
-
const searchDir = path2 ?
|
|
6072
|
+
const searchDir = path2 ? resolve11(ctx.workspace, path2) : ctx.workspace;
|
|
6044
6073
|
try {
|
|
6045
6074
|
let stdout;
|
|
6046
|
-
if (
|
|
6075
|
+
if (existsSync6("/usr/bin/rg") || existsSync6("/usr/local/bin/rg")) {
|
|
6047
6076
|
const args = ["-n"];
|
|
6048
6077
|
if (context > 0) args.push("-C", String(context));
|
|
6049
6078
|
if (include) args.push("-g", include);
|
|
@@ -6072,7 +6101,7 @@ function createGrepTool(ctx) {
|
|
|
6072
6101
|
import { tool as tool8 } from "ai";
|
|
6073
6102
|
import { z as z10 } from "zod";
|
|
6074
6103
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
6075
|
-
import { resolve as
|
|
6104
|
+
import { resolve as resolve12 } from "node:path";
|
|
6076
6105
|
function createGlobTool(ctx) {
|
|
6077
6106
|
return tool8({
|
|
6078
6107
|
description: "Find files matching a glob pattern.",
|
|
@@ -6081,7 +6110,7 @@ function createGlobTool(ctx) {
|
|
|
6081
6110
|
path: z10.string().optional().describe("Subdirectory relative to workspace")
|
|
6082
6111
|
}),
|
|
6083
6112
|
execute: async ({ pattern, path: path2 }) => {
|
|
6084
|
-
const searchDir = path2 ?
|
|
6113
|
+
const searchDir = path2 ? resolve12(ctx.workspace, path2) : ctx.workspace;
|
|
6085
6114
|
try {
|
|
6086
6115
|
const stdout = execFileSync2("find", [
|
|
6087
6116
|
searchDir,
|
|
@@ -6142,7 +6171,7 @@ function createQuestionTool(ctx) {
|
|
|
6142
6171
|
options: z12.array(z12.string()).optional().describe("Optional multiple choice options")
|
|
6143
6172
|
}),
|
|
6144
6173
|
execute: async ({ question, options }, { toolCallId }) => {
|
|
6145
|
-
return new Promise((
|
|
6174
|
+
return new Promise((resolve15, reject) => {
|
|
6146
6175
|
const timeout = setTimeout(() => {
|
|
6147
6176
|
ctx.pendingQuestions.delete(toolCallId);
|
|
6148
6177
|
reject(new Error("Question timed out"));
|
|
@@ -6150,7 +6179,7 @@ function createQuestionTool(ctx) {
|
|
|
6150
6179
|
ctx.pendingQuestions.set(toolCallId, {
|
|
6151
6180
|
resolve: (answer) => {
|
|
6152
6181
|
clearTimeout(timeout);
|
|
6153
|
-
|
|
6182
|
+
resolve15(answer);
|
|
6154
6183
|
},
|
|
6155
6184
|
reject: (err) => {
|
|
6156
6185
|
clearTimeout(timeout);
|
|
@@ -6297,9 +6326,8 @@ async function buildRouter4(deps) {
|
|
|
6297
6326
|
});
|
|
6298
6327
|
try {
|
|
6299
6328
|
const uiDir = new URL("../opencode/ui/", import.meta.url).pathname;
|
|
6300
|
-
router.use(
|
|
6301
|
-
router.
|
|
6302
|
-
router.get("/", ssr(join5(uiDir, "page.tsx")));
|
|
6329
|
+
router.use(rootLayout(uiDir));
|
|
6330
|
+
router.get("/", ssr(join7(uiDir, "page.tsx")));
|
|
6303
6331
|
} catch (e) {
|
|
6304
6332
|
console.warn("[opencode] UI not available:", e);
|
|
6305
6333
|
}
|
|
@@ -6307,17 +6335,17 @@ async function buildRouter4(deps) {
|
|
|
6307
6335
|
}
|
|
6308
6336
|
|
|
6309
6337
|
// opencode/ws.ts
|
|
6310
|
-
var
|
|
6338
|
+
var clients2 = /* @__PURE__ */ new WeakMap();
|
|
6311
6339
|
function createWSHandler2(deps) {
|
|
6312
6340
|
const { sql: sql2, model, workspace, systemPrompt, skills, skillsRegistry, permissions, pendingQuestions } = deps;
|
|
6313
6341
|
return {
|
|
6314
6342
|
open(ws, ctx) {
|
|
6315
6343
|
const userId = ctx.user?.id ?? 0;
|
|
6316
6344
|
const mountPath = ctx.mountPath ?? "";
|
|
6317
|
-
|
|
6345
|
+
clients2.set(ws, { userId, mountPath });
|
|
6318
6346
|
},
|
|
6319
6347
|
async message(ws, ctx, data) {
|
|
6320
|
-
const client =
|
|
6348
|
+
const client = clients2.get(ws);
|
|
6321
6349
|
if (!client) return;
|
|
6322
6350
|
let msg;
|
|
6323
6351
|
try {
|
|
@@ -6414,17 +6442,17 @@ function createWSHandler2(deps) {
|
|
|
6414
6442
|
}
|
|
6415
6443
|
},
|
|
6416
6444
|
close(ws) {
|
|
6417
|
-
const client =
|
|
6445
|
+
const client = clients2.get(ws);
|
|
6418
6446
|
if (client) {
|
|
6419
6447
|
client.abortController?.abort();
|
|
6420
|
-
|
|
6448
|
+
clients2.delete(ws);
|
|
6421
6449
|
}
|
|
6422
6450
|
},
|
|
6423
6451
|
error(ws, _ctx, _err) {
|
|
6424
|
-
const client =
|
|
6452
|
+
const client = clients2.get(ws);
|
|
6425
6453
|
if (client) {
|
|
6426
6454
|
client.abortController?.abort();
|
|
6427
|
-
|
|
6455
|
+
clients2.delete(ws);
|
|
6428
6456
|
}
|
|
6429
6457
|
}
|
|
6430
6458
|
};
|
|
@@ -6434,7 +6462,7 @@ function createWSHandler2(deps) {
|
|
|
6434
6462
|
import { readFile } from "node:fs/promises";
|
|
6435
6463
|
import { glob } from "node:fs/promises";
|
|
6436
6464
|
import { homedir } from "node:os";
|
|
6437
|
-
import { resolve as
|
|
6465
|
+
import { resolve as resolve13 } from "node:path";
|
|
6438
6466
|
import { parse as parseYaml } from "yaml";
|
|
6439
6467
|
var SEARCH_DIRS = [
|
|
6440
6468
|
(ws) => `${ws}/.opencode/skills`,
|
|
@@ -6472,7 +6500,7 @@ async function scanDir(dir) {
|
|
|
6472
6500
|
try {
|
|
6473
6501
|
const files = [];
|
|
6474
6502
|
for await (const entry of glob("*/SKILL.md", { cwd: dir })) {
|
|
6475
|
-
const skill = await parseSkillFile(
|
|
6503
|
+
const skill = await parseSkillFile(resolve13(dir, entry));
|
|
6476
6504
|
if (skill) files.push(skill);
|
|
6477
6505
|
}
|
|
6478
6506
|
return files;
|
|
@@ -6807,8 +6835,8 @@ function analytics(options) {
|
|
|
6807
6835
|
|
|
6808
6836
|
// preferences.ts
|
|
6809
6837
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
6810
|
-
import { existsSync as
|
|
6811
|
-
import { join as
|
|
6838
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
6839
|
+
import { join as join8, resolve as resolve14 } from "node:path";
|
|
6812
6840
|
var defaults = {
|
|
6813
6841
|
locale: { default: "en", cookie: "locale", fromAcceptLanguage: true },
|
|
6814
6842
|
theme: { default: "system", cookie: "theme" }
|
|
@@ -6857,7 +6885,7 @@ async function handlePrefSwitch(req, value, cookieName, load) {
|
|
|
6857
6885
|
});
|
|
6858
6886
|
}
|
|
6859
6887
|
function preferences(options) {
|
|
6860
|
-
const dir = options.dir ?
|
|
6888
|
+
const dir = options.dir ? resolve14(options.dir) : void 0;
|
|
6861
6889
|
const localeOpts = { ...defaults.locale, ...options.locale };
|
|
6862
6890
|
const themeOpts = { ...defaults.theme, ...options.theme };
|
|
6863
6891
|
const cache2 = /* @__PURE__ */ new Map();
|
|
@@ -6869,8 +6897,8 @@ function preferences(options) {
|
|
|
6869
6897
|
if (!validLocale(locale)) return {};
|
|
6870
6898
|
const cached = cache2.get(locale);
|
|
6871
6899
|
if (cached) return cached;
|
|
6872
|
-
const filePath =
|
|
6873
|
-
if (
|
|
6900
|
+
const filePath = join8(dir, `${locale}.json`);
|
|
6901
|
+
if (existsSync7(filePath)) {
|
|
6874
6902
|
try {
|
|
6875
6903
|
const content = await readFile2(filePath, "utf-8");
|
|
6876
6904
|
const data = JSON.parse(content);
|
|
@@ -7906,12 +7934,12 @@ function iii(opts = {}) {
|
|
|
7906
7934
|
const handler = async (payload) => {
|
|
7907
7935
|
if (!worker.ws) throw new Error(`Worker "${worker.name}" disconnected`);
|
|
7908
7936
|
const invocationId = crypto6.randomUUID();
|
|
7909
|
-
return new Promise((
|
|
7937
|
+
return new Promise((resolve15, reject) => {
|
|
7910
7938
|
const timer = setTimeout(() => {
|
|
7911
7939
|
pending.delete(invocationId);
|
|
7912
7940
|
reject(new Error(`Invocation timed out for "${id3}"`));
|
|
7913
7941
|
}, 3e4);
|
|
7914
|
-
pending.set(invocationId, { resolve:
|
|
7942
|
+
pending.set(invocationId, { resolve: resolve15, reject, timer });
|
|
7915
7943
|
worker.ws.send(JSON.stringify({
|
|
7916
7944
|
type: "invoke",
|
|
7917
7945
|
invocation_id: invocationId,
|
|
@@ -8152,8 +8180,8 @@ function registerWorker(url) {
|
|
|
8152
8180
|
function connect() {
|
|
8153
8181
|
if (intentionalClose) return;
|
|
8154
8182
|
ws = new WebSocket(url);
|
|
8155
|
-
ready = new Promise((
|
|
8156
|
-
resolveReady =
|
|
8183
|
+
ready = new Promise((resolve15) => {
|
|
8184
|
+
resolveReady = resolve15;
|
|
8157
8185
|
});
|
|
8158
8186
|
ws.onopen = () => {
|
|
8159
8187
|
reconnectAttempt = 0;
|
|
@@ -8273,13 +8301,13 @@ function registerWorker(url) {
|
|
|
8273
8301
|
}
|
|
8274
8302
|
return Promise.resolve(fn(request.payload, ctx));
|
|
8275
8303
|
}
|
|
8276
|
-
return new Promise((
|
|
8304
|
+
return new Promise((resolve15, reject) => {
|
|
8277
8305
|
const invocationId = genId();
|
|
8278
8306
|
const timer = setTimeout(() => {
|
|
8279
8307
|
pendingInvocations.delete(invocationId);
|
|
8280
8308
|
reject(new Error(`Invocation timed out for "${request.function_id}"`));
|
|
8281
8309
|
}, request.timeout_ms || 3e4);
|
|
8282
|
-
pendingInvocations.set(invocationId, { resolve:
|
|
8310
|
+
pendingInvocations.set(invocationId, { resolve: resolve15, reject, timer });
|
|
8283
8311
|
send({
|
|
8284
8312
|
type: "invoke",
|
|
8285
8313
|
invocation_id: invocationId,
|
|
@@ -8300,142 +8328,70 @@ function registerWorker(url) {
|
|
|
8300
8328
|
};
|
|
8301
8329
|
}
|
|
8302
8330
|
|
|
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();
|
|
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);
|
|
8418
8339
|
};
|
|
8419
|
-
return r;
|
|
8420
8340
|
}
|
|
8421
8341
|
|
|
8422
8342
|
// not-found.ts
|
|
8343
|
+
import { createElement as createElement2 } from "react";
|
|
8423
8344
|
function notFound(path2) {
|
|
8424
8345
|
return async (req, ctx) => {
|
|
8425
8346
|
if (!path2) return new Response("Not Found", { status: 404 });
|
|
8426
|
-
|
|
8427
|
-
|
|
8428
|
-
|
|
8429
|
-
|
|
8430
|
-
|
|
8431
|
-
|
|
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
|
|
8432
8389
|
});
|
|
8433
8390
|
};
|
|
8434
8391
|
}
|
|
8435
8392
|
|
|
8436
8393
|
// error-boundary.ts
|
|
8437
|
-
import { createElement as
|
|
8438
|
-
import { TextEncoder as TextEncoder3 } from "node:util";
|
|
8394
|
+
import { createElement as createElement3 } from "react";
|
|
8439
8395
|
function errorBoundary(errorPath) {
|
|
8440
8396
|
return async (req, ctx, next) => {
|
|
8441
8397
|
try {
|
|
@@ -8445,30 +8401,38 @@ function errorBoundary(errorPath) {
|
|
|
8445
8401
|
const ErrorComponent = mod.default;
|
|
8446
8402
|
if (!ErrorComponent) throw err;
|
|
8447
8403
|
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
|
-
|
|
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
|
|
8472
8436
|
});
|
|
8473
8437
|
}
|
|
8474
8438
|
};
|
|
@@ -8505,7 +8469,6 @@ export {
|
|
|
8505
8469
|
helmet,
|
|
8506
8470
|
iii,
|
|
8507
8471
|
layout,
|
|
8508
|
-
liveReload,
|
|
8509
8472
|
loadEnv,
|
|
8510
8473
|
logdb,
|
|
8511
8474
|
logger,
|
|
@@ -8521,6 +8484,7 @@ export {
|
|
|
8521
8484
|
redis,
|
|
8522
8485
|
registerWorker,
|
|
8523
8486
|
requestId,
|
|
8487
|
+
rootLayout,
|
|
8524
8488
|
runWorkflow,
|
|
8525
8489
|
seo,
|
|
8526
8490
|
seoMiddleware,
|
|
@@ -8532,7 +8496,6 @@ export {
|
|
|
8532
8496
|
ssr,
|
|
8533
8497
|
streamObject,
|
|
8534
8498
|
streamText,
|
|
8535
|
-
tailwind,
|
|
8536
8499
|
tenant,
|
|
8537
8500
|
tool2 as tool,
|
|
8538
8501
|
upload,
|