-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.ts
More file actions
111 lines (95 loc) · 3.23 KB
/
server.ts
File metadata and controls
111 lines (95 loc) · 3.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import { readFileSync, existsSync, statSync } from "fs"
import { join, extname } from "path"
import { spawn } from "child_process"
const PORT = 5555
const DIST_DIR = join(import.meta.dir, "app", "dist")
const sessions = new Map<unknown, string>() // ws -> cwd
const MIME_TYPES: Record<string, string> = {
".html": "text/html",
".js": "application/javascript",
".css": "text/css",
".json": "application/json",
".png": "image/png",
".svg": "image/svg+xml",
".ico": "image/x-icon",
".woff": "font/woff",
".woff2": "font/woff2",
}
function runCommand(command: string, cwd: string): { output: string; cwd: string } {
const trimmed = command.trim()
// Handle cd specially
if (trimmed === "cd" || trimmed.startsWith("cd ")) {
const parts = trimmed.split(/\s+/)
const target = parts[1] || Bun.env.HOME || "/"
const home = Bun.env.HOME || "/"
const expanded = target === "~" ? home : target.startsWith("~/") ? home + target.slice(1) : target
const resolved = expanded.startsWith("/")
? expanded
: join(cwd, expanded)
try {
const real = require("path").resolve(resolved)
if (statSync(real).isDirectory()) {
return { output: "", cwd: real }
}
} catch {}
return { output: `cd: no such file or directory: ${target}`, cwd }
}
try {
const result = Bun.spawnSync(["bash", "-c", trimmed], {
cwd,
timeout: 10_000,
})
let output = result.stdout.toString().trimEnd()
const stderr = result.stderr.toString().trimEnd()
if (stderr) output += (output ? "\n" : "") + stderr
return { output, cwd }
} catch (e) {
return { output: `(error: ${e})`, cwd }
}
}
const server = Bun.serve({
port: PORT,
fetch(req) {
const url = new URL(req.url)
// Upgrade WebSocket requests
if (url.pathname === "/ws") {
if (server.upgrade(req)) return undefined as any
return new Response("WebSocket upgrade failed", { status: 400 })
}
// Serve static files from dist
let filePath = join(DIST_DIR, url.pathname === "/" ? "index.html" : url.pathname)
// SPA fallback: if file doesn't exist and it's not a file extension, serve index.html
if (!existsSync(filePath) || !statSync(filePath).isFile()) {
if (!extname(url.pathname)) {
filePath = join(DIST_DIR, "index.html")
}
}
if (existsSync(filePath) && statSync(filePath).isFile()) {
const ext = extname(filePath)
const mime = MIME_TYPES[ext] || "application/octet-stream"
return new Response(readFileSync(filePath), {
headers: { "Content-Type": mime },
})
}
return new Response("Not found", { status: 404 })
},
websocket: {
open(ws) {
const home = Bun.env.HOME || "/"
sessions.set(ws, home)
ws.send(JSON.stringify({ output: "", cwd: home, init: true }))
},
message(ws, message) {
const data = JSON.parse(message.toString())
const command = data.command || ""
const cwd = sessions.get(ws) || "/"
const result = runCommand(command, cwd)
sessions.set(ws, result.cwd)
ws.send(JSON.stringify({ output: result.output, cwd: result.cwd }))
},
close(ws) {
sessions.delete(ws)
},
},
})
console.log(`Claudzooks running at http://localhost:${PORT}`)