|
|
@@ -1,5 +1,7 @@
|
|
|
import { afterEach, describe, expect, test } from "bun:test"
|
|
|
import net from "node:net"
|
|
|
+import path from "node:path"
|
|
|
+import { pathToFileURL } from "node:url"
|
|
|
import { Flag } from "@opencode-ai/core/flag/flag"
|
|
|
import { Server } from "../../src/server/server"
|
|
|
import { PtyPaths } from "../../src/server/routes/instance/httpapi/groups/pty"
|
|
|
@@ -300,6 +302,57 @@ describe("HttpApi Server.listen", () => {
|
|
|
expect(output).not.toContain("Sent HTTP response")
|
|
|
})
|
|
|
|
|
|
+ test("plugin client requests reuse the listening server instance", async () => {
|
|
|
+ await using tmp = await tmpdir({
|
|
|
+ init: async (directory) => {
|
|
|
+ const plugin = path.join(directory, "plugin.ts")
|
|
|
+ const initialized = path.join(directory, "initialized.txt")
|
|
|
+ const completed = path.join(directory, "completed.txt")
|
|
|
+ await Bun.write(
|
|
|
+ plugin,
|
|
|
+ [
|
|
|
+ "export default async function plugin(input) {",
|
|
|
+ ` await Bun.write(${JSON.stringify(initialized)}, (await Bun.file(${JSON.stringify(initialized)}).text().catch(() => "")) + "initialized\\n")`,
|
|
|
+ " setTimeout(async () => {",
|
|
|
+ " await input.client.config.get()",
|
|
|
+ ` await Bun.write(${JSON.stringify(completed)}, "completed")`,
|
|
|
+ " }, 50)",
|
|
|
+ " return {}",
|
|
|
+ "}",
|
|
|
+ "",
|
|
|
+ ].join("\n"),
|
|
|
+ )
|
|
|
+ await Bun.write(
|
|
|
+ path.join(directory, "opencode.json"),
|
|
|
+ JSON.stringify({ formatter: false, lsp: false, plugin: [pathToFileURL(plugin).href] }),
|
|
|
+ )
|
|
|
+ return { initialized, completed }
|
|
|
+ },
|
|
|
+ })
|
|
|
+ const previous = process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS
|
|
|
+ process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS = "1"
|
|
|
+ let listener: Awaited<ReturnType<typeof startListener>> | undefined
|
|
|
+ try {
|
|
|
+ listener = await startListener()
|
|
|
+ const response = await fetch(new URL("/config", listener.url), {
|
|
|
+ headers: { authorization: authorization(), "x-opencode-directory": tmp.path },
|
|
|
+ })
|
|
|
+ expect(response.status).toBe(200)
|
|
|
+ await withTimeout(
|
|
|
+ (async () => {
|
|
|
+ while (!(await Bun.file(tmp.extra.completed).exists())) await Bun.sleep(10)
|
|
|
+ })(),
|
|
|
+ 5_000,
|
|
|
+ "timed out waiting for plugin client request",
|
|
|
+ )
|
|
|
+ expect(await Bun.file(tmp.extra.initialized).text()).toBe("initialized\n")
|
|
|
+ } finally {
|
|
|
+ if (listener) await stop(listener, "timed out cleaning up plugin client listener").catch(() => undefined)
|
|
|
+ if (previous === undefined) delete process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS
|
|
|
+ else process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS = previous
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
test("port 0 prefers 4096 when free", async () => {
|
|
|
if (!(await isPortFree(4096))) return
|
|
|
const listener = await startListener()
|