import * as p from '@clack/prompts'; import { WizardCancelledError } from '../errors.js'; import type { WizardPrompter, SelectOption, MultiSelectOption, ProgressHandle, } from './interface.js'; function guardCancel(value: T | symbol): T { if (p.isCancel(value)) { throw new WizardCancelledError(); } return value as T; } export class ClackPrompter implements WizardPrompter { intro(message: string): void { p.intro(message); } outro(message: string): void { p.outro(message); } note(message: string, title?: string): void { p.note(message, title); } log(message: string): void { p.log.info(message); } warn(message: string): void { p.log.warn(message); } async text(opts: { message: string; placeholder?: string; defaultValue?: string; validate?: (value: string) => string | void; }): Promise { const validate = opts.validate ? (v: string) => { const r = opts.validate!(v); return r === undefined ? undefined : r; } : undefined; const result = await p.text({ message: opts.message, placeholder: opts.placeholder, defaultValue: opts.defaultValue, validate, }); return guardCancel(result); } async confirm(opts: { message: string; initialValue?: boolean }): Promise { const result = await p.confirm({ message: opts.message, initialValue: opts.initialValue, }); return guardCancel(result); } async select(opts: { message: string; options: SelectOption[]; initialValue?: T; }): Promise { const clackOptions = opts.options.map((o) => ({ value: o.value as T, label: o.label, hint: o.hint, })); const result = await p.select({ message: opts.message, // eslint-disable-next-line @typescript-eslint/no-explicit-any -- clack Option conditional type needs concrete Primitive options: clackOptions as any, initialValue: opts.initialValue, }); return guardCancel(result) as T; } async multiselect(opts: { message: string; options: MultiSelectOption[]; required?: boolean; }): Promise { const clackOptions = opts.options.map((o) => ({ value: o.value as T, label: o.label, hint: o.hint, })); const result = await p.multiselect({ message: opts.message, // eslint-disable-next-line @typescript-eslint/no-explicit-any options: clackOptions as any, required: opts.required, initialValues: opts.options.filter((o) => o.selected).map((o) => o.value), }); return guardCancel(result) as T[]; } async groupMultiselect(opts: { message: string; options: Record[]>; required?: boolean; }): Promise { const grouped: Record = {}; for (const [group, items] of Object.entries(opts.options)) { grouped[group] = items.map((o) => ({ value: o.value as T, label: o.label, hint: o.hint, })); } const result = await p.groupMultiselect({ message: opts.message, // eslint-disable-next-line @typescript-eslint/no-explicit-any options: grouped as any, required: opts.required, }); return guardCancel(result) as T[]; } spinner(): ProgressHandle { const s = p.spinner(); let started = false; return { update(message: string) { if (!started) { s.start(message); started = true; } else { s.message(message); } }, stop(message?: string) { if (started) { s.stop(message); started = false; } }, }; } separator(): void { p.log.info(''); } }