|
|
@@ -1,21 +1,68 @@
|
|
|
import { spawn } from "node:child_process"
|
|
|
|
|
|
export type ServerConfig = {
|
|
|
- host?: string
|
|
|
+ hostname?: string
|
|
|
port?: number
|
|
|
+ signal?: AbortSignal
|
|
|
+ timeout?: number
|
|
|
}
|
|
|
|
|
|
export async function createOpencodeServer(config?: ServerConfig) {
|
|
|
config = Object.assign(
|
|
|
{
|
|
|
- host: "127.0.0.1",
|
|
|
+ hostname: "127.0.0.1",
|
|
|
port: 4096,
|
|
|
+ timeout: 5000,
|
|
|
},
|
|
|
config ?? {},
|
|
|
)
|
|
|
|
|
|
- const proc = spawn(`opencode`, [`serve`, `--host=${config.host}`, `--port=${config.port}`])
|
|
|
- const url = `http://${config.host}:${config.port}`
|
|
|
+ const proc = spawn(`opencode`, [`servel`, `--hostname=${config.hostname}`, `--port=${config.port}`], {
|
|
|
+ signal: config.signal,
|
|
|
+ })
|
|
|
+
|
|
|
+ const url = await new Promise<string>((resolve, reject) => {
|
|
|
+ const id = setTimeout(() => {
|
|
|
+ reject(new Error(`Timeout waiting for server to start after ${config.timeout}ms`))
|
|
|
+ }, config.timeout)
|
|
|
+ let output = ""
|
|
|
+ proc.stdout?.on("data", (chunk) => {
|
|
|
+ output += chunk.toString()
|
|
|
+ const lines = output.split("\n")
|
|
|
+ for (const line of lines) {
|
|
|
+ if (line.startsWith("opencode server listening")) {
|
|
|
+ const match = line.match(/on\s+(https?:\/\/[^\s]+)/)
|
|
|
+ if (!match) {
|
|
|
+ throw new Error(`Failed to parse server url from output: ${line}`)
|
|
|
+ }
|
|
|
+ clearTimeout(id)
|
|
|
+ resolve(match[1])
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ proc.stderr?.on("data", (chunk) => {
|
|
|
+ output += chunk.toString()
|
|
|
+ })
|
|
|
+ proc.on("exit", (code) => {
|
|
|
+ clearTimeout(id)
|
|
|
+ let msg = `Server exited with code ${code}`
|
|
|
+ if (output.trim()) {
|
|
|
+ msg += `\nServer output: ${output}`
|
|
|
+ }
|
|
|
+ reject(new Error(msg))
|
|
|
+ })
|
|
|
+ proc.on("error", (error) => {
|
|
|
+ clearTimeout(id)
|
|
|
+ reject(error)
|
|
|
+ })
|
|
|
+ if (config.signal) {
|
|
|
+ config.signal.addEventListener("abort", () => {
|
|
|
+ clearTimeout(id)
|
|
|
+ reject(new Error("Aborted"))
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
|
|
|
return {
|
|
|
url,
|