1
- const { spawn } = require ( 'child_process' ) ;
1
+ const { spawn } = require ( "child_process" ) ;
2
+ const { parseOutput } = require ( "./cli-ansi-parser" ) ;
2
3
3
- // types
4
- /**
5
- * @typedef {({
6
- * textAfter: (question: string) => string,
7
- * stringOutput: string,
8
- * tokenizedOutput: string,
9
- * rawOutput: string
10
- * })} ParsedOutput
11
- *
12
- * @typedef {({
13
- * typeDelay: number,
14
- * logData: boolean,
15
- * cwd: string,
16
- * env: any
17
- * })} Options
18
- */
19
-
20
- // Big shoutout to https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 for super cool reference to all ansi codes
21
- const ESC = '(?:\\x1[bB])|(?:\\u001b)\\[' ;
22
- const ESC_NO_BRACKET = ESC . replace ( '\\[' , '' ) ;
23
- const STRING_ESC = '\x1b[' ;
24
- const ANSI_CHARACTER_MAPS = {
25
- ERASE_LINE : `${ ESC } 2K` ,
26
- CURSOR_LEFT : `${ ESC } 1G` ,
27
- CURSOR_UP : `${ ESC } 1A` ,
28
- CURSOR_DOWN : `${ ESC } 1B` ,
29
- CURSOR_VISIBLE : `${ ESC } \\?25h` ,
30
- CURSOR_INVISIBLE : `${ ESC } \\?25l` ,
31
- CURSOR_STORE : `${ ESC_NO_BRACKET } 7` ,
32
- CURSOR_RESTORE : `${ ESC_NO_BRACKET } 8` ,
33
- UNDERLINE : `${ ESC } 4m` ,
34
- // Colors
35
- RED_START : `${ ESC } 31m` ,
36
- GREEN_START : `${ ESC } 32m` ,
37
- YELLOW_START : `${ ESC } 33m` ,
38
- BLUE_START : `${ ESC } 34m` ,
39
- CYAN_START : `${ ESC } 36m` ,
40
- GREY_START : `${ ESC } 90m` ,
41
- COLOR_END : `${ ESC } 39m` ,
42
- BOLD_START : `${ ESC } 1m` ,
43
- BOLD_END : `${ ESC } 22m` ,
44
- BOLD_2_START : `${ ESC } 3m` ,
45
- BOLD_2_END : `${ ESC } 23m` ,
46
- }
47
-
48
- const wait = ms => new Promise ( resolve => setTimeout ( resolve , ms ) ) ;
49
-
50
- const parseOutput = ( output ) => {
51
- const textAfter = ( question ) => {
52
- const questionIndex = output . indexOf ( question ) + question . length ;
53
- const endlineIndex = output . indexOf ( '\n' , questionIndex + 1 ) ;
54
- const cleanEndlineIndex = endlineIndex <= 0 ? undefined : endlineIndex ;
55
- return output . slice ( questionIndex , cleanEndlineIndex ) . trim ( ) ;
56
- } ;
57
-
58
- const tokenizeOutput = ( ) => {
59
- let out = output ;
60
- for ( const [ ESCAPE_CHARACTER_NAME , ESCAPE_CHARACTER_REGEX ] of Object . entries ( ANSI_CHARACTER_MAPS ) ) {
61
- out = out . replace ( new RegExp ( `(${ ESCAPE_CHARACTER_REGEX } )` , 'g' ) , `[${ ESCAPE_CHARACTER_NAME } ]` ) ;
62
- }
63
-
64
- return out ;
65
- }
66
-
67
- const finalString = ( ) => {
68
- let parsedOutput = tokenizeOutput ( ) ;
69
- const lastEraseLineIndex = parsedOutput . lastIndexOf ( '[ERASE_LINE]' )
70
- const outputAfterLastEraseLine = parsedOutput . slice ( lastEraseLineIndex > 0 ? lastEraseLineIndex : 0 ) ;
71
- return outputAfterLastEraseLine . replace ( / \[ ( \w * ) \] / g, '' ) ;
72
- }
73
-
74
- return {
75
- textAfter,
76
- rawOutput : output ,
77
- tokenizedOutput : tokenizeOutput ( ) ,
78
- stringOutput : finalString ( ) ,
79
- } ;
80
- } ;
4
+ const wait = ( ms ) => new Promise ( ( resolve ) => setTimeout ( resolve , ms ) ) ;
81
5
82
6
/**
83
- *
84
- * @param {string } commandString
85
- * @param {Options } options
7
+ *
8
+ * @param {string } commandString
9
+ * @param {Options } options
86
10
*/
87
11
const createCommandInterface = ( commandString , options ) => {
88
12
// /** @type {import('child_process').ChildProcessWithoutNullStreams } */
89
13
// let command;
90
- const commandArgs = commandString . split ( ' ' ) ;
14
+ const commandArgs = commandString . split ( " " ) ;
91
15
const command = spawn ( commandArgs [ 0 ] , commandArgs . slice ( 1 ) , {
92
16
detached : true ,
93
- stdio : ' pipe' ,
17
+ stdio : " pipe" ,
94
18
cwd : options . cwd || process . cwd ( ) ,
95
19
env : options . env || undefined ,
96
20
} ) ;
97
21
98
- let outputs = '' ;
22
+ let outputs = "" ;
99
23
let isFinishTypingCalled = false ;
100
24
101
- command . stdout . on ( ' data' , ( data ) => {
25
+ command . stdout . on ( " data" , ( data ) => {
102
26
if ( options . logData ) {
103
27
console . log ( data . toString ( ) ) ;
104
28
}
105
29
outputs += data . toString ( ) ;
106
30
} ) ;
107
31
108
- command . stderr . on ( ' data' , ( error ) => {
32
+ command . stderr . on ( " data" , ( error ) => {
109
33
if ( options . logData ) {
110
34
console . error ( error . toString ( ) ) ;
111
35
}
112
36
outputs += error . toString ( ) ;
113
37
} ) ;
114
38
115
- command . on ( ' error' , ( error ) => {
39
+ command . on ( " error" , ( error ) => {
116
40
throw error ;
117
41
} ) ;
118
42
119
43
const type = async ( text ) => {
120
44
if ( isFinishTypingCalled ) {
121
- throw new Error ( '[cli-testing-library]: `type` cannot be called after `getOutput` or `finishTyping`' ) ;
122
- } ;
45
+ throw new Error (
46
+ "[cli-testing-library]: `type` cannot be called after `getOutput` or `finishTyping`"
47
+ ) ;
48
+ }
123
49
124
50
await wait ( options . typeDelay ? options . typeDelay : 100 ) ;
125
51
@@ -131,8 +57,8 @@ const createCommandInterface = (commandString, options) => {
131
57
} ;
132
58
133
59
const keys = {
134
- enter : ( ) => type ( '\n' ) ,
135
- arrowDown : ( ) => type ( `${ STRING_ESC } 1B` ) ,
60
+ enter : ( ) => type ( "\n" ) ,
61
+ arrowDown : ( ) => type ( `${ STRING_ESC } 1B` ) ,
136
62
arrowUp : ( ) => type ( `${ STRING_ESC } 1A` ) ,
137
63
} ;
138
64
@@ -144,7 +70,7 @@ const createCommandInterface = (commandString, options) => {
144
70
finishTyping ( ) ;
145
71
}
146
72
return new Promise ( ( resolve ) => {
147
- command . stdout . on ( ' end' , ( ) => {
73
+ command . stdout . on ( " end" , ( ) => {
148
74
return resolve ( parseOutput ( outputs . trim ( ) ) ) ;
149
75
} ) ;
150
76
} ) ;
@@ -160,12 +86,11 @@ const createCommandInterface = (commandString, options) => {
160
86
finishTyping,
161
87
getOutput,
162
88
command,
163
- keys
89
+ keys,
164
90
} ;
165
91
} ;
166
92
167
93
module . exports = {
168
94
createCommandInterface,
169
95
parseOutput,
170
96
} ;
171
-
0 commit comments