11import { afterEach , expect , test } from "bun:test"
2+ import { Hono } from "hono"
23import { existsSync } from "node:fs"
34import path from "node:path"
45import { pathToFileURL } from "node:url"
6+ import { bootstrap as cliBootstrap } from "../../src/cli/bootstrap"
57import { Instance } from "../../src/project/instance"
8+ import { InstanceRuntime } from "../../src/project/instance-runtime"
9+ import { InstanceMiddleware } from "../../src/server/routes/instance/middleware"
610import { disposeAllInstances , tmpdir } from "../fixture/fixture"
711
8- // Instance.provide must run bootstrap before fn. The plugin config hook writes
9- // a marker file, and fn deliberately avoids touching Plugin or config so the
10- // marker only exists if bootstrap ran at the instance boundary.
12+ // These regressions cover the legacy instance-loading paths fixed by PRs
13+ // #25389 and #25449. The plugin config hook writes a marker file, and the test
14+ // bodies deliberately avoid touching Plugin or config directly. The marker only
15+ // exists if InstanceBootstrap ran at the instance boundary.
1116
1217afterEach ( async ( ) => {
1318 await disposeAllInstances ( )
1419} )
1520
16- test ( "Instance.provide runs InstanceBootstrap before fn (boundary invariant)" , async ( ) => {
17- await using tmp = await tmpdir ( {
21+ async function bootstrapFixture ( ) {
22+ return tmpdir ( {
1823 init : async ( dir ) => {
1924 const marker = path . join ( dir , "config-hook-fired" )
2025 const pluginFile = path . join ( dir , "plugin.ts" )
@@ -40,14 +45,41 @@ test("Instance.provide runs InstanceBootstrap before fn (boundary invariant)", a
4045 return marker
4146 } ,
4247 } )
48+ }
49+
50+ test ( "Instance.provide runs InstanceBootstrap before fn (boundary invariant)" , async ( ) => {
51+ await using tmp = await bootstrapFixture ( )
4352
44- // The body of `fn` deliberately does not yield Plugin, read config, or
45- // touch any service that would force Plugin.state to materialize on
46- // demand. The only way the marker gets written is if bootstrap ran.
4753 await Instance . provide ( {
4854 directory : tmp . path ,
4955 fn : async ( ) => "ok" ,
5056 } )
5157
5258 expect ( existsSync ( tmp . extra ) ) . toBe ( true )
5359} )
60+
61+ test ( "CLI bootstrap runs InstanceBootstrap before callback" , async ( ) => {
62+ await using tmp = await bootstrapFixture ( )
63+
64+ await cliBootstrap ( tmp . path , async ( ) => "ok" )
65+
66+ expect ( existsSync ( tmp . extra ) ) . toBe ( true )
67+ } )
68+
69+ test ( "legacy Hono instance middleware runs InstanceBootstrap before next handler" , async ( ) => {
70+ await using tmp = await bootstrapFixture ( )
71+ const app = new Hono ( ) . use ( InstanceMiddleware ( ) ) . get ( "/probe" , ( c ) => c . text ( "ok" ) )
72+
73+ const response = await app . request ( "/probe" , { headers : { "x-opencode-directory" : tmp . path } } )
74+
75+ expect ( response . status ) . toBe ( 200 )
76+ expect ( existsSync ( tmp . extra ) ) . toBe ( true )
77+ } )
78+
79+ test ( "InstanceRuntime.reloadInstance runs InstanceBootstrap" , async ( ) => {
80+ await using tmp = await bootstrapFixture ( )
81+
82+ await InstanceRuntime . reloadInstance ( { directory : tmp . path } )
83+
84+ expect ( existsSync ( tmp . extra ) ) . toBe ( true )
85+ } )
0 commit comments