|
|
@@ -0,0 +1,78 @@
|
|
|
+import { afterEach, expect, test } from "bun:test"
|
|
|
+import { existsSync } from "node:fs"
|
|
|
+import path from "node:path"
|
|
|
+import { pathToFileURL } from "node:url"
|
|
|
+import { bootstrap as cliBootstrap } from "../../src/cli/bootstrap"
|
|
|
+import { WithInstance } from "../../src/project/with-instance"
|
|
|
+import { InstanceRuntime } from "../../src/project/instance-runtime"
|
|
|
+import { disposeAllInstances, tmpdir } from "../fixture/fixture"
|
|
|
+
|
|
|
+// InstanceBootstrap must run before any code touches the instance —
|
|
|
+// originally tracked by PRs #25389 and #25449, now a permanent
|
|
|
+// invariant. The plugin config hook writes a marker file; the test
|
|
|
+// bodies deliberately avoid Plugin/config directly. The marker only
|
|
|
+// appears if InstanceBootstrap ran at the instance boundary.
|
|
|
+//
|
|
|
+// The Hono variant of this check lived alongside these tests and is
|
|
|
+// going away with the Hono backend. The boundaries below are backend-
|
|
|
+// agnostic and stay.
|
|
|
+
|
|
|
+afterEach(async () => {
|
|
|
+ await disposeAllInstances()
|
|
|
+})
|
|
|
+
|
|
|
+async function bootstrapFixture() {
|
|
|
+ return tmpdir({
|
|
|
+ init: async (dir) => {
|
|
|
+ const marker = path.join(dir, "config-hook-fired")
|
|
|
+ const pluginFile = path.join(dir, "plugin.ts")
|
|
|
+ await Bun.write(
|
|
|
+ pluginFile,
|
|
|
+ [
|
|
|
+ `const MARKER = ${JSON.stringify(marker)}`,
|
|
|
+ "export default async () => ({",
|
|
|
+ " config: async () => {",
|
|
|
+ ' await Bun.write(MARKER, "ran")',
|
|
|
+ " },",
|
|
|
+ "})",
|
|
|
+ "",
|
|
|
+ ].join("\n"),
|
|
|
+ )
|
|
|
+ await Bun.write(
|
|
|
+ path.join(dir, "opencode.json"),
|
|
|
+ JSON.stringify({
|
|
|
+ $schema: "https://opencode.ai/config.json",
|
|
|
+ plugin: [pathToFileURL(pluginFile).href],
|
|
|
+ }),
|
|
|
+ )
|
|
|
+ return marker
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+test("WithInstance.provide runs InstanceBootstrap before fn", async () => {
|
|
|
+ await using tmp = await bootstrapFixture()
|
|
|
+
|
|
|
+ await WithInstance.provide({
|
|
|
+ directory: tmp.path,
|
|
|
+ fn: async () => "ok",
|
|
|
+ })
|
|
|
+
|
|
|
+ expect(existsSync(tmp.extra)).toBe(true)
|
|
|
+})
|
|
|
+
|
|
|
+test("CLI bootstrap runs InstanceBootstrap before callback", async () => {
|
|
|
+ await using tmp = await bootstrapFixture()
|
|
|
+
|
|
|
+ await cliBootstrap(tmp.path, async () => "ok")
|
|
|
+
|
|
|
+ expect(existsSync(tmp.extra)).toBe(true)
|
|
|
+})
|
|
|
+
|
|
|
+test("InstanceRuntime.reloadInstance runs InstanceBootstrap", async () => {
|
|
|
+ await using tmp = await bootstrapFixture()
|
|
|
+
|
|
|
+ await InstanceRuntime.reloadInstance({ directory: tmp.path })
|
|
|
+
|
|
|
+ expect(existsSync(tmp.extra)).toBe(true)
|
|
|
+})
|