mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-06 08:21:50 +00:00
feat(cli): add instance: false opt-out to effectCmd (#25507)
This commit is contained in:
parent
e709dc34fb
commit
e98c291866
2 changed files with 44 additions and 17 deletions
|
|
@ -1,21 +1,24 @@
|
|||
import { Effect } from "effect"
|
||||
import { Server } from "../../server/server"
|
||||
import { cmd } from "./cmd"
|
||||
import { effectCmd } from "../effect-cmd"
|
||||
import { withNetworkOptions, resolveNetworkOptions } from "../network"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
|
||||
export const ServeCommand = cmd({
|
||||
export const ServeCommand = effectCmd({
|
||||
command: "serve",
|
||||
builder: (yargs) => withNetworkOptions(yargs),
|
||||
describe: "starts a headless opencode server",
|
||||
handler: async (args) => {
|
||||
// Server loads instances per-request via x-opencode-directory header — no
|
||||
// need for an ambient project InstanceContext at startup.
|
||||
instance: false,
|
||||
handler: Effect.fn("Cli.serve")(function* (args) {
|
||||
if (!Flag.OPENCODE_SERVER_PASSWORD) {
|
||||
console.log("Warning: OPENCODE_SERVER_PASSWORD is not set; server is unsecured.")
|
||||
}
|
||||
const opts = await resolveNetworkOptions(args)
|
||||
const server = await Server.listen(opts)
|
||||
const opts = yield* Effect.promise(() => resolveNetworkOptions(args))
|
||||
const server = yield* Effect.promise(() => Server.listen(opts))
|
||||
console.log(`opencode server listening on http://${server.hostname}:${server.port}`)
|
||||
|
||||
await new Promise(() => {})
|
||||
await server.stop()
|
||||
},
|
||||
yield* Effect.never
|
||||
}),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -18,6 +18,34 @@ export class CliError extends Schema.TaggedErrorClass<CliError>()("CliError", {
|
|||
|
||||
export const fail = (message: string, exitCode = 1) => Effect.fail(new CliError({ message, exitCode }))
|
||||
|
||||
interface EffectCmdOpts<Args, A> {
|
||||
command: string | readonly string[]
|
||||
aliases?: string | readonly string[]
|
||||
describe: string | false
|
||||
builder?: (yargs: Argv) => Argv<Args>
|
||||
/**
|
||||
* Whether the command needs a project InstanceContext. Defaults to true.
|
||||
*
|
||||
* `true` (default): wraps the handler in `InstanceStore.Service.provide({directory})`
|
||||
* so `InstanceRef` resolves to a loaded `InstanceContext`. Auto-disposes via
|
||||
* `Effect.ensuring(store.dispose(ctx))` on every Exit (matches the legacy
|
||||
* `bootstrap()` finally-disposal). Runs InstanceBootstrap (config + plugin
|
||||
* init + LSP/File/etc forks) eagerly.
|
||||
*
|
||||
* `false`: skip the instance entirely. Saves the InstanceBootstrap work and
|
||||
* suppresses the `server.instance.disposed` IPC event. The handler runs
|
||||
* directly under AppRuntime — it can yield any `AppServices` but must not
|
||||
* yield `InstanceRef` (it'd be undefined, causing a defect).
|
||||
*
|
||||
* Use `false` for commands that don't read project state (e.g. `models`,
|
||||
* `serve`, `web`, `account`, `db`, `upgrade`).
|
||||
*/
|
||||
instance?: boolean
|
||||
/** Defaults to process.cwd(). Override for commands that take a directory positional. */
|
||||
directory?: (args: Args) => string
|
||||
handler: (args: Args) => Effect.Effect<A, CliError, AppServices | InstanceStore.Service>
|
||||
}
|
||||
|
||||
/**
|
||||
* Effect-native CLI command builder. Wraps yargs `cmd()` so the handler body is
|
||||
* an `Effect` with `InstanceRef` provided and any `AppServices` yieldable.
|
||||
|
|
@ -35,15 +63,7 @@ export const fail = (message: string, exitCode = 1) => Effect.fail(new CliError(
|
|||
* `effectCmd`, swapping the underlying `cmd()` factory for effect/cli's
|
||||
* `Command.make(...)` won't touch any handler bodies.
|
||||
*/
|
||||
export const effectCmd = <Args, A>(opts: {
|
||||
command: string | readonly string[]
|
||||
aliases?: string | readonly string[]
|
||||
describe: string | false
|
||||
builder?: (yargs: Argv) => Argv<Args>
|
||||
/** Defaults to process.cwd(). Override for commands that take a directory positional. */
|
||||
directory?: (args: Args) => string
|
||||
handler: (args: Args) => Effect.Effect<A, CliError, AppServices | InstanceStore.Service>
|
||||
}) =>
|
||||
export const effectCmd = <Args, A>(opts: EffectCmdOpts<Args, A>) =>
|
||||
cmd<{}, Args>({
|
||||
command: opts.command,
|
||||
aliases: opts.aliases,
|
||||
|
|
@ -52,6 +72,10 @@ export const effectCmd = <Args, A>(opts: {
|
|||
async handler(rawArgs) {
|
||||
// yargs typing wraps Args in ArgumentsCamelCase<WithDoubleDash<...>>; cast at the boundary.
|
||||
const args = rawArgs as unknown as Args
|
||||
if (opts.instance === false) {
|
||||
await AppRuntime.runPromise(opts.handler(args))
|
||||
return
|
||||
}
|
||||
const directory = opts.directory?.(args) ?? process.cwd()
|
||||
await AppRuntime.runPromise(
|
||||
InstanceStore.Service.use((store) =>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue