@@ -79,72 +79,100 @@ export class TerminalProcess extends Process {
79
79
}
80
80
this . logger . debug ( 'Starting terminal process' , JSON . stringify ( options , undefined , 2 ) ) ;
81
81
82
- try {
83
- this . terminal = spawn (
84
- options . command ,
85
- ( isWindows && options . commandLine ) || options . args || [ ] ,
86
- options . options || { }
87
- ) ;
88
-
89
- process . nextTick ( ( ) => this . emitOnStarted ( ) ) ;
90
-
91
- // node-pty actually wait for the underlying streams to be closed before emitting exit.
92
- // We should emulate the `exit` and `close` sequence.
93
- this . terminal . on ( 'exit' , ( code , signal ) => {
94
- // Make sure to only pass either code or signal as !undefined, not
95
- // both.
96
- //
97
- // node-pty quirk: On Linux/macOS, if the process exited through the
98
- // exit syscall (with an exit code), signal will be 0 (an invalid
99
- // signal value). If it was terminated because of a signal, the
100
- // signal parameter will hold the signal number and code should
101
- // be ignored.
102
- if ( signal === undefined || signal === 0 ) {
103
- this . onTerminalExit ( code , undefined ) ;
104
- } else {
105
- this . onTerminalExit ( undefined , signame ( signal ) ) ;
106
- }
107
- process . nextTick ( ( ) => {
108
- if ( signal === undefined || signal === 0 ) {
109
- this . emitOnClose ( code , undefined ) ;
110
- } else {
111
- this . emitOnClose ( undefined , signame ( signal ) ) ;
82
+ const startTerminal = ( command : string ) : { terminal : IPty | undefined , inputStream : Writable } => {
83
+ try {
84
+ return this . createPseudoTerminal ( command , options , ringBuffer ) ;
85
+ } catch ( error ) {
86
+ // Normalize the error to make it as close as possible as what
87
+ // node's child_process.spawn would generate in the same
88
+ // situation.
89
+ const message : string = error . message ;
90
+
91
+ if ( message . startsWith ( 'File not found: ' ) || message . endsWith ( NodePtyErrors . ENOENT ) ) {
92
+ if ( isWindows && command && ! command . toLowerCase ( ) . endsWith ( '.exe' ) ) {
93
+ const commandExe = command + '.exe' ;
94
+ this . logger . debug ( `Trying terminal command '${ commandExe } ' because '${ command } ' was not found.` ) ;
95
+ return startTerminal ( commandExe ) ;
112
96
}
113
- } ) ;
114
- } ) ;
115
97
116
- this . terminal . on ( 'data' , ( data : string ) => {
117
- ringBuffer . enq ( data ) ;
118
- } ) ;
98
+ // Proceed with failure, reporting the original command because it was
99
+ // the intended command and it was not found
100
+ error . errno = 'ENOENT' ;
101
+ error . code = 'ENOENT' ;
102
+ error . path = options . command ;
103
+ } else if ( message . endsWith ( NodePtyErrors . EACCES ) ) {
104
+ // The shell program exists but was not accessible, so just fail
105
+ error . errno = 'EACCES' ;
106
+ error . code = 'EACCES' ;
107
+ error . path = options . command ;
108
+ }
119
109
120
- this . inputStream = new Writable ( {
121
- write : ( chunk : string ) => {
122
- this . write ( chunk ) ;
123
- } ,
124
- } ) ;
110
+ // node-pty throws exceptions on Windows.
111
+ // Call the client error handler, but first give them a chance to register it.
112
+ this . emitOnErrorAsync ( error ) ;
125
113
126
- } catch ( error ) {
127
- this . inputStream = new DevNullStream ( { autoDestroy : true } ) ;
114
+ return { terminal : undefined , inputStream : new DevNullStream ( { autoDestroy : true } ) } ;
115
+ }
116
+ } ;
117
+
118
+ const { terminal, inputStream } = startTerminal ( options . command ) ;
119
+ this . terminal = terminal ;
120
+ this . inputStream = inputStream ;
121
+ }
128
122
129
- // Normalize the error to make it as close as possible as what
130
- // node's child_process.spawn would generate in the same
131
- // situation.
132
- const message : string = error . message ;
133
-
134
- if ( message . startsWith ( 'File not found: ' ) || message . endsWith ( NodePtyErrors . ENOENT ) ) {
135
- error . errno = 'ENOENT' ;
136
- error . code = 'ENOENT' ;
137
- error . path = options . command ;
138
- } else if ( message . endsWith ( NodePtyErrors . EACCES ) ) {
139
- error . errno = 'EACCES' ;
140
- error . code = 'EACCES' ;
141
- error . path = options . command ;
123
+ /**
124
+ * Helper for the constructor to attempt to create the pseudo-terminal encapsulating the shell process.
125
+ *
126
+ * @param command the shell command to launch
127
+ * @param options options for the shell process
128
+ * @param ringBuffer a ring buffer in which to collect terminal output
129
+ * @returns the terminal PTY and a stream by which it may be sent input
130
+ */
131
+ private createPseudoTerminal ( command : string , options : TerminalProcessOptions , ringBuffer : MultiRingBuffer ) : { terminal : IPty | undefined , inputStream : Writable } {
132
+ const terminal = spawn (
133
+ command ,
134
+ ( isWindows && options . commandLine ) || options . args || [ ] ,
135
+ options . options || { }
136
+ ) ;
137
+
138
+ process . nextTick ( ( ) => this . emitOnStarted ( ) ) ;
139
+
140
+ // node-pty actually wait for the underlying streams to be closed before emitting exit.
141
+ // We should emulate the `exit` and `close` sequence.
142
+ terminal . onExit ( ( { exitCode, signal } ) => {
143
+ // Make sure to only pass either code or signal as !undefined, not
144
+ // both.
145
+ //
146
+ // node-pty quirk: On Linux/macOS, if the process exited through the
147
+ // exit syscall (with an exit code), signal will be 0 (an invalid
148
+ // signal value). If it was terminated because of a signal, the
149
+ // signal parameter will hold the signal number and code should
150
+ // be ignored.
151
+ if ( signal === undefined || signal === 0 ) {
152
+ this . onTerminalExit ( exitCode , undefined ) ;
153
+ } else {
154
+ this . onTerminalExit ( undefined , signame ( signal ) ) ;
142
155
}
156
+ process . nextTick ( ( ) => {
157
+ if ( signal === undefined || signal === 0 ) {
158
+ this . emitOnClose ( exitCode , undefined ) ;
159
+ } else {
160
+ this . emitOnClose ( undefined , signame ( signal ) ) ;
161
+ }
162
+ } ) ;
163
+ } ) ;
143
164
144
- // node-pty throws exceptions on Windows.
145
- // Call the client error handler, but first give them a chance to register it.
146
- this . emitOnErrorAsync ( error ) ;
147
- }
165
+ terminal . onData ( ( data : string ) => {
166
+ ringBuffer . enq ( data ) ;
167
+ } ) ;
168
+
169
+ const inputStream = new Writable ( {
170
+ write : ( chunk : string ) => {
171
+ this . write ( chunk ) ;
172
+ } ,
173
+ } ) ;
174
+
175
+ return { terminal, inputStream } ;
148
176
}
149
177
150
178
createOutputStream ( ) : MultiRingBufferReadableStream {
0 commit comments