-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
index.mts
98 lines (84 loc) · 2.67 KB
/
index.mts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import { Stream } from 'node:stream';
import MuteStream from 'mute-stream';
import stripAnsi from 'strip-ansi';
import ansiEscapes from 'ansi-escapes';
import type { Prompt } from '@inquirer/type';
const ignoredAnsi = new Set([ansiEscapes.cursorHide, ansiEscapes.cursorShow]);
class BufferedStream extends Stream.Writable {
#_fullOutput: string = '';
#_chunks: Array<string> = [];
#_rawChunks: Array<string> = [];
override _write(chunk: Buffer, _encoding: string, callback: () => void) {
const str = chunk.toString();
this.#_fullOutput += str;
// There's some ANSI Inquirer just send to keep state of the terminal clear; we'll ignore those since they're
// unlikely to be used by end users or part of prompt code.
if (!ignoredAnsi.has(str)) {
this.#_rawChunks.push(str);
}
// Stripping the ANSI codes here because Inquirer will push commands ANSI (like cursor move.)
// This is probably fine since we don't care about those for testing; but this could become
// an issue if we ever want to test for those.
if (stripAnsi(str).trim().length > 0) {
this.#_chunks.push(str);
}
callback();
}
getLastChunk({ raw }: { raw?: boolean }): string {
const chunks = raw ? this.#_rawChunks : this.#_chunks;
const lastChunk = chunks.at(-1);
return lastChunk ?? '';
}
getFullOutput(): string {
return this.#_fullOutput;
}
}
export async function render<TestedPrompt extends Prompt<unknown, unknown>>(
prompt: TestedPrompt,
props: Parameters<TestedPrompt>[0],
options?: Parameters<TestedPrompt>[1],
) {
const input = new MuteStream();
input.unmute();
const output = new BufferedStream();
const answer = prompt(props, { input, output, ...options });
// Wait for event listeners to be ready
await Promise.resolve();
await Promise.resolve();
const events = {
keypress(
key:
| string
| {
name?: string | undefined;
ctrl?: boolean | undefined;
meta?: boolean | undefined;
shift?: boolean | undefined;
},
) {
if (typeof key === 'string') {
input.emit('keypress', null, { name: key });
} else {
input.emit('keypress', null, key);
}
},
type(text: string) {
input.write(text);
for (const char of text) {
input.emit('keypress', null, { name: char });
}
},
};
return {
answer,
input,
events,
getScreen({ raw }: { raw?: boolean } = {}): string {
const lastScreen = output.getLastChunk({ raw });
return raw ? lastScreen : stripAnsi(lastScreen).trim();
},
getFullOutput(): string {
return output.getFullOutput();
},
};
}