1import * as Data from "effect/Data";
2import * as Effect from "effect/Effect";
3import * as Runtime from "effect/Runtime";
4import * as CliKit from "../CliKit/index.ts";
5import { exitDeclined } from "./errors.ts";
6
7/**
8 * A declined confirmation prompt. By the time this propagates the decline
9 * message has already been printed and the exit code set, so the
10 * `[Runtime.errorReported] = false` marker suppresses the runtime's default
11 * failure log — the process just exits 1, matching the CLI's documented
12 * "1 = failure or decline" contract.
13 */
14export class ConfirmationDeclined extends Data.TaggedError(
15 "ConfirmationDeclined",
16) {
17 readonly [Runtime.errorReported] = false;
18}
19
20/**
21 * Gate a destructive operation behind a confirmation prompt.
22 *
23 * Returns void when `yes` was passed or the user approves. Otherwise prints
24 * `abortMessage` (default "Aborted."), sets the exit code to 1 via
25 * {@link exitDeclined}, and fails with {@link ConfirmationDeclined} so the
26 * command short-circuits before the destructive work.
27 */
28export const confirmOrDecline = Effect.fn(function* (options: {
29 readonly yes: boolean;
30 readonly message: string;
31 readonly confirmLabel?: string;
32 readonly cancelLabel?: string;
33 readonly abortMessage?: string;
34}) {
35 if (options.yes) return;
36 const approved = yield* CliKit.accessors.prompt.confirm({
37 message: options.message,
38 initialValue: false,
39 confirmLabel: options.confirmLabel,
40 cancelLabel: options.cancelLabel,
41 });
42 if (approved) return;
43 yield* CliKit.accessors.output.info(options.abortMessage ?? "Aborted.");
44 yield* exitDeclined;
45 return yield* Effect.fail(new ConfirmationDeclined());
46});