fix(cli): reuse server for noninteractive commands

This commit is contained in:
Dax Raad 2026-08-06 12:56:37 -04:00
parent 51cef27579
commit 0f27ee7b4c
5 changed files with 25 additions and 5 deletions

View file

@ -22,6 +22,7 @@ export default Runtime.handler(Commands, (input) =>
const server = yield* ServerConnection.resolve({
server: Option.getOrUndefined(input.server),
standalone: input.standalone,
mismatch: "replace",
onStart: (reason, previousVersion) => {
if (reason === "version-mismatch" && preflight.begin(previousVersion)) return
process.stderr.write(

View file

@ -10,7 +10,11 @@ export default Runtime.handler(Commands.commands.mini, (input) =>
const { runMini, validateMiniTerminal } = yield* Effect.promise(() => import("../../mini"))
yield* Effect.promise(async () => validateMiniTerminal())
const serverURL = Option.getOrUndefined(input.server)
const server = yield* ServerConnection.resolve({ server: serverURL, standalone: input.standalone })
const server = yield* ServerConnection.resolve({
server: serverURL,
standalone: input.standalone,
mismatch: "replace",
})
const config = yield* Config.Service
const resolved = resolve(yield* config.get(), { terminalSuspend: process.platform !== "win32" })
const fileSystem = yield* FileSystem.FileSystem

View file

@ -42,9 +42,10 @@ export const resolve = Effect.fn("cli.server-connection.resolve")(function* (arg
return { endpoint: yield* Standalone.start() } satisfies Resolved
}
const options = yield* ServiceConfig.options()
const mismatch = args.mismatch ?? "ignore"
const options = yield* ServiceConfig.options({ checkVersion: mismatch !== "ignore" })
return {
endpoint: yield* resolveManaged({ ...options, onStart: args.onStart }, args.mismatch ?? "replace"),
endpoint: yield* resolveManaged({ ...options, onStart: args.onStart }, mismatch),
service: managedService(options),
} satisfies Resolved
})

View file

@ -98,12 +98,12 @@ const paths = Effect.gen(function* () {
}
})
export const options = Effect.fnUntraced(function* () {
export const options = Effect.fnUntraced(function* (input: { readonly checkVersion?: boolean } = {}) {
const { file, legacyRegistrationFiles } = yield* paths
yield* Effect.forEach(legacyRegistrationFiles, (legacy) => migrateRegistration(legacy, file))
return {
file,
version: OPENCODE_VERSION,
version: input.checkVersion ? OPENCODE_VERSION : undefined,
command: [...selfCommand(), "serve", "--service"],
}
})

View file

@ -55,3 +55,17 @@ test("resolution groups Effect-native lifecycle operations only for the managed
await fs.rm(root, { recursive: true, force: true })
}
})
test("service options only require a matching version when requested", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-service-options-"))
const layer = Global.layerWith({ config: path.join(root, "config"), state: path.join(root, "state") })
const runPromise = <A, E>(effect: Effect.Effect<A, E, Global.Service | FileSystem.FileSystem | Scope.Scope>) =>
Effect.runPromise(effect.pipe(Effect.provide(layer), Effect.provide(NodeFileSystem.layer), Effect.scoped))
try {
expect((await runPromise(ServiceConfig.options())).version).toBeUndefined()
expect((await runPromise(ServiceConfig.options({ checkVersion: true }))).version).toBe(OPENCODE_VERSION)
} finally {
await fs.rm(root, { recursive: true, force: true })
}
})