#!/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) }