Skip to content

Commit 123e182

Browse files
committed
feat: add CommonOptions so all prompts accept a custom output/input
Allows all prompts to take a custom `output` or `input` which defaults to `process.stdout` and `process.stdin` respectively.
1 parent e6ff090 commit 123e182

File tree

1 file changed

+63
-36
lines changed

1 file changed

+63
-36
lines changed

packages/prompts/src/index.ts

Lines changed: 63 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -98,14 +98,17 @@ const limitOptions = <TOption>(params: LimitOptionsParams<TOption>): string[] =>
9898
});
9999
};
100100

101-
export interface TextOptions {
101+
export interface CommonOptions {
102+
input?: Readable;
103+
output?: Writable;
104+
}
105+
106+
export interface TextOptions extends CommonOptions {
102107
message: string;
103108
placeholder?: string;
104109
defaultValue?: string;
105110
initialValue?: string;
106111
validate?: (value: string) => string | Error | undefined;
107-
input?: Readable;
108-
output?: Writable;
109112
}
110113
export const text = (opts: TextOptions) => {
111114
return new TextPrompt({
@@ -140,7 +143,7 @@ export const text = (opts: TextOptions) => {
140143
}).prompt() as Promise<string | symbol>;
141144
};
142145

143-
export interface PasswordOptions {
146+
export interface PasswordOptions extends CommonOptions {
144147
message: string;
145148
mask?: string;
146149
validate?: (value: string) => string | Error | undefined;
@@ -149,6 +152,8 @@ export const password = (opts: PasswordOptions) => {
149152
return new PasswordPrompt({
150153
validate: opts.validate,
151154
mask: opts.mask ?? S_PASSWORD_MASK,
155+
input: opts.input,
156+
output: opts.output,
152157
render() {
153158
const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
154159
const value = this.valueWithCursor;
@@ -172,7 +177,7 @@ export const password = (opts: PasswordOptions) => {
172177
}).prompt() as Promise<string | symbol>;
173178
};
174179

175-
export interface ConfirmOptions {
180+
export interface ConfirmOptions extends CommonOptions {
176181
message: string;
177182
active?: string;
178183
inactive?: string;
@@ -184,6 +189,8 @@ export const confirm = (opts: ConfirmOptions) => {
184189
return new ConfirmPrompt({
185190
active,
186191
inactive,
192+
input: opts.input,
193+
output: opts.output,
187194
initialValue: opts.initialValue ?? true,
188195
render() {
189196
const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
@@ -252,7 +259,7 @@ export type Option<Value> = Value extends Primitive
252259
hint?: string;
253260
};
254261

255-
export interface SelectOptions<Value> {
262+
export interface SelectOptions<Value> extends CommonOptions {
256263
message: string;
257264
options: Option<Value>[];
258265
initialValue?: Value;
@@ -278,6 +285,8 @@ export const select = <Value>(opts: SelectOptions<Value>) => {
278285

279286
return new SelectPrompt({
280287
options: opts.options,
288+
input: opts.input,
289+
output: opts.output,
281290
initialValue: opts.initialValue,
282291
render() {
283292
const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
@@ -327,6 +336,8 @@ export const selectKey = <Value extends string>(opts: SelectOptions<Value>) => {
327336

328337
return new SelectKeyPrompt({
329338
options: opts.options,
339+
input: opts.input,
340+
output: opts.output,
330341
initialValue: opts.initialValue,
331342
render() {
332343
const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
@@ -351,7 +362,7 @@ export const selectKey = <Value extends string>(opts: SelectOptions<Value>) => {
351362
}).prompt() as Promise<Value | symbol>;
352363
};
353364

354-
export interface MultiSelectOptions<Value> {
365+
export interface MultiSelectOptions<Value> extends CommonOptions {
355366
message: string;
356367
options: Option<Value>[];
357368
initialValues?: Value[];
@@ -389,6 +400,8 @@ export const multiselect = <Value>(opts: MultiSelectOptions<Value>) => {
389400

390401
return new MultiSelectPrompt({
391402
options: opts.options,
403+
input: opts.input,
404+
output: opts.output,
392405
initialValues: opts.initialValues,
393406
required: opts.required ?? true,
394407
cursorAt: opts.cursorAt,
@@ -461,7 +474,7 @@ export const multiselect = <Value>(opts: MultiSelectOptions<Value>) => {
461474
}).prompt() as Promise<Value[] | symbol>;
462475
};
463476

464-
export interface GroupMultiSelectOptions<Value> {
477+
export interface GroupMultiSelectOptions<Value> extends CommonOptions {
465478
message: string;
466479
options: Record<string, Option<Value>[]>;
467480
initialValues?: Value[];
@@ -522,6 +535,8 @@ export const groupMultiselect = <Value>(opts: GroupMultiSelectOptions<Value>) =>
522535

523536
return new GroupMultiSelectPrompt({
524537
options: opts.options,
538+
input: opts.input,
539+
output: opts.output,
525540
initialValues: opts.initialValues,
526541
required: opts.required ?? true,
527542
cursorAt: opts.cursorAt,
@@ -614,9 +629,10 @@ export const groupMultiselect = <Value>(opts: GroupMultiSelectOptions<Value>) =>
614629
}).prompt() as Promise<Value[] | symbol>;
615630
};
616631

617-
export const note = (message = '', title = '') => {
632+
export const note = (message = '', title = '', opts?: CommonOptions) => {
618633
const lines = `\n${message}\n`.split('\n');
619634
const titleLen = strip(title).length;
635+
const output: Writable = opts?.output ?? process.stdout;
620636
const len =
621637
Math.max(
622638
lines.reduce((sum, ln) => {
@@ -633,59 +649,71 @@ export const note = (message = '', title = '') => {
633649
)}`
634650
)
635651
.join('\n');
636-
process.stdout.write(
652+
output.write(
637653
`${color.gray(S_BAR)}\n${color.green(S_STEP_SUBMIT)} ${color.reset(title)} ${color.gray(
638654
S_BAR_H.repeat(Math.max(len - titleLen - 1, 1)) + S_CORNER_TOP_RIGHT
639655
)}\n${msg}\n${color.gray(S_CONNECT_LEFT + S_BAR_H.repeat(len + 2) + S_CORNER_BOTTOM_RIGHT)}\n`
640656
);
641657
};
642658

643-
export const cancel = (message = '') => {
644-
process.stdout.write(`${color.gray(S_BAR_END)} ${color.red(message)}\n\n`);
659+
export const cancel = (message = '', opts?: CommonOptions) => {
660+
const output: Writable = opts?.output ?? process.stdout;
661+
output.write(`${color.gray(S_BAR_END)} ${color.red(message)}\n\n`);
645662
};
646663

647-
export const intro = (title = '') => {
648-
process.stdout.write(`${color.gray(S_BAR_START)} ${title}\n`);
664+
export const intro = (title = '', opts?: CommonOptions) => {
665+
const output: Writable = opts?.output ?? process.stdout;
666+
output.write(`${color.gray(S_BAR_START)} ${title}\n`);
649667
};
650668

651-
export const outro = (message = '') => {
652-
process.stdout.write(`${color.gray(S_BAR)}\n${color.gray(S_BAR_END)} ${message}\n\n`);
669+
export const outro = (message = '', opts?: CommonOptions) => {
670+
const output: Writable = opts?.output ?? process.stdout;
671+
output.write(`${color.gray(S_BAR)}\n${color.gray(S_BAR_END)} ${message}\n\n`);
653672
};
654673

655-
export type LogMessageOptions = {
674+
export interface LogMessageOptions extends CommonOptions {
656675
symbol?: string;
657-
};
676+
}
658677
export const log = {
659-
message: (message = '', { symbol = color.gray(S_BAR) }: LogMessageOptions = {}) => {
678+
message: (
679+
message = '',
680+
{ symbol = color.gray(S_BAR), output = process.stdout }: LogMessageOptions = {}
681+
) => {
660682
const parts = [`${color.gray(S_BAR)}`];
661683
if (message) {
662684
const [firstLine, ...lines] = message.split('\n');
663685
parts.push(`${symbol} ${firstLine}`, ...lines.map((ln) => `${color.gray(S_BAR)} ${ln}`));
664686
}
665-
process.stdout.write(`${parts.join('\n')}\n`);
687+
output.write(`${parts.join('\n')}\n`);
666688
},
667-
info: (message: string) => {
668-
log.message(message, { symbol: color.blue(S_INFO) });
689+
info: (message: string, opts?: LogMessageOptions) => {
690+
log.message(message, { ...opts, symbol: color.blue(S_INFO) });
669691
},
670-
success: (message: string) => {
671-
log.message(message, { symbol: color.green(S_SUCCESS) });
692+
success: (message: string, opts?: LogMessageOptions) => {
693+
log.message(message, { ...opts, symbol: color.green(S_SUCCESS) });
672694
},
673-
step: (message: string) => {
674-
log.message(message, { symbol: color.green(S_STEP_SUBMIT) });
695+
step: (message: string, opts?: LogMessageOptions) => {
696+
log.message(message, { ...opts, symbol: color.green(S_STEP_SUBMIT) });
675697
},
676-
warn: (message: string) => {
677-
log.message(message, { symbol: color.yellow(S_WARN) });
698+
warn: (message: string, opts?: LogMessageOptions) => {
699+
log.message(message, { ...opts, symbol: color.yellow(S_WARN) });
678700
},
679701
/** alias for `log.warn()`. */
680-
warning: (message: string) => {
681-
log.warn(message);
702+
warning: (message: string, opts?: LogMessageOptions) => {
703+
log.warn(message, opts);
682704
},
683-
error: (message: string) => {
684-
log.message(message, { symbol: color.red(S_ERROR) });
705+
error: (message: string, opts?: LogMessageOptions) => {
706+
log.message(message, { ...opts, symbol: color.red(S_ERROR) });
685707
},
686708
};
687709

688710
const prefix = `${color.gray(S_BAR)} `;
711+
712+
// TODO (43081j): this currently doesn't support custom `output` writables
713+
// because we rely on `columns` existing (i.e. `process.stdout.columns).
714+
//
715+
// If we want to support `output` being passed in, we will need to use
716+
// a condition like `if (output insance Writable)` to check if it has columns
689717
export const stream = {
690718
message: async (
691719
iterable: Iterable<string> | AsyncIterable<string>,
@@ -730,9 +758,8 @@ export const stream = {
730758
},
731759
};
732760

733-
export interface SpinnerOptions {
761+
export interface SpinnerOptions extends CommonOptions {
734762
indicator?: 'dots' | 'timer';
735-
output?: Writable;
736763
}
737764

738765
export interface SpinnerResult {
@@ -937,11 +964,11 @@ export type Task = {
937964
/**
938965
* Define a group of tasks to be executed
939966
*/
940-
export const tasks = async (tasks: Task[]) => {
967+
export const tasks = async (tasks: Task[], opts?: CommonOptions) => {
941968
for (const task of tasks) {
942969
if (task.enabled === false) continue;
943970

944-
const s = spinner();
971+
const s = spinner(opts);
945972
s.start(task.title);
946973
const result = await task.task(s.message);
947974
s.stop(result || task.title);

0 commit comments

Comments
 (0)