Some checks failed
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
59 lines
1.5 KiB
TypeScript
59 lines
1.5 KiB
TypeScript
/**
|
|
* Interactive item selection. Uses @clack/prompts when TTY, falls back to numbered list.
|
|
*/
|
|
export async function selectItem<T>(
|
|
items: T[],
|
|
opts: {
|
|
message: string;
|
|
render: (item: T) => string;
|
|
emptyMessage?: string;
|
|
},
|
|
): Promise<T | undefined> {
|
|
if (items.length === 0) {
|
|
console.log(opts.emptyMessage ?? 'No items found.');
|
|
return undefined;
|
|
}
|
|
|
|
const isTTY = process.stdin.isTTY;
|
|
|
|
if (isTTY) {
|
|
try {
|
|
const { select } = await import('@clack/prompts');
|
|
const result = await select({
|
|
message: opts.message,
|
|
options: items.map((item, i) => ({
|
|
value: i,
|
|
label: opts.render(item),
|
|
})),
|
|
});
|
|
|
|
if (typeof result === 'symbol') {
|
|
return undefined;
|
|
}
|
|
|
|
return items[result as number];
|
|
} catch {
|
|
// Fall through to non-interactive
|
|
}
|
|
}
|
|
|
|
// Non-interactive: display numbered list and read a number
|
|
console.log(`\n${opts.message}\n`);
|
|
for (let i = 0; i < items.length; i++) {
|
|
console.log(` ${i + 1}. ${opts.render(items[i]!)}`);
|
|
}
|
|
|
|
const readline = await import('node:readline');
|
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
const answer = await new Promise<string>((resolve) => rl.question('\nSelect: ', resolve));
|
|
rl.close();
|
|
|
|
const index = parseInt(answer, 10) - 1;
|
|
if (isNaN(index) || index < 0 || index >= items.length) {
|
|
console.error('Invalid selection.');
|
|
return undefined;
|
|
}
|
|
|
|
return items[index];
|
|
}
|