@@ -2,6 +2,7 @@ import type { Argv } from "yargs"
22import { Effect , Schema } from "effect"
33import { AppRuntime , type AppServices } from "@/effect/app-runtime"
44import { InstanceStore } from "@/project/instance-store"
5+ import { InstanceRef } from "@/effect/instance-ref"
56import { cmd } from "./cmd/cmd"
67
78/**
@@ -21,6 +22,11 @@ export const fail = (message: string, exitCode = 1) => Effect.fail(new CliError(
2122 * Effect-native CLI command builder. Wraps yargs `cmd()` so the handler body is
2223 * an `Effect` with `InstanceRef` provided and any `AppServices` yieldable.
2324 *
25+ * The handler is wrapped in `Effect.ensuring(store.dispose(ctx))` so the loaded
26+ * InstanceContext is disposed (runDisposers + IPC `server.instance.disposed`)
27+ * on every Exit — success, typed failure, defect, or interruption. Matches the
28+ * legacy `bootstrap()` finally-disposal semantics without per-handler boilerplate.
29+ *
2430 * Errors propagate to the existing top-level handler in `src/index.ts`; use
2531 * `fail("...")` for user-visible domain failures (clean exit, formatted message).
2632 *
@@ -47,6 +53,17 @@ export const effectCmd = <Args, A>(opts: {
4753 // yargs typing wraps Args in ArgumentsCamelCase<WithDoubleDash<...>>; cast at the boundary.
4854 const args = rawArgs as unknown as Args
4955 const directory = opts . directory ?.( args ) ?? process . cwd ( )
50- await AppRuntime . runPromise ( InstanceStore . Service . use ( ( s ) => s . provide ( { directory } , opts . handler ( args ) ) ) )
56+ await AppRuntime . runPromise (
57+ InstanceStore . Service . use ( ( store ) =>
58+ store . provide (
59+ { directory } ,
60+ Effect . gen ( function * ( ) {
61+ const ctx = yield * InstanceRef
62+ const body = opts . handler ( args )
63+ return ctx ? yield * body . pipe ( Effect . ensuring ( store . dispose ( ctx ) ) ) : yield * body
64+ } ) ,
65+ ) ,
66+ ) ,
67+ )
5168 } ,
5269 } )
0 commit comments