@artenx/dshb 0.0.5 → 0.1.1
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 +18 -0
- package/cordis.patch.yml +9 -9
- package/lib/auth/credentials.d.ts +30 -0
- package/lib/auth/credentials.js +121 -0
- package/lib/auth/credentials.js.map +1 -0
- package/lib/auth/decorate-webserver.d.ts +7 -0
- package/lib/auth/decorate-webserver.js +75 -0
- package/lib/auth/decorate-webserver.js.map +1 -0
- package/lib/auth/gate.d.ts +14 -0
- package/lib/auth/gate.js +45 -0
- package/lib/auth/gate.js.map +1 -0
- package/lib/auth/index.d.ts +8 -0
- package/lib/auth/index.js +51 -0
- package/lib/auth/index.js.map +1 -0
- package/lib/auth/login-page.d.ts +5 -0
- package/lib/auth/login-page.js +87 -0
- package/lib/auth/login-page.js.map +1 -0
- package/lib/auth/loopback-compat.d.ts +6 -0
- package/lib/auth/loopback-compat.js +94 -0
- package/lib/auth/loopback-compat.js.map +1 -0
- package/lib/auth/loopback.d.ts +7 -0
- package/lib/auth/loopback.js +24 -0
- package/lib/auth/loopback.js.map +1 -0
- package/lib/auth/ratelimit.d.ts +11 -0
- package/lib/auth/ratelimit.js +37 -0
- package/lib/auth/ratelimit.js.map +1 -0
- package/lib/auth/routes.d.ts +8 -0
- package/lib/auth/routes.js +224 -0
- package/lib/auth/routes.js.map +1 -0
- package/lib/auth/session.d.ts +11 -0
- package/lib/auth/session.js +52 -0
- package/lib/auth/session.js.map +1 -0
- package/lib/auth/startup.d.ts +15 -0
- package/lib/auth/startup.js +68 -0
- package/lib/auth/startup.js.map +1 -0
- package/lib/auth/webserver.d.ts +18 -0
- package/lib/auth/webserver.js +60 -0
- package/lib/auth/webserver.js.map +1 -0
- package/lib/client.js +2770 -0
- package/lib/client.js.map +1 -0
- package/lib/connection-sIX25pkS.d.ts +46 -0
- package/lib/core/audit.d.ts +16 -0
- package/lib/core/audit.js +38 -0
- package/lib/core/audit.js.map +1 -0
- package/lib/core/index.d.ts +8 -0
- package/lib/core/index.js +77 -0
- package/lib/core/index.js.map +1 -0
- package/lib/core/known-hosts.d.ts +23 -0
- package/lib/core/known-hosts.js +76 -0
- package/lib/core/known-hosts.js.map +1 -0
- package/lib/core/node-registry.d.ts +108 -0
- package/lib/core/node-registry.js +191 -0
- package/lib/core/node-registry.js.map +1 -0
- package/lib/core/routes.d.ts +9 -0
- package/lib/core/routes.js +404 -0
- package/lib/core/routes.js.map +1 -0
- package/lib/core/ssh-config.d.ts +23 -0
- package/lib/core/ssh-config.js +70 -0
- package/lib/core/ssh-config.js.map +1 -0
- package/lib/core/test.d.ts +43 -0
- package/lib/core/test.js +116 -0
- package/lib/core/test.js.map +1 -0
- package/lib/core/workspace-bindings.d.ts +28 -0
- package/lib/core/workspace-bindings.js +86 -0
- package/lib/core/workspace-bindings.js.map +1 -0
- package/lib/core/workspaces.d.ts +9 -0
- package/lib/core/workspaces.js +283 -0
- package/lib/core/workspaces.js.map +1 -0
- package/lib/exec-docker/daemon-runner.d.ts +21 -0
- package/lib/exec-docker/daemon-runner.js +326 -0
- package/lib/exec-docker/daemon-runner.js.map +1 -0
- package/lib/exec-docker/docker-backend.d.ts +48 -0
- package/lib/exec-docker/docker-backend.js +1 -0
- package/lib/exec-docker/docker-client.d.ts +46 -0
- package/lib/exec-docker/docker-client.js +302 -0
- package/lib/exec-docker/docker-client.js.map +1 -0
- package/lib/exec-docker/docker-routes.d.ts +6 -0
- package/lib/exec-docker/docker-routes.js +215 -0
- package/lib/exec-docker/docker-routes.js.map +1 -0
- package/lib/exec-docker/fs-provider.d.ts +50 -0
- package/lib/exec-docker/fs-provider.js +92 -0
- package/lib/exec-docker/fs-provider.js.map +1 -0
- package/lib/exec-docker/host-runner.d.ts +29 -0
- package/lib/exec-docker/host-runner.js +17 -0
- package/lib/exec-docker/host-runner.js.map +1 -0
- package/lib/exec-docker/index.d.ts +21 -0
- package/lib/exec-docker/index.js +137 -0
- package/lib/exec-docker/index.js.map +1 -0
- package/lib/exec-docker/process-provider.d.ts +20 -0
- package/lib/exec-docker/process-provider.js +75 -0
- package/lib/exec-docker/process-provider.js.map +1 -0
- package/lib/exec-docker/provision.d.ts +24 -0
- package/lib/exec-docker/provision.js +103 -0
- package/lib/exec-docker/provision.js.map +1 -0
- package/lib/exec-docker/remote-docker-cli.d.ts +52 -0
- package/lib/exec-docker/remote-docker-cli.js +155 -0
- package/lib/exec-docker/remote-docker-cli.js.map +1 -0
- package/lib/exec-docker/world.d.ts +37 -0
- package/lib/exec-docker/world.js +62 -0
- package/lib/exec-docker/world.js.map +1 -0
- package/lib/exec-ssh/connection-pool.d.ts +25 -0
- package/lib/exec-ssh/connection-pool.js +54 -0
- package/lib/exec-ssh/connection-pool.js.map +1 -0
- package/lib/exec-ssh/connection.d.ts +2 -0
- package/lib/exec-ssh/connection.js +125 -0
- package/lib/exec-ssh/connection.js.map +1 -0
- package/lib/exec-ssh/env-scrub.d.ts +7 -0
- package/lib/exec-ssh/env-scrub.js +40 -0
- package/lib/exec-ssh/env-scrub.js.map +1 -0
- package/lib/exec-ssh/executor.d.ts +46 -0
- package/lib/exec-ssh/executor.js +212 -0
- package/lib/exec-ssh/executor.js.map +1 -0
- package/lib/exec-ssh/fs-provider.d.ts +50 -0
- package/lib/exec-ssh/fs-provider.js +92 -0
- package/lib/exec-ssh/fs-provider.js.map +1 -0
- package/lib/exec-ssh/index.d.ts +16 -0
- package/lib/exec-ssh/index.js +30 -0
- package/lib/exec-ssh/index.js.map +1 -0
- package/lib/exec-ssh/process-provider.d.ts +20 -0
- package/lib/exec-ssh/process-provider.js +75 -0
- package/lib/exec-ssh/process-provider.js.map +1 -0
- package/lib/exec-ssh/runner.d.ts +13 -0
- package/lib/exec-ssh/runner.js +62 -0
- package/lib/exec-ssh/runner.js.map +1 -0
- package/lib/exec-ssh/world.d.ts +79 -0
- package/lib/exec-ssh/world.js +95 -0
- package/lib/exec-ssh/world.js.map +1 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/lib/router/decorate-fs.d.ts +6 -0
- package/lib/router/decorate-fs.js +78 -0
- package/lib/router/decorate-fs.js.map +1 -0
- package/lib/router/decorate-shell.d.ts +6 -0
- package/lib/router/decorate-shell.js +35 -0
- package/lib/router/decorate-shell.js.map +1 -0
- package/lib/router/decorate-subprocess.d.ts +6 -0
- package/lib/router/decorate-subprocess.js +35 -0
- package/lib/router/decorate-subprocess.js.map +1 -0
- package/lib/router/index.d.ts +10 -0
- package/lib/router/index.js +21 -0
- package/lib/router/index.js.map +1 -0
- package/lib/router/resolve.d.ts +22 -0
- package/lib/router/resolve.js +45 -0
- package/lib/router/resolve.js.map +1 -0
- package/lib/router/router-fs.d.ts +25 -0
- package/lib/router/router-fs.js +64 -0
- package/lib/router/router-fs.js.map +1 -0
- package/lib/router/router-shell.d.ts +14 -0
- package/lib/router/router-shell.js +36 -0
- package/lib/router/router-shell.js.map +1 -0
- package/lib/router/router-subprocess.d.ts +13 -0
- package/lib/router/router-subprocess.js +33 -0
- package/lib/router/router-subprocess.js.map +1 -0
- package/lib/router/types.d.ts +2 -0
- package/lib/router/types.js +1 -0
- package/lib/types-C0TtEO7z.d.ts +67 -0
- package/lib/ui/decorate-picker.d.ts +6 -0
- package/lib/ui/decorate-picker.js +58 -0
- package/lib/ui/decorate-picker.js.map +1 -0
- package/lib/ui/index.d.ts +8 -0
- package/lib/ui/index.js +11 -0
- package/lib/ui/index.js.map +1 -0
- package/lib/ui/picker.d.ts +10 -0
- package/lib/ui/picker.js +58 -0
- package/lib/ui/picker.js.map +1 -0
- package/package.json +146 -13
- package/LICENSE +0 -21
package/lib/core/test.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { createConnection } from "node:net";
|
|
2
|
+
//#region src/core/test.ts
|
|
3
|
+
function testReachability(host, port, timeoutMs = 5e3) {
|
|
4
|
+
return new Promise((resolve) => {
|
|
5
|
+
let settled = false;
|
|
6
|
+
const finish = (report) => {
|
|
7
|
+
if (settled) return;
|
|
8
|
+
settled = true;
|
|
9
|
+
socket.destroy();
|
|
10
|
+
resolve(report);
|
|
11
|
+
};
|
|
12
|
+
const socket = createConnection({
|
|
13
|
+
host,
|
|
14
|
+
port
|
|
15
|
+
});
|
|
16
|
+
socket.setTimeout(timeoutMs);
|
|
17
|
+
socket.once("connect", () => finish({
|
|
18
|
+
ok: true,
|
|
19
|
+
reachable: true
|
|
20
|
+
}));
|
|
21
|
+
socket.once("timeout", () => finish({
|
|
22
|
+
ok: false,
|
|
23
|
+
reachable: false,
|
|
24
|
+
error: "connection timed out"
|
|
25
|
+
}));
|
|
26
|
+
socket.once("error", (err) => finish({
|
|
27
|
+
ok: false,
|
|
28
|
+
reachable: false,
|
|
29
|
+
error: err instanceof Error ? err.message : String(err)
|
|
30
|
+
}));
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
async function testNode(registry, id, sshTester, overrides, dockerTester) {
|
|
34
|
+
const node = registry.get(id);
|
|
35
|
+
if (!node) return {
|
|
36
|
+
ok: false,
|
|
37
|
+
error: `node ${id} not found`
|
|
38
|
+
};
|
|
39
|
+
if (node.type === "local-host") {
|
|
40
|
+
registry.setStatus(id, {
|
|
41
|
+
reachable: true,
|
|
42
|
+
lastCheckedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
ok: true,
|
|
46
|
+
reachable: true
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (node.type === "local-docker") {
|
|
50
|
+
let report;
|
|
51
|
+
if (dockerTester) report = await dockerTester.test({
|
|
52
|
+
id: node.id,
|
|
53
|
+
type: node.type,
|
|
54
|
+
docker: node.docker
|
|
55
|
+
});
|
|
56
|
+
else report = {
|
|
57
|
+
ok: true,
|
|
58
|
+
reachable: true
|
|
59
|
+
};
|
|
60
|
+
registry.setStatus(id, {
|
|
61
|
+
reachable: report.reachable,
|
|
62
|
+
lastCheckedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
63
|
+
error: report.error
|
|
64
|
+
});
|
|
65
|
+
return report;
|
|
66
|
+
}
|
|
67
|
+
if (node.type === "remote-ssh" || node.type === "remote-docker") {
|
|
68
|
+
const ssh = overrides?.ssh ?? node.ssh;
|
|
69
|
+
if (!ssh) return {
|
|
70
|
+
ok: false,
|
|
71
|
+
error: "node has no ssh config"
|
|
72
|
+
};
|
|
73
|
+
let report;
|
|
74
|
+
if (sshTester) {
|
|
75
|
+
const savedSecrets = await registry.getSecrets(id) ?? {};
|
|
76
|
+
const secrets = overrides?.secrets ? {
|
|
77
|
+
password: overrides.secrets.password || savedSecrets.password,
|
|
78
|
+
privateKey: overrides.secrets.privateKey || savedSecrets.privateKey,
|
|
79
|
+
passphrase: overrides.secrets.passphrase || savedSecrets.passphrase
|
|
80
|
+
} : savedSecrets;
|
|
81
|
+
const t = await sshTester.test({
|
|
82
|
+
host: ssh.host,
|
|
83
|
+
port: ssh.port,
|
|
84
|
+
username: ssh.username,
|
|
85
|
+
auth: ssh.auth,
|
|
86
|
+
jump: ssh.jump
|
|
87
|
+
}, secrets);
|
|
88
|
+
report = {
|
|
89
|
+
ok: t.ok,
|
|
90
|
+
reachable: t.ok,
|
|
91
|
+
error: t.error,
|
|
92
|
+
...t.category ? { category: t.category } : {}
|
|
93
|
+
};
|
|
94
|
+
} else report = await testReachability(ssh.host, ssh.port);
|
|
95
|
+
if (report.ok && node.type === "remote-docker" && dockerTester) report = await dockerTester.test({
|
|
96
|
+
id: node.id,
|
|
97
|
+
type: node.type,
|
|
98
|
+
ssh,
|
|
99
|
+
docker: node.docker
|
|
100
|
+
});
|
|
101
|
+
registry.setStatus(id, {
|
|
102
|
+
reachable: report.reachable,
|
|
103
|
+
lastCheckedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
104
|
+
error: report.error
|
|
105
|
+
});
|
|
106
|
+
return report;
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
ok: false,
|
|
110
|
+
error: "unsupported node type"
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
export { testNode, testReachability };
|
|
115
|
+
|
|
116
|
+
//# sourceMappingURL=test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test.js","names":[],"sources":["../../src/core/test.ts"],"sourcesContent":["import { createConnection } from 'node:net'\nimport type { NodeRegistry, NodeSsh, NodeTestReport } from './node-registry.js'\n\nexport interface SshHandshakeTester {\n test(\n target: { host: string; port: number; username: string; auth: NodeSsh['auth']; jump?: NodeSsh['jump'] },\n secrets: { password?: string; privateKey?: string; passphrase?: string },\n ): Promise<{ ok: boolean; error?: string; category?: string }>\n}\n\nexport function testReachability(host: string, port: number, timeoutMs = 5000): Promise<NodeTestReport> {\n return new Promise((resolve) => {\n let settled = false\n const finish = (report: NodeTestReport) => {\n if (settled) return\n settled = true\n socket.destroy()\n resolve(report)\n }\n const socket = createConnection({ host, port })\n socket.setTimeout(timeoutMs)\n socket.once('connect', () => finish({ ok: true, reachable: true }))\n socket.once('timeout', () => finish({ ok: false, reachable: false, error: 'connection timed out' }))\n socket.once('error', (err) =>\n finish({ ok: false, reachable: false, error: err instanceof Error ? err.message : String(err) }),\n )\n })\n}\n\nexport interface NodeTestOverrides {\n ssh?: NodeSsh\n secrets?: { password?: string; privateKey?: string; passphrase?: string }\n}\n\nexport interface DockerNodeTester {\n test(node: { id: string; type: string; ssh?: NodeSsh; docker?: { containerId?: string; image?: string } }): Promise<NodeTestReport>\n}\n\nexport async function testNode(\n registry: NodeRegistry,\n id: string,\n sshTester?: SshHandshakeTester,\n overrides?: NodeTestOverrides,\n dockerTester?: DockerNodeTester,\n): Promise<NodeTestReport> {\n const node = registry.get(id)\n if (!node) return { ok: false, error: `node ${id} not found` }\n if (node.type === 'local-host') {\n registry.setStatus(id, { reachable: true, lastCheckedAt: new Date().toISOString() })\n return { ok: true, reachable: true }\n }\n if (node.type === 'local-docker') {\n let report: NodeTestReport\n if (dockerTester) {\n report = await dockerTester.test({ id: node.id, type: node.type, docker: node.docker })\n } else {\n report = { ok: true, reachable: true }\n }\n registry.setStatus(id, {\n reachable: report.reachable,\n lastCheckedAt: new Date().toISOString(),\n error: report.error,\n })\n return report\n }\n if (node.type === 'remote-ssh' || node.type === 'remote-docker') {\n const ssh = overrides?.ssh ?? node.ssh\n if (!ssh) return { ok: false, error: 'node has no ssh config' }\n let report: NodeTestReport\n if (sshTester) {\n const savedSecrets = (await registry.getSecrets(id)) ?? {}\n const secrets = overrides?.secrets\n ? {\n password: overrides.secrets.password || savedSecrets.password,\n privateKey: overrides.secrets.privateKey || savedSecrets.privateKey,\n passphrase: overrides.secrets.passphrase || savedSecrets.passphrase,\n }\n : savedSecrets\n const t = await sshTester.test(\n { host: ssh.host, port: ssh.port, username: ssh.username, auth: ssh.auth, jump: ssh.jump },\n secrets,\n )\n report = { ok: t.ok, reachable: t.ok, error: t.error, ...(t.category ? { category: t.category } : {}) }\n } else {\n report = await testReachability(ssh.host, ssh.port)\n }\n if (report.ok && node.type === 'remote-docker' && dockerTester) {\n report = await dockerTester.test({ id: node.id, type: node.type, ssh, docker: node.docker })\n }\n registry.setStatus(id, {\n reachable: report.reachable,\n lastCheckedAt: new Date().toISOString(),\n error: report.error,\n })\n return report\n }\n return { ok: false, error: 'unsupported node type' }\n}\n"],"mappings":";;AAUA,SAAgB,iBAAiB,MAAc,MAAc,YAAY,KAA+B;CACtG,OAAO,IAAI,SAAS,YAAY;EAC9B,IAAI,UAAU;EACd,MAAM,UAAU,WAA2B;GACzC,IAAI,SAAS;GACb,UAAU;GACV,OAAO,QAAQ;GACf,QAAQ,MAAM;EAChB;EACA,MAAM,SAAS,iBAAiB;GAAE;GAAM;EAAK,CAAC;EAC9C,OAAO,WAAW,SAAS;EAC3B,OAAO,KAAK,iBAAiB,OAAO;GAAE,IAAI;GAAM,WAAW;EAAK,CAAC,CAAC;EAClE,OAAO,KAAK,iBAAiB,OAAO;GAAE,IAAI;GAAO,WAAW;GAAO,OAAO;EAAuB,CAAC,CAAC;EACnG,OAAO,KAAK,UAAU,QACpB,OAAO;GAAE,IAAI;GAAO,WAAW;GAAO,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;EAAE,CAAC,CACjG;CACF,CAAC;AACH;AAWA,eAAsB,SACpB,UACA,IACA,WACA,WACA,cACyB;CACzB,MAAM,OAAO,SAAS,IAAI,EAAE;CAC5B,IAAI,CAAC,MAAM,OAAO;EAAE,IAAI;EAAO,OAAO,QAAQ,GAAG;CAAY;CAC7D,IAAI,KAAK,SAAS,cAAc;EAC9B,SAAS,UAAU,IAAI;GAAE,WAAW;GAAM,gCAAe,IAAI,KAAK,EAAA,CAAE,YAAY;EAAE,CAAC;EACnF,OAAO;GAAE,IAAI;GAAM,WAAW;EAAK;CACrC;CACA,IAAI,KAAK,SAAS,gBAAgB;EAChC,IAAI;EACJ,IAAI,cACF,SAAS,MAAM,aAAa,KAAK;GAAE,IAAI,KAAK;GAAI,MAAM,KAAK;GAAM,QAAQ,KAAK;EAAO,CAAC;OAEtF,SAAS;GAAE,IAAI;GAAM,WAAW;EAAK;EAEvC,SAAS,UAAU,IAAI;GACrB,WAAW,OAAO;GAClB,gCAAe,IAAI,KAAK,EAAA,CAAE,YAAY;GACtC,OAAO,OAAO;EAChB,CAAC;EACD,OAAO;CACT;CACA,IAAI,KAAK,SAAS,gBAAgB,KAAK,SAAS,iBAAiB;EAC/D,MAAM,MAAM,WAAW,OAAO,KAAK;EACnC,IAAI,CAAC,KAAK,OAAO;GAAE,IAAI;GAAO,OAAO;EAAyB;EAC9D,IAAI;EACJ,IAAI,WAAW;GACb,MAAM,eAAgB,MAAM,SAAS,WAAW,EAAE,KAAM,CAAC;GACzD,MAAM,UAAU,WAAW,UACvB;IACE,UAAU,UAAU,QAAQ,YAAY,aAAa;IACrD,YAAY,UAAU,QAAQ,cAAc,aAAa;IACzD,YAAY,UAAU,QAAQ,cAAc,aAAa;GAC3D,IACA;GACJ,MAAM,IAAI,MAAM,UAAU,KACxB;IAAE,MAAM,IAAI;IAAM,MAAM,IAAI;IAAM,UAAU,IAAI;IAAU,MAAM,IAAI;IAAM,MAAM,IAAI;GAAK,GACzF,OACF;GACA,SAAS;IAAE,IAAI,EAAE;IAAI,WAAW,EAAE;IAAI,OAAO,EAAE;IAAO,GAAI,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,IAAI,CAAC;GAAG;EACxG,OACE,SAAS,MAAM,iBAAiB,IAAI,MAAM,IAAI,IAAI;EAEpD,IAAI,OAAO,MAAM,KAAK,SAAS,mBAAmB,cAChD,SAAS,MAAM,aAAa,KAAK;GAAE,IAAI,KAAK;GAAI,MAAM,KAAK;GAAM;GAAK,QAAQ,KAAK;EAAO,CAAC;EAE7F,SAAS,UAAU,IAAI;GACrB,WAAW,OAAO;GAClB,gCAAe,IAAI,KAAK,EAAA,CAAE,YAAY;GACtC,OAAO,OAAO;EAChB,CAAC;EACD,OAAO;CACT;CACA,OAAO;EAAE,IAAI;EAAO,OAAO;CAAwB;AACrD"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//#region src/core/workspace-bindings.d.ts
|
|
2
|
+
interface WorkspaceBinding {
|
|
3
|
+
mirrorPath: string;
|
|
4
|
+
nodeId: string;
|
|
5
|
+
remotePath: string;
|
|
6
|
+
workspaceId?: string;
|
|
7
|
+
}
|
|
8
|
+
interface BindingHit {
|
|
9
|
+
nodeId: string;
|
|
10
|
+
remotePath: string;
|
|
11
|
+
}
|
|
12
|
+
declare class WorkspaceBindingsStore {
|
|
13
|
+
private bindings;
|
|
14
|
+
private loaded;
|
|
15
|
+
private file;
|
|
16
|
+
private load;
|
|
17
|
+
private save;
|
|
18
|
+
add(binding: Omit<WorkspaceBinding, 'mirrorPath'> & {
|
|
19
|
+
mirrorPath: string;
|
|
20
|
+
}): void;
|
|
21
|
+
remove(mirrorPath: string): void;
|
|
22
|
+
list(): WorkspaceBinding[];
|
|
23
|
+
resolve(path: string): BindingHit | undefined;
|
|
24
|
+
mirrorRoot(nodeId: string, slug: string): string;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { BindingHit, WorkspaceBinding, WorkspaceBindingsStore };
|
|
28
|
+
//# sourceMappingURL=workspace-bindings.d.ts.map
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
//#region src/core/workspace-bindings.ts
|
|
5
|
+
function dshHome() {
|
|
6
|
+
return process.env.DSH_HOME ?? join(homedir(), ".dsh");
|
|
7
|
+
}
|
|
8
|
+
function normalize(p) {
|
|
9
|
+
return p.replace(/\/+$/, "") || "/";
|
|
10
|
+
}
|
|
11
|
+
var WorkspaceBindingsStore = class {
|
|
12
|
+
bindings = [];
|
|
13
|
+
loaded = false;
|
|
14
|
+
file() {
|
|
15
|
+
return join(dshHome(), "dshb", "workspace-bindings.json");
|
|
16
|
+
}
|
|
17
|
+
load() {
|
|
18
|
+
if (this.loaded) return;
|
|
19
|
+
this.loaded = true;
|
|
20
|
+
try {
|
|
21
|
+
this.bindings = JSON.parse(readFileSync(this.file(), "utf8")).map((b) => ({
|
|
22
|
+
...b,
|
|
23
|
+
mirrorPath: normalize(b.mirrorPath)
|
|
24
|
+
}));
|
|
25
|
+
} catch {
|
|
26
|
+
this.bindings = [];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
save() {
|
|
30
|
+
const file = this.file();
|
|
31
|
+
mkdirSync(join(file, ".."), {
|
|
32
|
+
recursive: true,
|
|
33
|
+
mode: 448
|
|
34
|
+
});
|
|
35
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
36
|
+
writeFileSync(tmp, JSON.stringify(this.bindings, null, 2), { mode: 384 });
|
|
37
|
+
renameSync(tmp, file);
|
|
38
|
+
chmodSync(file, 384);
|
|
39
|
+
}
|
|
40
|
+
add(binding) {
|
|
41
|
+
this.load();
|
|
42
|
+
const mirrorPath = normalize(binding.mirrorPath);
|
|
43
|
+
this.bindings = this.bindings.filter((b) => normalize(b.mirrorPath) !== mirrorPath);
|
|
44
|
+
this.bindings.push({
|
|
45
|
+
...binding,
|
|
46
|
+
mirrorPath
|
|
47
|
+
});
|
|
48
|
+
this.save();
|
|
49
|
+
}
|
|
50
|
+
remove(mirrorPath) {
|
|
51
|
+
this.load();
|
|
52
|
+
const target = normalize(mirrorPath);
|
|
53
|
+
this.bindings = this.bindings.filter((b) => normalize(b.mirrorPath) !== target);
|
|
54
|
+
this.save();
|
|
55
|
+
}
|
|
56
|
+
list() {
|
|
57
|
+
this.load();
|
|
58
|
+
return this.bindings.map((b) => ({ ...b }));
|
|
59
|
+
}
|
|
60
|
+
resolve(path) {
|
|
61
|
+
this.load();
|
|
62
|
+
const target = normalize(path);
|
|
63
|
+
let best;
|
|
64
|
+
for (const b of this.bindings) {
|
|
65
|
+
const root = normalize(b.mirrorPath);
|
|
66
|
+
if (target === root || target.startsWith(`${root}/`)) {
|
|
67
|
+
if (!best || root.length > normalize(best.mirrorPath).length) best = b;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (!best) return void 0;
|
|
71
|
+
const root = normalize(best.mirrorPath);
|
|
72
|
+
const remoteRoot = normalize(best.remotePath);
|
|
73
|
+
const suffix = target === root ? "" : target.slice(root.length);
|
|
74
|
+
return {
|
|
75
|
+
nodeId: best.nodeId,
|
|
76
|
+
remotePath: `${remoteRoot}${suffix}`
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
mirrorRoot(nodeId, slug) {
|
|
80
|
+
return join(dshHome(), "dshb", "mirrors", nodeId, slug);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
//#endregion
|
|
84
|
+
export { WorkspaceBindingsStore };
|
|
85
|
+
|
|
86
|
+
//# sourceMappingURL=workspace-bindings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-bindings.js","names":[],"sources":["../../src/core/workspace-bindings.ts"],"sourcesContent":["import { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\n\nexport interface WorkspaceBinding {\n mirrorPath: string\n nodeId: string\n remotePath: string\n workspaceId?: string\n}\n\nexport interface BindingHit {\n nodeId: string\n remotePath: string\n}\n\nfunction dshHome(): string {\n return process.env.DSH_HOME ?? join(homedir(), '.dsh')\n}\n\nfunction normalize(p: string): string {\n return p.replace(/\\/+$/, '') || '/'\n}\n\nexport class WorkspaceBindingsStore {\n private bindings: WorkspaceBinding[] = []\n private loaded = false\n\n private file(): string {\n return join(dshHome(), 'dshb', 'workspace-bindings.json')\n }\n\n private load(): void {\n if (this.loaded) return\n this.loaded = true\n try {\n this.bindings = (JSON.parse(readFileSync(this.file(), 'utf8')) as WorkspaceBinding[]).map((b) => ({\n ...b,\n mirrorPath: normalize(b.mirrorPath),\n }))\n } catch {\n this.bindings = []\n }\n }\n\n private save(): void {\n const file = this.file()\n mkdirSync(join(file, '..'), { recursive: true, mode: 0o700 })\n const tmp = `${file}.${process.pid}.tmp`\n writeFileSync(tmp, JSON.stringify(this.bindings, null, 2), { mode: 0o600 })\n renameSync(tmp, file)\n chmodSync(file, 0o600)\n }\n\n add(binding: Omit<WorkspaceBinding, 'mirrorPath'> & { mirrorPath: string }): void {\n this.load()\n const mirrorPath = normalize(binding.mirrorPath)\n this.bindings = this.bindings.filter((b) => normalize(b.mirrorPath) !== mirrorPath)\n this.bindings.push({ ...binding, mirrorPath })\n this.save()\n }\n\n remove(mirrorPath: string): void {\n this.load()\n const target = normalize(mirrorPath)\n this.bindings = this.bindings.filter((b) => normalize(b.mirrorPath) !== target)\n this.save()\n }\n\n list(): WorkspaceBinding[] {\n this.load()\n return this.bindings.map((b) => ({ ...b }))\n }\n\n resolve(path: string): BindingHit | undefined {\n this.load()\n const target = normalize(path)\n let best: WorkspaceBinding | undefined\n for (const b of this.bindings) {\n const root = normalize(b.mirrorPath)\n if (target === root || target.startsWith(`${root}/`)) {\n if (!best || root.length > normalize(best.mirrorPath).length) best = b\n }\n }\n if (!best) return undefined\n const root = normalize(best.mirrorPath)\n const remoteRoot = normalize(best.remotePath)\n const suffix = target === root ? '' : target.slice(root.length)\n return { nodeId: best.nodeId, remotePath: `${remoteRoot}${suffix}` }\n }\n\n mirrorRoot(nodeId: string, slug: string): string {\n return join(dshHome(), 'dshb', 'mirrors', nodeId, slug)\n }\n}\n"],"mappings":";;;;AAgBA,SAAS,UAAkB;CACzB,OAAO,QAAQ,IAAI,YAAY,KAAK,QAAQ,GAAG,MAAM;AACvD;AAEA,SAAS,UAAU,GAAmB;CACpC,OAAO,EAAE,QAAQ,QAAQ,EAAE,KAAK;AAClC;AAEA,IAAa,yBAAb,MAAoC;CAClC,WAAuC,CAAC;CACxC,SAAiB;CAEjB,OAAuB;EACrB,OAAO,KAAK,QAAQ,GAAG,QAAQ,yBAAyB;CAC1D;CAEA,OAAqB;EACnB,IAAI,KAAK,QAAQ;EACjB,KAAK,SAAS;EACd,IAAI;GACF,KAAK,WAAY,KAAK,MAAM,aAAa,KAAK,KAAK,GAAG,MAAM,CAAC,CAAC,CAAwB,KAAK,OAAO;IAChG,GAAG;IACH,YAAY,UAAU,EAAE,UAAU;GACpC,EAAE;EACJ,QAAQ;GACN,KAAK,WAAW,CAAC;EACnB;CACF;CAEA,OAAqB;EACnB,MAAM,OAAO,KAAK,KAAK;EACvB,UAAU,KAAK,MAAM,IAAI,GAAG;GAAE,WAAW;GAAM,MAAM;EAAM,CAAC;EAC5D,MAAM,MAAM,GAAG,KAAK,GAAG,QAAQ,IAAI;EACnC,cAAc,KAAK,KAAK,UAAU,KAAK,UAAU,MAAM,CAAC,GAAG,EAAE,MAAM,IAAM,CAAC;EAC1E,WAAW,KAAK,IAAI;EACpB,UAAU,MAAM,GAAK;CACvB;CAEA,IAAI,SAA8E;EAChF,KAAK,KAAK;EACV,MAAM,aAAa,UAAU,QAAQ,UAAU;EAC/C,KAAK,WAAW,KAAK,SAAS,QAAQ,MAAM,UAAU,EAAE,UAAU,MAAM,UAAU;EAClF,KAAK,SAAS,KAAK;GAAE,GAAG;GAAS;EAAW,CAAC;EAC7C,KAAK,KAAK;CACZ;CAEA,OAAO,YAA0B;EAC/B,KAAK,KAAK;EACV,MAAM,SAAS,UAAU,UAAU;EACnC,KAAK,WAAW,KAAK,SAAS,QAAQ,MAAM,UAAU,EAAE,UAAU,MAAM,MAAM;EAC9E,KAAK,KAAK;CACZ;CAEA,OAA2B;EACzB,KAAK,KAAK;EACV,OAAO,KAAK,SAAS,KAAK,OAAO,EAAE,GAAG,EAAE,EAAE;CAC5C;CAEA,QAAQ,MAAsC;EAC5C,KAAK,KAAK;EACV,MAAM,SAAS,UAAU,IAAI;EAC7B,IAAI;EACJ,KAAK,MAAM,KAAK,KAAK,UAAU;GAC7B,MAAM,OAAO,UAAU,EAAE,UAAU;GACnC,IAAI,WAAW,QAAQ,OAAO,WAAW,GAAG,KAAK,EAAE,GAC7C;QAAA,CAAC,QAAQ,KAAK,SAAS,UAAU,KAAK,UAAU,CAAC,CAAC,QAAQ,OAAO;GAAA;EAEzE;EACA,IAAI,CAAC,MAAM,OAAO,KAAA;EAClB,MAAM,OAAO,UAAU,KAAK,UAAU;EACtC,MAAM,aAAa,UAAU,KAAK,UAAU;EAC5C,MAAM,SAAS,WAAW,OAAO,KAAK,OAAO,MAAM,KAAK,MAAM;EAC9D,OAAO;GAAE,QAAQ,KAAK;GAAQ,YAAY,GAAG,aAAa;EAAS;CACrE;CAEA,WAAW,QAAgB,MAAsB;EAC/C,OAAO,KAAK,QAAQ,GAAG,QAAQ,WAAW,QAAQ,IAAI;CACxD;AACF"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { NodeRegistry } from "./node-registry.js";
|
|
2
|
+
import { WorkspaceBindingsStore } from "./workspace-bindings.js";
|
|
3
|
+
import { Context } from "@deepseek-ai/cordis";
|
|
4
|
+
//#region src/core/workspaces.d.ts
|
|
5
|
+
declare function registerWorkspaceRoutes(ctx: Context, registry: NodeRegistry, bindings: WorkspaceBindingsStore): void;
|
|
6
|
+
declare function registerRemoteDownloadRoutes(ctx: Context, registry: NodeRegistry, bindings: WorkspaceBindingsStore): void;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { registerRemoteDownloadRoutes, registerWorkspaceRoutes };
|
|
9
|
+
//# sourceMappingURL=workspaces.d.ts.map
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
import { basename, extname } from "node:path";
|
|
3
|
+
//#region src/core/workspaces.ts
|
|
4
|
+
const MAX_BODY = 65536;
|
|
5
|
+
function sendJson(res, status, body) {
|
|
6
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
7
|
+
res.end(JSON.stringify(body));
|
|
8
|
+
}
|
|
9
|
+
function readBody(req) {
|
|
10
|
+
return new Promise((resolve) => {
|
|
11
|
+
if ((req.headers["content-type"] ?? "") !== "application/json") {
|
|
12
|
+
resolve(null);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const chunks = [];
|
|
16
|
+
let size = 0;
|
|
17
|
+
req.on("data", (chunk) => {
|
|
18
|
+
size += chunk.length;
|
|
19
|
+
if (size > MAX_BODY) {
|
|
20
|
+
resolve(null);
|
|
21
|
+
req.destroy();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
chunks.push(chunk);
|
|
25
|
+
});
|
|
26
|
+
req.on("end", () => {
|
|
27
|
+
try {
|
|
28
|
+
resolve(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
29
|
+
} catch {
|
|
30
|
+
resolve(null);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
req.on("error", () => resolve(null));
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function pathSegments(req) {
|
|
37
|
+
return new URL(req.url ?? "/", "http://x").pathname.split("/").filter(Boolean);
|
|
38
|
+
}
|
|
39
|
+
function slugify(name) {
|
|
40
|
+
return name.trim().toLowerCase().replace(/[^a-z0-9一-龥]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "workspace";
|
|
41
|
+
}
|
|
42
|
+
function registerWorkspaceRoutes(ctx, registry, bindings) {
|
|
43
|
+
const webServer = ctx.webServer;
|
|
44
|
+
ctx.effect(() => webServer.register({
|
|
45
|
+
kind: "prefix",
|
|
46
|
+
path: "/api/dshb/workspaces",
|
|
47
|
+
handler: async (req, res) => {
|
|
48
|
+
const worlds = ctx.get("dshbWorlds");
|
|
49
|
+
const action = pathSegments(req)[3];
|
|
50
|
+
if (req.method === "POST" && action === "bind") {
|
|
51
|
+
const body = await readBody(req);
|
|
52
|
+
if (!body || typeof body.nodeId !== "string" || typeof body.remotePath !== "string") {
|
|
53
|
+
sendJson(res, 400, {
|
|
54
|
+
ok: false,
|
|
55
|
+
error: "需要 nodeId 与 remotePath"
|
|
56
|
+
});
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const node = registry.get(body.nodeId);
|
|
60
|
+
if (!node) {
|
|
61
|
+
sendJson(res, 404, {
|
|
62
|
+
ok: false,
|
|
63
|
+
error: "node not found"
|
|
64
|
+
});
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const remotePath = String(body.remotePath);
|
|
68
|
+
if (node.type === "local-host") {
|
|
69
|
+
sendJson(res, 200, {
|
|
70
|
+
ok: true,
|
|
71
|
+
mirrorPath: remotePath
|
|
72
|
+
});
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const slug = slugify(String(body.name ?? remotePath.split("/").filter(Boolean).pop() ?? "workspace"));
|
|
76
|
+
const mirrorPath = bindings.mirrorRoot(node.id, slug);
|
|
77
|
+
try {
|
|
78
|
+
if (node.type === "local-docker" || node.type === "remote-docker") {
|
|
79
|
+
const dockerWorlds = ctx.get("dshbDockerWorldsGeneric");
|
|
80
|
+
if (!dockerWorlds) {
|
|
81
|
+
sendJson(res, 503, {
|
|
82
|
+
ok: false,
|
|
83
|
+
error: "Docker 执行世界不可用(dshb-exec-docker 未加载)"
|
|
84
|
+
});
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const world = await dockerWorlds.ensure(node.id);
|
|
88
|
+
if (!world) {
|
|
89
|
+
sendJson(res, 503, {
|
|
90
|
+
ok: false,
|
|
91
|
+
error: "容器未就绪,请先拉起容器"
|
|
92
|
+
});
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
await world.ensureDir(remotePath);
|
|
96
|
+
} else {
|
|
97
|
+
if (!worlds) {
|
|
98
|
+
sendJson(res, 503, {
|
|
99
|
+
ok: false,
|
|
100
|
+
error: "远程执行世界不可用"
|
|
101
|
+
});
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const world = await worlds.ensure(node.id);
|
|
105
|
+
if (!world) {
|
|
106
|
+
sendJson(res, 503, {
|
|
107
|
+
ok: false,
|
|
108
|
+
error: "节点执行世界未就绪"
|
|
109
|
+
});
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
await world.ensureDir(remotePath);
|
|
113
|
+
}
|
|
114
|
+
mkdirSync(mirrorPath, { recursive: true });
|
|
115
|
+
bindings.add({
|
|
116
|
+
mirrorPath,
|
|
117
|
+
nodeId: node.id,
|
|
118
|
+
remotePath
|
|
119
|
+
});
|
|
120
|
+
sendJson(res, 201, {
|
|
121
|
+
ok: true,
|
|
122
|
+
mirrorPath
|
|
123
|
+
});
|
|
124
|
+
} catch (err) {
|
|
125
|
+
sendJson(res, 502, {
|
|
126
|
+
ok: false,
|
|
127
|
+
error: err instanceof Error ? err.message : String(err)
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (req.method === "GET" && action === void 0) {
|
|
133
|
+
sendJson(res, 200, {
|
|
134
|
+
ok: true,
|
|
135
|
+
bindings: bindings.list()
|
|
136
|
+
});
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
sendJson(res, 404, {
|
|
140
|
+
ok: false,
|
|
141
|
+
error: "not found"
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
function resolveRemoteWorld(ctx, node) {
|
|
147
|
+
if (node.type === "local-docker" || node.type === "remote-docker") return ctx.get("dshbDockerWorldsGeneric");
|
|
148
|
+
if (node.type === "remote-ssh") return ctx.get("dshbWorlds");
|
|
149
|
+
}
|
|
150
|
+
const REMOTE_MAX_BYTES = 536870912;
|
|
151
|
+
function registerRemoteDownloadRoutes(ctx, registry, bindings) {
|
|
152
|
+
const webServer = ctx.webServer;
|
|
153
|
+
ctx.effect(() => webServer.register({
|
|
154
|
+
kind: "prefix",
|
|
155
|
+
path: "/api/dshb/remote",
|
|
156
|
+
handler: async (req, res) => {
|
|
157
|
+
const action = pathSegments(req)[3];
|
|
158
|
+
if (req.method !== "GET" || action !== "bound" && action !== "file") {
|
|
159
|
+
sendJson(res, 404, {
|
|
160
|
+
ok: false,
|
|
161
|
+
error: "not found"
|
|
162
|
+
});
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const path = new URL(req.url ?? "/", "http://x").searchParams.get("path") ?? "";
|
|
166
|
+
if (!path) {
|
|
167
|
+
sendJson(res, 400, {
|
|
168
|
+
ok: false,
|
|
169
|
+
error: "需要 path"
|
|
170
|
+
});
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const hit = bindings.resolve(path);
|
|
174
|
+
if (!hit) {
|
|
175
|
+
sendJson(res, action === "bound" ? 200 : 404, {
|
|
176
|
+
ok: true,
|
|
177
|
+
bound: false
|
|
178
|
+
});
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const node = registry.get(hit.nodeId);
|
|
182
|
+
if (!node) {
|
|
183
|
+
sendJson(res, 502, {
|
|
184
|
+
ok: false,
|
|
185
|
+
bound: true,
|
|
186
|
+
error: "node not found"
|
|
187
|
+
});
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const worlds = resolveRemoteWorld(ctx, node);
|
|
191
|
+
if (!worlds) {
|
|
192
|
+
sendJson(res, 502, {
|
|
193
|
+
ok: false,
|
|
194
|
+
bound: true,
|
|
195
|
+
error: "远程执行世界不可用"
|
|
196
|
+
});
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
const world = await worlds.ensure(node.id);
|
|
201
|
+
if (!world) {
|
|
202
|
+
sendJson(res, 502, {
|
|
203
|
+
ok: false,
|
|
204
|
+
bound: true,
|
|
205
|
+
error: "节点执行世界未就绪"
|
|
206
|
+
});
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const isFile = (await world.fs.stat(hit.remotePath))?.isFile ?? false;
|
|
210
|
+
if (action === "bound") {
|
|
211
|
+
sendJson(res, 200, {
|
|
212
|
+
ok: true,
|
|
213
|
+
bound: true,
|
|
214
|
+
nodeId: node.id,
|
|
215
|
+
remotePath: hit.remotePath,
|
|
216
|
+
kind: isFile ? "file" : "dir"
|
|
217
|
+
});
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (!isFile) {
|
|
221
|
+
sendJson(res, 404, {
|
|
222
|
+
ok: false,
|
|
223
|
+
bound: true,
|
|
224
|
+
error: "目标不是文件"
|
|
225
|
+
});
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const data = await world.fs.readBytes(hit.remotePath, void 0, REMOTE_MAX_BYTES);
|
|
229
|
+
const name = basename(hit.remotePath) || "download";
|
|
230
|
+
let contentType = {
|
|
231
|
+
".txt": "text/plain",
|
|
232
|
+
".md": "text/markdown",
|
|
233
|
+
".json": "application/json",
|
|
234
|
+
".html": "text/html",
|
|
235
|
+
".htm": "text/html",
|
|
236
|
+
".css": "text/css",
|
|
237
|
+
".js": "text/javascript",
|
|
238
|
+
".mjs": "text/javascript",
|
|
239
|
+
".svg": "image/svg+xml",
|
|
240
|
+
".png": "image/png",
|
|
241
|
+
".jpg": "image/jpeg",
|
|
242
|
+
".jpeg": "image/jpeg",
|
|
243
|
+
".gif": "image/gif",
|
|
244
|
+
".webp": "image/webp",
|
|
245
|
+
".pdf": "application/pdf",
|
|
246
|
+
".zip": "application/zip",
|
|
247
|
+
".gz": "application/gzip",
|
|
248
|
+
".tar": "application/x-tar",
|
|
249
|
+
".csv": "text/csv",
|
|
250
|
+
".yaml": "text/yaml",
|
|
251
|
+
".yml": "text/yaml"
|
|
252
|
+
}[extname(name).toLowerCase()] ?? "application/octet-stream";
|
|
253
|
+
let body = Buffer.from(data);
|
|
254
|
+
if (contentType.startsWith("text/") || contentType === "application/json" || contentType === "application/javascript") {
|
|
255
|
+
contentType += "; charset=utf-8";
|
|
256
|
+
const bom = Buffer.from([
|
|
257
|
+
239,
|
|
258
|
+
187,
|
|
259
|
+
191
|
|
260
|
+
]);
|
|
261
|
+
if (!body.subarray(0, 3).equals(bom)) body = Buffer.concat([bom, body]);
|
|
262
|
+
}
|
|
263
|
+
const asciiFallback = name.replace(/[^\x20-\x7e]/g, "_");
|
|
264
|
+
res.writeHead(200, {
|
|
265
|
+
"content-type": contentType,
|
|
266
|
+
"content-disposition": `attachment; filename="${asciiFallback}"; filename*=UTF-8''${encodeURIComponent(name).replace(/['()]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`)}`,
|
|
267
|
+
"cache-control": "no-store"
|
|
268
|
+
});
|
|
269
|
+
res.end(body);
|
|
270
|
+
} catch (err) {
|
|
271
|
+
sendJson(res, 502, {
|
|
272
|
+
ok: false,
|
|
273
|
+
bound: true,
|
|
274
|
+
error: err instanceof Error ? err.message : String(err)
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}));
|
|
279
|
+
}
|
|
280
|
+
//#endregion
|
|
281
|
+
export { registerRemoteDownloadRoutes, registerWorkspaceRoutes };
|
|
282
|
+
|
|
283
|
+
//# sourceMappingURL=workspaces.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspaces.js","names":[],"sources":["../../src/core/workspaces.ts"],"sourcesContent":["import { mkdirSync } from 'node:fs'\nimport { basename, extname } from 'node:path'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { Context } from '@deepseek-ai/cordis'\nimport type {} from '@deepseek-ai/dsh-host-webserver'\nimport type { NodeRegistry } from './node-registry.js'\nimport type { WorkspaceBindingsStore } from './workspace-bindings.js'\n\nconst MAX_BODY = 64 * 1024\n\nfunction sendJson(res: ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, { 'content-type': 'application/json' })\n res.end(JSON.stringify(body))\n}\n\nfunction readBody(req: IncomingMessage): Promise<Record<string, unknown> | null> {\n return new Promise((resolve) => {\n if ((req.headers['content-type'] ?? '') !== 'application/json') {\n resolve(null)\n return\n }\n const chunks: Buffer[] = []\n let size = 0\n req.on('data', (chunk: Buffer) => {\n size += chunk.length\n if (size > MAX_BODY) {\n resolve(null)\n req.destroy()\n return\n }\n chunks.push(chunk)\n })\n req.on('end', () => {\n try {\n resolve(JSON.parse(Buffer.concat(chunks).toString('utf8')) as Record<string, unknown>)\n } catch {\n resolve(null)\n }\n })\n req.on('error', () => resolve(null))\n })\n}\n\nfunction pathSegments(req: IncomingMessage): string[] {\n return new URL(req.url ?? '/', 'http://x').pathname.split('/').filter(Boolean)\n}\n\ninterface WorldsLike {\n ensure(nodeId: string): Promise<{ ensureDir(path: string): Promise<void> } | undefined>\n}\n\ninterface DockerProvisionLike {\n provision(nodeId: string): Promise<{ containerId: string } | undefined>\n}\n\nfunction slugify(name: string): string {\n return name\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9一-龥]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 40) || 'workspace'\n}\n\nexport function registerWorkspaceRoutes(ctx: Context, registry: NodeRegistry, bindings: WorkspaceBindingsStore): void {\n const webServer = ctx.webServer\n\n ctx.effect(() =>\n webServer.register({\n kind: 'prefix',\n path: '/api/dshb/workspaces',\n handler: async (req, res) => {\n const worlds = ctx.get('dshbWorlds') as WorldsLike | undefined\n const segs = pathSegments(req)\n const action = segs[3]\n\n if (req.method === 'POST' && action === 'bind') {\n const body = await readBody(req)\n if (!body || typeof body.nodeId !== 'string' || typeof body.remotePath !== 'string') {\n sendJson(res, 400, { ok: false, error: '需要 nodeId 与 remotePath' })\n return\n }\n const node = registry.get(body.nodeId)\n if (!node) {\n sendJson(res, 404, { ok: false, error: 'node not found' })\n return\n }\n const remotePath = String(body.remotePath)\n if (node.type === 'local-host') {\n sendJson(res, 200, { ok: true, mirrorPath: remotePath })\n return\n }\n const slug = slugify(String(body.name ?? remotePath.split('/').filter(Boolean).pop() ?? 'workspace'))\n const mirrorPath = bindings.mirrorRoot(node.id, slug)\n try {\n const isDocker = node.type === 'local-docker' || node.type === 'remote-docker'\n if (isDocker) {\n const dockerWorlds = ctx.get('dshbDockerWorldsGeneric') as WorldsLike | undefined\n if (!dockerWorlds) {\n sendJson(res, 503, { ok: false, error: 'Docker 执行世界不可用(dshb-exec-docker 未加载)' })\n return\n }\n const world = await dockerWorlds.ensure(node.id)\n if (!world) {\n sendJson(res, 503, { ok: false, error: '容器未就绪,请先拉起容器' })\n return\n }\n await world.ensureDir(remotePath)\n } else {\n if (!worlds) {\n sendJson(res, 503, { ok: false, error: '远程执行世界不可用' })\n return\n }\n const world = await worlds.ensure(node.id)\n if (!world) {\n sendJson(res, 503, { ok: false, error: '节点执行世界未就绪' })\n return\n }\n await world.ensureDir(remotePath)\n }\n mkdirSync(mirrorPath, { recursive: true })\n bindings.add({ mirrorPath, nodeId: node.id, remotePath })\n sendJson(res, 201, { ok: true, mirrorPath })\n } catch (err) {\n sendJson(res, 502, { ok: false, error: err instanceof Error ? err.message : String(err) })\n }\n return\n }\n\n if (req.method === 'GET' && action === undefined) {\n sendJson(res, 200, { ok: true, bindings: bindings.list() })\n return\n }\n\n sendJson(res, 404, { ok: false, error: 'not found' })\n },\n }),\n )\n}\n\ninterface RemoteWorldLike {\n ensure(nodeId: string): Promise<{\n fs: {\n stat(path: string): Promise<{ isFile?: boolean; isDirectory?: boolean } | undefined>\n readBytes(path: string, signal?: AbortSignal, maxBytes?: number): Promise<Uint8Array>\n }\n } | undefined>\n}\n\nfunction resolveRemoteWorld(ctx: Context, node: { type: string }): RemoteWorldLike | undefined {\n if (node.type === 'local-docker' || node.type === 'remote-docker') {\n return ctx.get('dshbDockerWorldsGeneric') as RemoteWorldLike | undefined\n }\n if (node.type === 'remote-ssh') {\n return ctx.get('dshbWorlds') as RemoteWorldLike | undefined\n }\n return undefined\n}\n\nconst REMOTE_MAX_BYTES = 512 * 1024 * 1024\n\nexport function registerRemoteDownloadRoutes(ctx: Context, registry: NodeRegistry, bindings: WorkspaceBindingsStore): void {\n const webServer = ctx.webServer\n\n ctx.effect(() =>\n webServer.register({\n kind: 'prefix',\n path: '/api/dshb/remote',\n handler: async (req, res) => {\n const segs = pathSegments(req)\n const action = segs[3]\n if (req.method !== 'GET' || (action !== 'bound' && action !== 'file')) {\n sendJson(res, 404, { ok: false, error: 'not found' })\n return\n }\n const path = new URL(req.url ?? '/', 'http://x').searchParams.get('path') ?? ''\n if (!path) {\n sendJson(res, 400, { ok: false, error: '需要 path' })\n return\n }\n const hit = bindings.resolve(path)\n if (!hit) {\n sendJson(res, action === 'bound' ? 200 : 404, { ok: true, bound: false })\n return\n }\n const node = registry.get(hit.nodeId)\n if (!node) {\n sendJson(res, 502, { ok: false, bound: true, error: 'node not found' })\n return\n }\n const worlds = resolveRemoteWorld(ctx, node)\n if (!worlds) {\n sendJson(res, 502, { ok: false, bound: true, error: '远程执行世界不可用' })\n return\n }\n try {\n const world = await worlds.ensure(node.id)\n if (!world) {\n sendJson(res, 502, { ok: false, bound: true, error: '节点执行世界未就绪' })\n return\n }\n const info = await world.fs.stat(hit.remotePath)\n const isFile = info?.isFile ?? false\n if (action === 'bound') {\n sendJson(res, 200, { ok: true, bound: true, nodeId: node.id, remotePath: hit.remotePath, kind: isFile ? 'file' : 'dir' })\n return\n }\n if (!isFile) {\n sendJson(res, 404, { ok: false, bound: true, error: '目标不是文件' })\n return\n }\n const data = await world.fs.readBytes(hit.remotePath, undefined, REMOTE_MAX_BYTES)\n const name = basename(hit.remotePath) || 'download'\n const ext = extname(name).toLowerCase()\n const mime: Record<string, string> = {\n '.txt': 'text/plain', '.md': 'text/markdown', '.json': 'application/json', '.html': 'text/html', '.htm': 'text/html',\n '.css': 'text/css', '.js': 'text/javascript', '.mjs': 'text/javascript', '.svg': 'image/svg+xml', '.png': 'image/png',\n '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.webp': 'image/webp', '.pdf': 'application/pdf',\n '.zip': 'application/zip', '.gz': 'application/gzip', '.tar': 'application/x-tar', '.csv': 'text/csv', '.yaml': 'text/yaml', '.yml': 'text/yaml',\n }\n let contentType = mime[ext] ?? 'application/octet-stream'\n let body = Buffer.from(data)\n const isTextual = contentType.startsWith('text/') || contentType === 'application/json' || contentType === 'application/javascript'\n if (isTextual) {\n contentType += '; charset=utf-8'\n const bom = Buffer.from([0xef, 0xbb, 0xbf])\n if (!body.subarray(0, 3).equals(bom)) body = Buffer.concat([bom, body])\n }\n const asciiFallback = name.replace(/[^\\x20-\\x7e]/g, '_')\n res.writeHead(200, {\n 'content-type': contentType,\n 'content-disposition': `attachment; filename=\"${asciiFallback}\"; filename*=UTF-8''${encodeURIComponent(name).replace(/['()]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`)}`,\n 'cache-control': 'no-store',\n })\n res.end(body)\n } catch (err) {\n sendJson(res, 502, { ok: false, bound: true, error: err instanceof Error ? err.message : String(err) })\n }\n },\n }),\n )\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { HostCommandRunner } from "./docker-backend.js";
|
|
2
|
+
//#region src/exec-docker/daemon-runner.d.ts
|
|
3
|
+
interface DaemonRequest {
|
|
4
|
+
(method: string, path: string, body?: string): Promise<{
|
|
5
|
+
status: number;
|
|
6
|
+
body: Buffer;
|
|
7
|
+
}>;
|
|
8
|
+
}
|
|
9
|
+
interface DaemonTarget {
|
|
10
|
+
socketPath?: string;
|
|
11
|
+
host?: string;
|
|
12
|
+
port?: number;
|
|
13
|
+
tls?: boolean;
|
|
14
|
+
}
|
|
15
|
+
declare function parseDaemonHost(env?: NodeJS.ProcessEnv): DaemonTarget;
|
|
16
|
+
declare function buildEngineRunner(request: DaemonRequest): HostCommandRunner;
|
|
17
|
+
declare function dockerDaemonRunner(env?: NodeJS.ProcessEnv): HostCommandRunner;
|
|
18
|
+
declare function bytesOfForTest(value: string): number;
|
|
19
|
+
//#endregion
|
|
20
|
+
export { DaemonRequest, buildEngineRunner, bytesOfForTest, dockerDaemonRunner, parseDaemonHost };
|
|
21
|
+
//# sourceMappingURL=daemon-runner.d.ts.map
|