mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-23 12:54:42 +00:00
106 lines
2.7 KiB
Text
Executable file
106 lines
2.7 KiB
Text
Executable file
#!/usr/bin/env bun
|
|
|
|
// This script runs a separate OpenCode server to be used as a remote
|
|
// workspace, simulating a remote environment but all local to make
|
|
// debugger easier
|
|
//
|
|
// *Important*: make sure you add the debug workspace plugin first.
|
|
// In `.opencode/opencode.jsonc` in the root of this project add:
|
|
//
|
|
// "plugin": ["../packages/opencode/src/control-plane/dev/debug-workspace-plugin.ts"]
|
|
//
|
|
// Afterwards, run `./packages/opencode/script/run-workspace-server`
|
|
|
|
import { stat } from "node:fs/promises"
|
|
import { setTimeout as sleep } from "node:timers/promises"
|
|
|
|
const DEV_DATA_FILE = "/tmp/opencode-workspace-dev-data.json"
|
|
const RESTART_POLL_INTERVAL = 250
|
|
|
|
async function readData() {
|
|
return await Bun.file(DEV_DATA_FILE).json()
|
|
}
|
|
|
|
async function readDataMtime() {
|
|
return await stat(DEV_DATA_FILE)
|
|
.then((info) => info.mtimeMs)
|
|
.catch((error) => {
|
|
if (typeof error === "object" && error && "code" in error && error.code === "ENOENT") {
|
|
return undefined
|
|
}
|
|
|
|
throw error
|
|
})
|
|
}
|
|
|
|
async function readSnapshot() {
|
|
while (true) {
|
|
try {
|
|
const before = await readDataMtime()
|
|
if (before === undefined) {
|
|
await sleep(RESTART_POLL_INTERVAL)
|
|
continue
|
|
}
|
|
|
|
const data = await readData()
|
|
const after = await readDataMtime()
|
|
|
|
if (before === after) {
|
|
return { data, mtime: after }
|
|
}
|
|
} catch (error) {
|
|
if (typeof error === "object" && error && "code" in error && error.code === "ENOENT") {
|
|
await sleep(RESTART_POLL_INTERVAL)
|
|
continue
|
|
}
|
|
|
|
throw error
|
|
}
|
|
}
|
|
}
|
|
|
|
function startDevServer(data: any) {
|
|
const env = Object.fromEntries(Object.entries(data.env ?? {}).filter(([, value]) => value !== undefined))
|
|
|
|
return Bun.spawn(["bun", "run", "dev", "serve", "--port", String(data.port), "--print-logs"], {
|
|
env: {
|
|
...process.env,
|
|
...env,
|
|
XDG_DATA_HOME: "/tmp/data",
|
|
},
|
|
stdin: "inherit",
|
|
stdout: "inherit",
|
|
stderr: "inherit",
|
|
})
|
|
}
|
|
|
|
async function waitForRestartSignal(mtime: number, signal: AbortSignal) {
|
|
while (!signal.aborted) {
|
|
await sleep(RESTART_POLL_INTERVAL)
|
|
if (signal.aborted) return false
|
|
if ((await readDataMtime()) !== mtime) return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
while (true) {
|
|
const { data, mtime } = await readSnapshot()
|
|
const proc = startDevServer(data)
|
|
const restartAbort = new AbortController()
|
|
|
|
const result = await Promise.race([
|
|
proc.exited.then((code) => ({ type: "exit" as const, code })),
|
|
waitForRestartSignal(mtime, restartAbort.signal).then((restart) => ({ type: "restart" as const, restart })),
|
|
])
|
|
|
|
restartAbort.abort()
|
|
|
|
if (result.type === "restart" && result.restart) {
|
|
proc.kill()
|
|
await proc.exited
|
|
continue
|
|
}
|
|
|
|
process.exit(result.code)
|
|
}
|