[READ-ONLY] Mirror of https://github.com/bombshell-dev/clack. Effortlessly build beautiful command-line apps bomb.sh/docs/clack/basics/getting-started/
cli command-line command-line-app node prompt prompts
5

Configure Feed

Select the types of activity you want to include in your feed.

chore: format

authored by

Christian Preston and committed by
Nate Moore
(Dec 13, 2024, 10:39 PM -0600) a0bca41a 51e12bce

+93 -83
+4 -2
packages/core/src/index.ts
··· 2 2 export { default as GroupMultiSelectPrompt } from './prompts/group-multiselect'; 3 3 export { default as MultiSelectPrompt } from './prompts/multi-select'; 4 4 export { default as PasswordPrompt } from './prompts/password'; 5 - export { default as Prompt, isCancel, type State } from './prompts/prompt'; 5 + export { default as Prompt } from './prompts/prompt'; 6 6 export { default as SelectPrompt } from './prompts/select'; 7 7 export { default as SelectKeyPrompt } from './prompts/select-key'; 8 8 export { default as TextPrompt } from './prompts/text'; 9 - export { block, setGlobalAliases } from './utils'; 9 + export type { ClackState as State } from './types'; 10 + export { block, isCancel, setGlobalAliases } from './utils'; 11 +
+18 -64
packages/core/src/prompts/prompt.ts
··· 1 - import type { Key, ReadLine } from 'node:readline'; 2 - 3 1 import { stdin, stdout } from 'node:process'; 4 - import readline from 'node:readline'; 2 + import readline, { type Key, type ReadLine } from 'node:readline'; 5 3 import { Readable, Writable } from 'node:stream'; 6 4 import { WriteStream } from 'node:tty'; 7 5 import { cursor, erase } from 'sisteransi'; 8 6 import wrap from 'wrap-ansi'; 9 7 10 - import { type InferSetType, aliases, keys, hasAliasKey } from '../utils'; 8 + import { ALIASES, CANCEL_SYMBOL, diffLines, hasAliasKey, KEYS, setRawMode } from '../utils'; 11 9 12 - function diffLines(a: string, b: string) { 13 - if (a === b) return; 14 - 15 - const aLines = a.split('\n'); 16 - const bLines = b.split('\n'); 17 - const diff: number[] = []; 18 - 19 - for (let i = 0; i < Math.max(aLines.length, bLines.length); i++) { 20 - if (aLines[i] !== bLines[i]) diff.push(i); 21 - } 22 - 23 - return diff; 24 - } 25 - 26 - const cancel = Symbol('clack:cancel'); 27 - export function isCancel(value: unknown): value is symbol { 28 - return value === cancel; 29 - } 30 - 31 - function setRawMode(input: Readable, value: boolean) { 32 - if ((input as typeof stdin).isTTY) (input as typeof stdin).setRawMode(value); 33 - } 10 + import type { ClackEvents, ClackState, InferSetType } from '../types'; 34 11 35 12 export interface PromptOptions<Self extends Prompt> { 36 13 render(this: Omit<Self, 'prompt'>): string | void; ··· 42 19 debug?: boolean; 43 20 } 44 21 45 - export type State = 'initial' | 'active' | 'cancel' | 'submit' | 'error'; 46 - 47 - /** 48 - * Typed event emitter for clack 49 - */ 50 - interface ClackHooks { 51 - 'initial': (value?: any) => void; 52 - 'active': (value?: any) => void; 53 - 'cancel': (value?: any) => void; 54 - 'submit': (value?: any) => void; 55 - 'error': (value?: any) => void; 56 - 'cursor': (key?: InferSetType<typeof keys>) => void; 57 - 'key': (key?: string) => void; 58 - 'value': (value?: string) => void; 59 - 'confirm': (value?: boolean) => void; 60 - 'finalize': () => void; 61 - } 62 - 63 22 export default class Prompt { 64 23 protected input: Readable; 65 24 protected output: Writable; ··· 72 31 private _subscribers = new Map<string, { cb: (...args: any) => any; once?: boolean }[]>(); 73 32 protected _cursor = 0; 74 33 75 - public state: State = 'initial'; 34 + public state: ClackState = 'initial'; 76 35 public error = ''; 77 36 public value: any; 78 37 79 - constructor( 80 - options: PromptOptions<Prompt>, 81 - trackValue: boolean = true 82 - ) { 83 - const { 84 - input = stdin, 85 - output = stdout, 86 - render, 87 - ...opts 88 - } = options; 38 + constructor(options: PromptOptions<Prompt>, trackValue: boolean = true) { 39 + const { input = stdin, output = stdout, render, ...opts } = options; 89 40 90 41 this.opts = opts; 91 42 this.onKeypress = this.onKeypress.bind(this); ··· 109 60 * Set a subscriber with opts 110 61 * @param event - The event name 111 62 */ 112 - private setSubscriber<T extends keyof ClackHooks>(event: T, opts: { cb: ClackHooks[T]; once?: boolean }) { 63 + private setSubscriber<T extends keyof ClackEvents>( 64 + event: T, 65 + opts: { cb: ClackEvents[T]; once?: boolean } 66 + ) { 113 67 const params = this._subscribers.get(event) ?? []; 114 68 params.push(opts); 115 69 this._subscribers.set(event, params); ··· 120 74 * @param event - The event name 121 75 * @param cb - The callback 122 76 */ 123 - public on<T extends keyof ClackHooks>(event: T, cb: ClackHooks[T]) { 77 + public on<T extends keyof ClackEvents>(event: T, cb: ClackEvents[T]) { 124 78 this.setSubscriber(event, { cb }); 125 79 } 126 80 ··· 129 83 * @param event - The event name 130 84 * @param cb - The callback 131 85 */ 132 - public once<T extends keyof ClackHooks>(event: T, cb: ClackHooks[T]) { 86 + public once<T extends keyof ClackEvents>(event: T, cb: ClackEvents[T]) { 133 87 this.setSubscriber(event, { cb, once: true }); 134 88 } 135 89 ··· 138 92 * @param event - The event name 139 93 * @param data - The data to pass to the callback 140 94 */ 141 - public emit<T extends keyof ClackHooks>(event: T, ...data: Parameters<ClackHooks[T]>) { 95 + public emit<T extends keyof ClackEvents>(event: T, ...data: Parameters<ClackEvents[T]>) { 142 96 const cbs = this._subscribers.get(event) ?? []; 143 97 const cleanup: (() => void)[] = []; 144 98 ··· 197 151 this.output.write(cursor.show); 198 152 this.output.off('resize', this.render); 199 153 setRawMode(this.input, false); 200 - resolve(cancel); 154 + resolve(CANCEL_SYMBOL); 201 155 }); 202 156 }); 203 157 } ··· 206 160 if (this.state === 'error') { 207 161 this.state = 'active'; 208 162 } 209 - if (key?.name && !this._track && aliases.has(key.name)) { 210 - this.emit('cursor', aliases.get(key.name)); 163 + if (key?.name && !this._track && ALIASES.has(key.name)) { 164 + this.emit('cursor', ALIASES.get(key.name)); 211 165 } 212 - if (key?.name && keys.has(key.name as InferSetType<typeof keys>)) { 213 - this.emit('cursor', key.name as InferSetType<typeof keys>); 166 + if (key?.name && KEYS.has(key.name as InferSetType<typeof KEYS>)) { 167 + this.emit('cursor', key.name as InferSetType<typeof KEYS>); 214 168 } 215 169 if (char && (char.toLowerCase() === 'y' || char.toLowerCase() === 'n')) { 216 170 this.emit('confirm', char.toLowerCase() === 'y');
+24
packages/core/src/types.ts
··· 1 + import { KEYS } from './utils'; 2 + 3 + export type InferSetType<T> = T extends Set<infer U> ? U : never; 4 + 5 + /** 6 + * The state of the prompt 7 + */ 8 + export type ClackState = 'initial' | 'active' | 'cancel' | 'submit' | 'error'; 9 + 10 + /** 11 + * Typed event emitter for clack 12 + */ 13 + export interface ClackEvents { 14 + initial: (value?: any) => void; 15 + active: (value?: any) => void; 16 + cancel: (value?: any) => void; 17 + submit: (value?: any) => void; 18 + error: (value?: any) => void; 19 + cursor: (key?: InferSetType<typeof KEYS>) => void; 20 + key: (key?: string) => void; 21 + value: (value?: string) => void; 22 + confirm: (value?: boolean) => void; 23 + finalize: () => void; 24 + }
+17 -13
packages/core/src/utils/aliases.ts
··· 1 - 2 - export type InferSetType<T> = T extends Set<infer U> ? U : never; 1 + import type { InferSetType } from '../types'; 3 2 4 3 const DEFAULT_KEYS = ['up', 'down', 'left', 'right', 'space', 'enter', 'cancel'] as const; 5 - export const keys = new Set(DEFAULT_KEYS); 4 + export const KEYS = new Set(DEFAULT_KEYS); 6 5 7 - export const aliases = new Map<string, InferSetType<typeof keys>>([ 6 + export const ALIASES = new Map<string, InferSetType<typeof KEYS>>([ 8 7 ['k', 'up'], 9 8 ['j', 'down'], 10 9 ['h', 'left'], ··· 19 18 * @default 20 19 * new Map([['k', 'up'], ['j', 'down'], ['h', 'left'], ['l', 'right'], ['\x03', 'cancel'],]) 21 20 */ 22 - export function setGlobalAliases(alias: Array<[string, InferSetType<typeof keys>]>) { 21 + export function setGlobalAliases(alias: Array<[string, InferSetType<typeof KEYS>]>) { 23 22 for (const [newAlias, key] of alias) { 24 - if (!aliases.has(newAlias)) { 25 - aliases.set(newAlias, key); 23 + if (!ALIASES.has(newAlias)) { 24 + ALIASES.set(newAlias, key); 26 25 } 27 26 } 28 27 } ··· 33 32 * @param type - The type of key to check for 34 33 * @returns boolean 35 34 */ 36 - export function hasAliasKey(key: string | Array<string | undefined>, type: InferSetType<typeof keys>) { 35 + export function hasAliasKey( 36 + key: string | Array<string | undefined>, 37 + type: InferSetType<typeof KEYS> 38 + ) { 37 39 if (typeof key === 'string') { 38 - return aliases.has(key) && aliases.get(key) === type; 40 + return ALIASES.has(key) && ALIASES.get(key) === type; 39 41 } 40 42 41 - return key.map((n) => { 42 - if (n !== undefined && aliases.has(n) && aliases.get(n) === type) return true; 43 - return false; 44 - }).includes(true); 43 + return key 44 + .map((n) => { 45 + if (n !== undefined && ALIASES.has(n) && ALIASES.get(n) === type) return true; 46 + return false; 47 + }) 48 + .includes(true); 45 49 }
+17 -4
packages/core/src/utils/index.ts
··· 1 + import { stdin, stdout } from 'node:process'; 1 2 import type { Key } from 'node:readline'; 2 - 3 - import { stdin, stdout } from 'node:process'; 4 3 import * as readline from 'node:readline'; 4 + import type { Readable } from 'node:stream'; 5 5 import { cursor } from 'sisteransi'; 6 6 import { hasAliasKey } from './aliases'; 7 7 8 8 const isWindows = globalThis.process.platform.startsWith('win'); 9 + 10 + export * from './aliases'; 11 + export * from './string'; 12 + 13 + export const CANCEL_SYMBOL = Symbol('clack:cancel'); 14 + 15 + export function isCancel(value: unknown): value is symbol { 16 + return value === CANCEL_SYMBOL; 17 + } 18 + 19 + export function setRawMode(input: Readable, value: boolean) { 20 + const i = input as typeof stdin; 21 + 22 + if (i.isTTY) i.setRawMode(value); 23 + } 9 24 10 25 export function block({ 11 26 input = stdin, ··· 54 69 rl.close(); 55 70 }; 56 71 } 57 - 58 - export * from './aliases';
+13
packages/core/src/utils/string.ts
··· 1 + export function diffLines(a: string, b: string) { 2 + if (a === b) return; 3 + 4 + const aLines = a.split('\n'); 5 + const bLines = b.split('\n'); 6 + const diff: number[] = []; 7 + 8 + for (let i = 0; i < Math.max(aLines.length, bLines.length); i++) { 9 + if (aLines[i] !== bLines[i]) diff.push(i); 10 + } 11 + 12 + return diff; 13 + }