All checks were successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
153 lines
3.7 KiB
TypeScript
153 lines
3.7 KiB
TypeScript
import * as p from '@clack/prompts';
|
|
import { WizardCancelledError } from '../errors.js';
|
|
import type {
|
|
WizardPrompter,
|
|
SelectOption,
|
|
MultiSelectOption,
|
|
ProgressHandle,
|
|
} from './interface.js';
|
|
|
|
function guardCancel<T>(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<string> {
|
|
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<boolean> {
|
|
const result = await p.confirm({
|
|
message: opts.message,
|
|
initialValue: opts.initialValue,
|
|
});
|
|
return guardCancel(result);
|
|
}
|
|
|
|
async select<T>(opts: {
|
|
message: string;
|
|
options: SelectOption<T>[];
|
|
initialValue?: T;
|
|
}): Promise<T> {
|
|
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<T>(opts: {
|
|
message: string;
|
|
options: MultiSelectOption<T>[];
|
|
required?: boolean;
|
|
}): Promise<T[]> {
|
|
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<T>(opts: {
|
|
message: string;
|
|
options: Record<string, MultiSelectOption<T>[]>;
|
|
required?: boolean;
|
|
}): Promise<T[]> {
|
|
const grouped: Record<string, { value: T; label: string; hint?: string }[]> = {};
|
|
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('');
|
|
}
|
|
}
|