import { Effect, FileSystem, Scope, Stdio } from "effect" import { Command, GlobalFlag } from "effect/unstable/cli" import { PrintLogs } from "../commands/commands" import { Spec } from "./spec" import { Global } from "@opencode/util/global" import { Updater } from "../services/updater" import { Config } from "../config" import { Npm } from "@opencode/util/npm" import { prepareArguments } from "../args/environment.ts" export type Input = Value extends Spec.Node ? Input : Value extends Command.Command ? Input : never type RuntimeHandler = ( input: unknown, ) => Effect.Effect< void, unknown, FileSystem.FileSystem | Global.Service | Npm.Service | Updater.Service | Config.Service | Scope.Scope > type Loader = () => Promise<{ default: ( input: Input, ) => Effect.Effect< void, any, FileSystem.FileSystem | Global.Service | Npm.Service | Updater.Service | Config.Service | Scope.Scope > }> type ProvidedCommand = Command.Command< string, unknown, unknown, unknown, FileSystem.FileSystem | Global.Service | Npm.Service | Updater.Service | Config.Service | Scope.Scope > export type Handlers = keyof Node["commands"] extends never ? Loader : { readonly $?: Loader } & { readonly [Key in keyof Node["commands"]]: Handlers } interface LazyHandler { readonly spec: Command.Command.Any readonly load: () => Promise<{ default: RuntimeHandler }> } type RuntimeHandlers = | (() => Promise<{ default: RuntimeHandler }>) | { readonly $?: () => Promise<{ default: RuntimeHandler }> readonly [key: string]: RuntimeHandlers | (() => Promise<{ default: RuntimeHandler }>) | undefined } export function handler( _node: Node, run: (input: Input) => Effect.Effect, ) { return run } export function handlers(root: Root, handlers: Handlers) { const result: LazyHandler[] = [] function add(node: Spec.Any, value: RuntimeHandlers) { if (typeof value === "function") { result.push({ spec: node.spec, load: value as () => Promise<{ default: RuntimeHandler }> }) for (const alias of node.aliases) result.push({ spec: alias.spec, load: value as () => Promise<{ default: RuntimeHandler }> }) return } if (value.$) { result.push({ spec: node.spec, load: value.$ as () => Promise<{ default: RuntimeHandler }> }) for (const alias of node.aliases) result.push({ spec: alias.spec, load: value.$ as () => Promise<{ default: RuntimeHandler }> }) } for (const [name, child] of Object.entries(node.commands)) add(child, value[name] as RuntimeHandlers) } add(root, handlers as RuntimeHandlers) return result } export function run( commands: Spec.Any, handlers: ReadonlyArray, options: { readonly version: string readonly conflicts?: ReadonlyArray }, ) { const globals = [...GlobalFlag.BuiltIns.map((flag) => flag.flag), PrintLogs.flag] const command = provide(commands, handlers).pipe(Command.withGlobalFlags([PrintLogs])) return Stdio.Stdio.use(({ args }) => Effect.flatMap(args, (args) => { const prepared = prepareArguments(commands, args, process.env, globals, options.conflicts ?? []) for (const warning of prepared.warnings) process.stderr.write(`${warning}\n`) return Command.runWith(command, { version: options.version })(prepared.args) }), ) as Effect.Effect } function provide(node: Spec.Any, handlers: ReadonlyArray): ProvidedCommand { const handler = handlers.find((handler) => handler.spec === node.spec) const spec = handler ? node.spec.pipe( Command.withHandler((input) => Effect.gen(function* () { if (yield* PrintLogs) process.env.OPENCODE_PRINT_LOGS = "1" const module = yield* Effect.promise(handler.load) return yield* module.default(input) }), ), ) : node.spec if (!Object.keys(node.commands).length) return spec as ProvidedCommand const children = Object.values(node.commands) return spec.pipe( Command.withSubcommands([ ...children.map((child) => provide(child, handlers)), ...children.flatMap((child) => child.aliases.map((alias) => provide(alias, handlers))), ]), ) as ProvidedCommand } export * as Runtime from "./runtime"