import fs from "node:fs/promises" import net from "node:net" import os from "node:os" import path from "node:path" async function freePort() { return await new Promise((resolve, reject) => { const server = net.createServer() server.once("error", reject) server.listen(0, () => { const address = server.address() if (!address || typeof address === "string") { server.close(() => reject(new Error("Failed to acquire a free port"))) return } server.close((err) => { if (err) { reject(err) return } resolve(address.port) }) }) }) } async function waitForHealth(url: string) { const timeout = Date.now() + 120_000 const errors: string[] = [] while (Date.now() < timeout) { const result = await fetch(url) .then((r) => ({ ok: r.ok, error: undefined })) .catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), })) if (result.ok) return if (result.error) errors.push(result.error) await new Promise((r) => setTimeout(r, 250)) } const last = errors.length ? ` (last error: ${errors[errors.length - 1]})` : "" throw new Error(`Timed out waiting for server health: ${url}${last}`) } const appDir = process.cwd() const repoDir = path.resolve(appDir, "../..") const opencodeDir = path.join(repoDir, "packages", "opencode") const extraArgs = (() => { const args = process.argv.slice(2) if (args[0] === "--") return args.slice(1) return args })() const [serverPort, webPort] = await Promise.all([freePort(), freePort()]) const sandbox = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-e2e-")) const serverEnv = { ...process.env, OPENCODE_DISABLE_SHARE: process.env.OPENCODE_DISABLE_SHARE ?? "true", OPENCODE_DISABLE_LSP_DOWNLOAD: "true", OPENCODE_DISABLE_DEFAULT_PLUGINS: "true", OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: "true", OPENCODE_TEST_HOME: path.join(sandbox, "home"), XDG_DATA_HOME: path.join(sandbox, "share"), XDG_CACHE_HOME: path.join(sandbox, "cache"), XDG_CONFIG_HOME: path.join(sandbox, "config"), XDG_STATE_HOME: path.join(sandbox, "state"), OPENCODE_E2E_PROJECT_DIR: repoDir, OPENCODE_E2E_SESSION_TITLE: "E2E Session", OPENCODE_E2E_MESSAGE: "Seeded for UI e2e", OPENCODE_E2E_MODEL: "opencode/gpt-5-nano", OPENCODE_CLIENT: "app", } satisfies Record const runnerEnv = { ...serverEnv, PLAYWRIGHT_SERVER_HOST: "127.0.0.1", PLAYWRIGHT_SERVER_PORT: String(serverPort), VITE_OPENCODE_SERVER_HOST: "127.0.0.1", VITE_OPENCODE_SERVER_PORT: String(serverPort), PLAYWRIGHT_PORT: String(webPort), } satisfies Record const seed = Bun.spawn(["bun", "script/seed-e2e.ts"], { cwd: opencodeDir, env: serverEnv, stdout: "inherit", stderr: "inherit", }) const seedExit = await seed.exited if (seedExit !== 0) { process.exit(seedExit) } Object.assign(process.env, serverEnv) process.env.AGENT = "1" process.env.OPENCODE = "1" const log = await import("../../opencode/src/util/log") const install = await import("../../opencode/src/installation") await log.Log.init({ print: true, dev: install.Installation.isLocal(), level: "WARN", }) const servermod = await import("../../opencode/src/server/server") const inst = await import("../../opencode/src/project/instance") const server = servermod.Server.listen({ port: serverPort, hostname: "127.0.0.1" }) console.log(`opencode server listening on http://127.0.0.1:${serverPort}`) const result = await (async () => { try { await waitForHealth(`http://127.0.0.1:${serverPort}/global/health`) const runner = Bun.spawn(["bun", "test:e2e", ...extraArgs], { cwd: appDir, env: runnerEnv, stdout: "inherit", stderr: "inherit", }) return { code: await runner.exited } } catch (error) { return { error } } finally { await inst.Instance.disposeAll() await server.stop() } })() if ("error" in result) { console.error(result.error) process.exit(1) } process.exit(result.code)