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>
132 lines
3.6 KiB
TypeScript
132 lines
3.6 KiB
TypeScript
import type {
|
|
WizardPrompter,
|
|
SelectOption,
|
|
MultiSelectOption,
|
|
ProgressHandle,
|
|
} from './interface.js';
|
|
|
|
export type AnswerValue = string | boolean | string[];
|
|
|
|
export class HeadlessPrompter implements WizardPrompter {
|
|
private answers: Map<string, AnswerValue>;
|
|
private logs: string[] = [];
|
|
|
|
constructor(answers: Record<string, AnswerValue> = {}) {
|
|
this.answers = new Map(Object.entries(answers));
|
|
}
|
|
|
|
intro(message: string): void {
|
|
this.logs.push(`[intro] ${message}`);
|
|
}
|
|
outro(message: string): void {
|
|
this.logs.push(`[outro] ${message}`);
|
|
}
|
|
note(message: string, title?: string): void {
|
|
this.logs.push(`[note] ${title ?? ''}: ${message}`);
|
|
}
|
|
log(message: string): void {
|
|
this.logs.push(`[log] ${message}`);
|
|
}
|
|
warn(message: string): void {
|
|
this.logs.push(`[warn] ${message}`);
|
|
}
|
|
|
|
async text(opts: {
|
|
message: string;
|
|
placeholder?: string;
|
|
defaultValue?: string;
|
|
validate?: (value: string) => string | void;
|
|
}): Promise<string> {
|
|
const answer = this.answers.get(opts.message);
|
|
const value =
|
|
typeof answer === 'string'
|
|
? answer
|
|
: opts.defaultValue !== undefined
|
|
? opts.defaultValue
|
|
: undefined;
|
|
|
|
if (value === undefined) {
|
|
throw new Error(`HeadlessPrompter: no answer for "${opts.message}"`);
|
|
}
|
|
|
|
if (opts.validate) {
|
|
const error = opts.validate(value);
|
|
if (error)
|
|
throw new Error(`HeadlessPrompter validation failed for "${opts.message}": ${error}`);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
async confirm(opts: { message: string; initialValue?: boolean }): Promise<boolean> {
|
|
const answer = this.answers.get(opts.message);
|
|
if (typeof answer === 'boolean') return answer;
|
|
return opts.initialValue ?? true;
|
|
}
|
|
|
|
async select<T>(opts: {
|
|
message: string;
|
|
options: SelectOption<T>[];
|
|
initialValue?: T;
|
|
}): Promise<T> {
|
|
const answer = this.answers.get(opts.message);
|
|
if (answer !== undefined) {
|
|
// Find matching option by value string comparison
|
|
const match = opts.options.find((o) => String(o.value) === String(answer));
|
|
if (match) return match.value;
|
|
}
|
|
if (opts.initialValue !== undefined) return opts.initialValue;
|
|
if (opts.options.length === 0) {
|
|
throw new Error(`HeadlessPrompter: no options for "${opts.message}"`);
|
|
}
|
|
const first = opts.options[0];
|
|
if (first === undefined) {
|
|
throw new Error(`HeadlessPrompter: no options for "${opts.message}"`);
|
|
}
|
|
return first.value;
|
|
}
|
|
|
|
async multiselect<T>(opts: {
|
|
message: string;
|
|
options: MultiSelectOption<T>[];
|
|
required?: boolean;
|
|
}): Promise<T[]> {
|
|
const answer = this.answers.get(opts.message);
|
|
if (Array.isArray(answer)) {
|
|
return opts.options
|
|
.filter((o) => (answer as string[]).includes(String(o.value)))
|
|
.map((o) => o.value);
|
|
}
|
|
return opts.options.filter((o) => o.selected).map((o) => o.value);
|
|
}
|
|
|
|
async groupMultiselect<T>(opts: {
|
|
message: string;
|
|
options: Record<string, MultiSelectOption<T>[]>;
|
|
required?: boolean;
|
|
}): Promise<T[]> {
|
|
const answer = this.answers.get(opts.message);
|
|
if (Array.isArray(answer)) {
|
|
const all = Object.values(opts.options).flat();
|
|
return all.filter((o) => (answer as string[]).includes(String(o.value))).map((o) => o.value);
|
|
}
|
|
return Object.values(opts.options)
|
|
.flat()
|
|
.filter((o) => o.selected)
|
|
.map((o) => o.value);
|
|
}
|
|
|
|
spinner(): ProgressHandle {
|
|
return {
|
|
update(_message: string) {},
|
|
stop(_message?: string) {},
|
|
};
|
|
}
|
|
|
|
separator(): void {}
|
|
|
|
getLogs(): string[] {
|
|
return [...this.logs];
|
|
}
|
|
}
|