import * as Effect from "effect/Effect" import * as Command from "effect/unstable/cli/Command" import { CliApi } from "./cli-api" export type Input = Value extends CliApi.Node ? Input : Value extends Command.Command ? Input : never type RuntimeHandler = (input: unknown) => Effect.Effect type Loader = () => Promise<{ default: (input: Input) => Effect.Effect }> type ProvidedCommand = Command.Command 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: CliApi.Any, value: RuntimeHandlers) { if (typeof value === "function") { result.push({ spec: node.spec, load: value as () => Promise<{ default: RuntimeHandler }> }) return } if (value.$) result.push({ spec: node.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(api: CliApi.Any, handlers: ReadonlyArray, options: { readonly version: string }) { return Command.run(provide(api, handlers), options) as Effect.Effect } function provide(node: CliApi.Any, handlers: ReadonlyArray): ProvidedCommand { const spec: Command.Command.Any = Object.keys(node.commands).length ? (node.spec as Command.Command).pipe( Command.withSubcommands(Object.values(node.commands).map((child) => provide(child, handlers))), ) : node.spec const handler = handlers.find((handler) => handler.spec === node.spec) if (!handler) return spec as ProvidedCommand return spec.pipe( Command.withHandler((input) => Effect.flatMap(Effect.promise(handler.load), (module) => module.default(input))), ) as ProvidedCommand } export * as CliBuilder from "./cli-builder"