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