node-pty 0.11.0-beta9 → 1.1.0-beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/binding.gyp +70 -29
- package/lib/conpty_console_list_agent.js.map +1 -1
- package/lib/terminal.js +10 -4
- package/lib/terminal.js.map +1 -1
- package/lib/terminal.test.js +3 -0
- package/lib/terminal.test.js.map +1 -1
- package/lib/unixTerminal.js +30 -4
- package/lib/unixTerminal.js.map +1 -1
- package/lib/unixTerminal.test.js +147 -18
- package/lib/unixTerminal.test.js.map +1 -1
- package/lib/windowsPtyAgent.js +13 -6
- package/lib/windowsPtyAgent.js.map +1 -1
- package/lib/windowsTerminal.js +21 -3
- package/lib/windowsTerminal.js.map +1 -1
- package/lib/windowsTerminal.test.js +98 -97
- package/lib/windowsTerminal.test.js.map +1 -1
- package/lib/worker/conoutSocketWorker.js +3 -0
- package/lib/worker/conoutSocketWorker.js.map +1 -1
- package/package.json +3 -4
- package/scripts/post-install.js +1 -0
- package/src/conpty_console_list_agent.ts +1 -1
- package/src/interfaces.ts +8 -2
- package/src/native.d.ts +6 -5
- package/src/terminal.test.ts +3 -0
- package/src/terminal.ts +21 -20
- package/src/tsconfig.json +1 -3
- package/src/unix/pty.cc +297 -205
- package/src/unix/spawn-helper.cc +23 -0
- package/src/unixTerminal.test.ts +117 -25
- package/src/unixTerminal.ts +31 -11
- package/src/win/conpty.cc +85 -38
- package/src/win/conpty_console_list.cc +3 -3
- package/src/win/path_util.cc +22 -3
- package/src/win/path_util.h +1 -0
- package/src/win/winpty.cc +118 -54
- package/src/windowsConoutConnection.ts +1 -1
- package/src/windowsPtyAgent.ts +16 -11
- package/src/windowsTerminal.test.ts +100 -99
- package/src/windowsTerminal.ts +23 -4
- package/src/worker/conoutSocketWorker.ts +4 -0
- package/typings/node-pty.d.ts +11 -20
- package/deps/winpty/.gitignore +0 -15
- package/deps/winpty/misc/.gitignore +0 -2
- package/scripts/install.js +0 -18
package/src/win/winpty.cc
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
#include <iostream>
|
|
12
|
+
#include <map>
|
|
12
13
|
#include <nan.h>
|
|
13
14
|
#include <Shlwapi.h> // PathCombine, PathIsRelative
|
|
14
15
|
#include <sstream>
|
|
@@ -37,21 +38,25 @@ static volatile LONG ptyCounter;
|
|
|
37
38
|
* Helpers
|
|
38
39
|
*/
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
/** Keeps track of the handles created by PtyStartProcess */
|
|
42
|
+
static std::map<DWORD, HANDLE> createdHandles;
|
|
43
|
+
|
|
44
|
+
static winpty_t *get_pipe_handle(DWORD pid) {
|
|
41
45
|
for (size_t i = 0; i < ptyHandles.size(); ++i) {
|
|
42
46
|
winpty_t *ptyHandle = ptyHandles[i];
|
|
43
|
-
|
|
44
|
-
if (current ==
|
|
47
|
+
HANDLE current = winpty_agent_process(ptyHandle);
|
|
48
|
+
if (GetProcessId(current) == pid) {
|
|
45
49
|
return ptyHandle;
|
|
46
50
|
}
|
|
47
51
|
}
|
|
48
52
|
return nullptr;
|
|
49
53
|
}
|
|
50
54
|
|
|
51
|
-
static bool remove_pipe_handle(
|
|
55
|
+
static bool remove_pipe_handle(DWORD pid) {
|
|
52
56
|
for (size_t i = 0; i < ptyHandles.size(); ++i) {
|
|
53
57
|
winpty_t *ptyHandle = ptyHandles[i];
|
|
54
|
-
|
|
58
|
+
HANDLE current = winpty_agent_process(ptyHandle);
|
|
59
|
+
if (GetProcessId(current) == pid) {
|
|
55
60
|
winpty_free(ptyHandle);
|
|
56
61
|
ptyHandles.erase(ptyHandles.begin() + i);
|
|
57
62
|
ptyHandle = nullptr;
|
|
@@ -62,11 +67,10 @@ static bool remove_pipe_handle(int handle) {
|
|
|
62
67
|
}
|
|
63
68
|
|
|
64
69
|
void throw_winpty_error(const char *generalMsg, winpty_error_ptr_t error_ptr) {
|
|
65
|
-
std::
|
|
70
|
+
std::wstringstream why;
|
|
66
71
|
std::wstring msg(winpty_error_msg(error_ptr));
|
|
67
|
-
|
|
68
|
-
why
|
|
69
|
-
Nan::ThrowError(why.str().c_str());
|
|
72
|
+
why << generalMsg << ": " << msg;
|
|
73
|
+
Nan::ThrowError(path_util::from_wstring(why.str().c_str()));
|
|
70
74
|
winpty_error_free(error_ptr);
|
|
71
75
|
}
|
|
72
76
|
|
|
@@ -75,13 +79,24 @@ static NAN_METHOD(PtyGetExitCode) {
|
|
|
75
79
|
|
|
76
80
|
if (info.Length() != 1 ||
|
|
77
81
|
!info[0]->IsNumber()) {
|
|
78
|
-
Nan::ThrowError("Usage: pty.getExitCode(
|
|
82
|
+
Nan::ThrowError("Usage: pty.getExitCode(pid)");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
DWORD pid = info[0]->Uint32Value(Nan::GetCurrentContext()).FromJust();
|
|
87
|
+
HANDLE handle = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
|
|
88
|
+
if (handle == NULL) {
|
|
89
|
+
info.GetReturnValue().Set(Nan::New<v8::Number>(-1));
|
|
79
90
|
return;
|
|
80
91
|
}
|
|
81
92
|
|
|
82
93
|
DWORD exitCode = 0;
|
|
83
|
-
GetExitCodeProcess(
|
|
94
|
+
BOOL success = GetExitCodeProcess(handle, &exitCode);
|
|
95
|
+
if (success == FALSE) {
|
|
96
|
+
exitCode = -1;
|
|
97
|
+
}
|
|
84
98
|
|
|
99
|
+
CloseHandle(handle);
|
|
85
100
|
info.GetReturnValue().Set(Nan::New<v8::Number>(exitCode));
|
|
86
101
|
}
|
|
87
102
|
|
|
@@ -94,8 +109,7 @@ static NAN_METHOD(PtyGetProcessList) {
|
|
|
94
109
|
return;
|
|
95
110
|
}
|
|
96
111
|
|
|
97
|
-
|
|
98
|
-
|
|
112
|
+
DWORD pid = info[0]->Uint32Value(Nan::GetCurrentContext()).FromJust();
|
|
99
113
|
winpty_t *pc = get_pipe_handle(pid);
|
|
100
114
|
if (pc == nullptr) {
|
|
101
115
|
info.GetReturnValue().Set(Nan::New<v8::Array>(0));
|
|
@@ -104,9 +118,13 @@ static NAN_METHOD(PtyGetProcessList) {
|
|
|
104
118
|
int processList[64];
|
|
105
119
|
const int processCount = 64;
|
|
106
120
|
int actualCount = winpty_get_console_process_list(pc, processList, processCount, nullptr);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
121
|
+
if (actualCount <= 0) {
|
|
122
|
+
info.GetReturnValue().Set(Nan::New<v8::Array>(0));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
uint32_t actualCountSize = static_cast<uint32_t>(actualCount);
|
|
126
|
+
v8::Local<v8::Array> result = Nan::New<v8::Array>(actualCountSize);
|
|
127
|
+
for (uint32_t i = 0; i < actualCountSize; i++) {
|
|
110
128
|
Nan::Set(result, i, Nan::New<v8::Number>(processList[i]));
|
|
111
129
|
}
|
|
112
130
|
info.GetReturnValue().Set(result);
|
|
@@ -127,8 +145,6 @@ static NAN_METHOD(PtyStartProcess) {
|
|
|
127
145
|
return;
|
|
128
146
|
}
|
|
129
147
|
|
|
130
|
-
std::stringstream why;
|
|
131
|
-
|
|
132
148
|
const wchar_t *filename = path_util::to_wstring(Nan::Utf8String(info[0]));
|
|
133
149
|
const wchar_t *cmdline = path_util::to_wstring(Nan::Utf8String(info[1]));
|
|
134
150
|
const wchar_t *cwd = path_util::to_wstring(Nan::Utf8String(info[3]));
|
|
@@ -157,12 +173,14 @@ static NAN_METHOD(PtyStartProcess) {
|
|
|
157
173
|
shellpath = filename;
|
|
158
174
|
}
|
|
159
175
|
|
|
160
|
-
std::string shellpath_(shellpath.begin(), shellpath.end());
|
|
161
|
-
|
|
162
176
|
if (shellpath.empty() || !path_util::file_exists(shellpath)) {
|
|
163
|
-
why
|
|
164
|
-
|
|
165
|
-
|
|
177
|
+
std::wstringstream why;
|
|
178
|
+
why << "File not found: " << shellpath;
|
|
179
|
+
Nan::ThrowError(path_util::from_wstring(why.str().c_str()));
|
|
180
|
+
delete filename;
|
|
181
|
+
delete cmdline;
|
|
182
|
+
delete cwd;
|
|
183
|
+
return;
|
|
166
184
|
}
|
|
167
185
|
|
|
168
186
|
int cols = info[4]->Int32Value(Nan::GetCurrentContext()).FromJust();
|
|
@@ -177,7 +195,10 @@ static NAN_METHOD(PtyStartProcess) {
|
|
|
177
195
|
winpty_config_t* winpty_config = winpty_config_new(0, &error_ptr);
|
|
178
196
|
if (winpty_config == nullptr) {
|
|
179
197
|
throw_winpty_error("Error creating WinPTY config", error_ptr);
|
|
180
|
-
|
|
198
|
+
delete filename;
|
|
199
|
+
delete cmdline;
|
|
200
|
+
delete cwd;
|
|
201
|
+
return;
|
|
181
202
|
}
|
|
182
203
|
winpty_error_free(error_ptr);
|
|
183
204
|
|
|
@@ -189,18 +210,22 @@ static NAN_METHOD(PtyStartProcess) {
|
|
|
189
210
|
winpty_config_free(winpty_config);
|
|
190
211
|
if (pc == nullptr) {
|
|
191
212
|
throw_winpty_error("Error launching WinPTY agent", error_ptr);
|
|
192
|
-
|
|
213
|
+
delete filename;
|
|
214
|
+
delete cmdline;
|
|
215
|
+
delete cwd;
|
|
216
|
+
return;
|
|
193
217
|
}
|
|
194
218
|
winpty_error_free(error_ptr);
|
|
195
219
|
|
|
196
|
-
// Save pty struct for later use
|
|
197
|
-
ptyHandles.insert(ptyHandles.end(), pc);
|
|
198
|
-
|
|
199
220
|
// Create winpty spawn config
|
|
200
221
|
winpty_spawn_config_t* config = winpty_spawn_config_new(WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, shellpath.c_str(), cmdline, cwd, env.c_str(), &error_ptr);
|
|
201
222
|
if (config == nullptr) {
|
|
202
223
|
throw_winpty_error("Error creating WinPTY spawn config", error_ptr);
|
|
203
|
-
|
|
224
|
+
winpty_free(pc);
|
|
225
|
+
delete filename;
|
|
226
|
+
delete cmdline;
|
|
227
|
+
delete cwd;
|
|
228
|
+
return;
|
|
204
229
|
}
|
|
205
230
|
winpty_error_free(error_ptr);
|
|
206
231
|
|
|
@@ -210,32 +235,68 @@ static NAN_METHOD(PtyStartProcess) {
|
|
|
210
235
|
winpty_spawn_config_free(config);
|
|
211
236
|
if (!spawnSuccess) {
|
|
212
237
|
throw_winpty_error("Unable to start terminal process", error_ptr);
|
|
213
|
-
|
|
238
|
+
if (handle) {
|
|
239
|
+
CloseHandle(handle);
|
|
240
|
+
}
|
|
241
|
+
winpty_free(pc);
|
|
242
|
+
delete filename;
|
|
243
|
+
delete cmdline;
|
|
244
|
+
delete cwd;
|
|
245
|
+
return;
|
|
214
246
|
}
|
|
215
247
|
winpty_error_free(error_ptr);
|
|
216
248
|
|
|
217
|
-
|
|
249
|
+
LPCWSTR coninPipeName = winpty_conin_name(pc);
|
|
250
|
+
std::string coninPipeNameStr(path_util::from_wstring(coninPipeName));
|
|
251
|
+
if (coninPipeNameStr.empty()) {
|
|
252
|
+
Nan::ThrowError("Failed to initialize winpty conin");
|
|
253
|
+
CloseHandle(handle);
|
|
254
|
+
winpty_free(pc);
|
|
255
|
+
delete filename;
|
|
256
|
+
delete cmdline;
|
|
257
|
+
delete cwd;
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
LPCWSTR conoutPipeName = winpty_conout_name(pc);
|
|
262
|
+
std::string conoutPipeNameStr(path_util::from_wstring(conoutPipeName));
|
|
263
|
+
if (conoutPipeNameStr.empty()) {
|
|
264
|
+
Nan::ThrowError("Failed to initialize winpty conout");
|
|
265
|
+
CloseHandle(handle);
|
|
266
|
+
winpty_free(pc);
|
|
267
|
+
delete filename;
|
|
268
|
+
delete cmdline;
|
|
269
|
+
delete cwd;
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
DWORD innerPid = GetProcessId(handle);
|
|
274
|
+
if (createdHandles[innerPid]) {
|
|
275
|
+
std::stringstream why;
|
|
276
|
+
why << "There is already a process with innerPid " << innerPid;
|
|
277
|
+
Nan::ThrowError(why.str().c_str());
|
|
278
|
+
CloseHandle(handle);
|
|
279
|
+
winpty_free(pc);
|
|
280
|
+
delete filename;
|
|
281
|
+
delete cmdline;
|
|
282
|
+
delete cwd;
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
createdHandles[innerPid] = handle;
|
|
286
|
+
|
|
287
|
+
// Save pty struct for later use
|
|
288
|
+
ptyHandles.push_back(pc);
|
|
289
|
+
|
|
218
290
|
v8::Local<v8::Object> marshal = Nan::New<v8::Object>();
|
|
219
|
-
|
|
220
|
-
Nan::Set(marshal, Nan::New<v8::String>("
|
|
221
|
-
Nan::Set(marshal, Nan::New<v8::String>("pid").ToLocalChecked(), Nan::New<v8::Number>((
|
|
291
|
+
DWORD pid = GetProcessId(winpty_agent_process(pc));
|
|
292
|
+
Nan::Set(marshal, Nan::New<v8::String>("innerPid").ToLocalChecked(), Nan::New<v8::Number>(static_cast<double>(innerPid)));
|
|
293
|
+
Nan::Set(marshal, Nan::New<v8::String>("pid").ToLocalChecked(), Nan::New<v8::Number>(static_cast<double>(pid)));
|
|
222
294
|
Nan::Set(marshal, Nan::New<v8::String>("pty").ToLocalChecked(), Nan::New<v8::Number>(InterlockedIncrement(&ptyCounter)));
|
|
223
295
|
Nan::Set(marshal, Nan::New<v8::String>("fd").ToLocalChecked(), Nan::New<v8::Number>(-1));
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
std::wstring coninPipeNameWStr(coninPipeName);
|
|
227
|
-
std::string coninPipeNameStr(coninPipeNameWStr.begin(), coninPipeNameWStr.end());
|
|
228
|
-
Nan::Set(marshal, Nan::New<v8::String>("conin").ToLocalChecked(), Nan::New<v8::String>(coninPipeNameStr).ToLocalChecked());
|
|
229
|
-
LPCWSTR conoutPipeName = winpty_conout_name(pc);
|
|
230
|
-
std::wstring conoutPipeNameWStr(conoutPipeName);
|
|
231
|
-
std::string conoutPipeNameStr(conoutPipeNameWStr.begin(), conoutPipeNameWStr.end());
|
|
232
|
-
Nan::Set(marshal, Nan::New<v8::String>("conout").ToLocalChecked(), Nan::New<v8::String>(conoutPipeNameStr).ToLocalChecked());
|
|
233
|
-
}
|
|
296
|
+
Nan::Set(marshal, Nan::New<v8::String>("conin").ToLocalChecked(), Nan::New<v8::String>(coninPipeNameStr).ToLocalChecked());
|
|
297
|
+
Nan::Set(marshal, Nan::New<v8::String>("conout").ToLocalChecked(), Nan::New<v8::String>(conoutPipeNameStr).ToLocalChecked());
|
|
234
298
|
info.GetReturnValue().Set(marshal);
|
|
235
299
|
|
|
236
|
-
goto cleanup;
|
|
237
|
-
|
|
238
|
-
cleanup:
|
|
239
300
|
delete filename;
|
|
240
301
|
delete cmdline;
|
|
241
302
|
delete cwd;
|
|
@@ -252,11 +313,11 @@ static NAN_METHOD(PtyResize) {
|
|
|
252
313
|
return;
|
|
253
314
|
}
|
|
254
315
|
|
|
255
|
-
|
|
316
|
+
DWORD pid = info[0]->Uint32Value(Nan::GetCurrentContext()).FromJust();
|
|
256
317
|
int cols = info[1]->Int32Value(Nan::GetCurrentContext()).FromJust();
|
|
257
318
|
int rows = info[2]->Int32Value(Nan::GetCurrentContext()).FromJust();
|
|
258
319
|
|
|
259
|
-
winpty_t *pc = get_pipe_handle(
|
|
320
|
+
winpty_t *pc = get_pipe_handle(pid);
|
|
260
321
|
|
|
261
322
|
if (pc == nullptr) {
|
|
262
323
|
Nan::ThrowError("The pty doesn't appear to exist");
|
|
@@ -277,20 +338,23 @@ static NAN_METHOD(PtyKill) {
|
|
|
277
338
|
if (info.Length() != 2 ||
|
|
278
339
|
!info[0]->IsNumber() ||
|
|
279
340
|
!info[1]->IsNumber()) {
|
|
280
|
-
Nan::ThrowError("Usage: pty.kill(pid,
|
|
341
|
+
Nan::ThrowError("Usage: pty.kill(pid, innerPid)");
|
|
281
342
|
return;
|
|
282
343
|
}
|
|
283
344
|
|
|
284
|
-
|
|
285
|
-
|
|
345
|
+
DWORD pid = info[0]->Uint32Value(Nan::GetCurrentContext()).FromJust();
|
|
346
|
+
DWORD innerPid = info[1]->Uint32Value(Nan::GetCurrentContext()).FromJust();
|
|
286
347
|
|
|
287
|
-
winpty_t *pc = get_pipe_handle(
|
|
348
|
+
winpty_t *pc = get_pipe_handle(pid);
|
|
288
349
|
if (pc == nullptr) {
|
|
289
350
|
Nan::ThrowError("Pty seems to have been killed already");
|
|
290
351
|
return;
|
|
291
352
|
}
|
|
292
353
|
|
|
293
|
-
assert(remove_pipe_handle(
|
|
354
|
+
assert(remove_pipe_handle(pid));
|
|
355
|
+
|
|
356
|
+
HANDLE innerPidHandle = createdHandles[innerPid];
|
|
357
|
+
createdHandles.erase(innerPid);
|
|
294
358
|
CloseHandle(innerPidHandle);
|
|
295
359
|
|
|
296
360
|
return info.GetReturnValue().SetUndefined();
|
|
@@ -30,7 +30,7 @@ const FLUSH_DATA_INTERVAL = 1000;
|
|
|
30
30
|
*/
|
|
31
31
|
export class ConoutConnection implements IDisposable {
|
|
32
32
|
private _worker: Worker;
|
|
33
|
-
private _drainTimeout: NodeJS.Timeout;
|
|
33
|
+
private _drainTimeout: NodeJS.Timeout | undefined;
|
|
34
34
|
private _isDisposed: boolean = false;
|
|
35
35
|
|
|
36
36
|
private _onReady = new EventEmitter2<void>();
|
package/src/windowsPtyAgent.ts
CHANGED
|
@@ -29,10 +29,9 @@ const FLUSH_DATA_INTERVAL = 1000;
|
|
|
29
29
|
export class WindowsPtyAgent {
|
|
30
30
|
private _inSocket: Socket;
|
|
31
31
|
private _outSocket: Socket;
|
|
32
|
-
private _pid: number;
|
|
33
|
-
private _innerPid: number;
|
|
34
|
-
private
|
|
35
|
-
private _closeTimeout: NodeJS.Timer;
|
|
32
|
+
private _pid: number = 0;
|
|
33
|
+
private _innerPid: number = 0;
|
|
34
|
+
private _closeTimeout: NodeJS.Timer | undefined;
|
|
36
35
|
private _exitCode: number | undefined;
|
|
37
36
|
private _conoutSocketWorker: ConoutConnection;
|
|
38
37
|
|
|
@@ -105,7 +104,6 @@ export class WindowsPtyAgent {
|
|
|
105
104
|
term = (this._ptyNative as IWinptyNative).startProcess(file, commandLine, env, cwd, cols, rows, debug);
|
|
106
105
|
this._pid = (term as IWinptyProcess).pid;
|
|
107
106
|
this._innerPid = (term as IWinptyProcess).innerPid;
|
|
108
|
-
this._innerPidHandle = (term as IWinptyProcess).innerPidHandle;
|
|
109
107
|
}
|
|
110
108
|
|
|
111
109
|
// Not available on windows.
|
|
@@ -152,6 +150,12 @@ export class WindowsPtyAgent {
|
|
|
152
150
|
this._ptyNative.resize(this._pid, cols, rows);
|
|
153
151
|
}
|
|
154
152
|
|
|
153
|
+
public clear(): void {
|
|
154
|
+
if (this._useConpty) {
|
|
155
|
+
(this._ptyNative as IConptyNative).clear(this._pty);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
155
159
|
public kill(): void {
|
|
156
160
|
this._inSocket.readable = false;
|
|
157
161
|
this._outSocket.readable = false;
|
|
@@ -168,14 +172,14 @@ export class WindowsPtyAgent {
|
|
|
168
172
|
(this._ptyNative as IConptyNative).kill(this._pty);
|
|
169
173
|
});
|
|
170
174
|
} else {
|
|
171
|
-
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
// dropped, we want to immediately kill the entire console process list.
|
|
175
|
+
// Because pty.kill closes the handle, it will kill most processes by itself.
|
|
176
|
+
// Process IDs can be reused as soon as all handles to them are
|
|
177
|
+
// dropped, so we want to immediately kill the entire console process list.
|
|
175
178
|
// If we do not force kill all processes here, node servers in particular
|
|
176
179
|
// seem to become detached and remain running (see
|
|
177
180
|
// Microsoft/vscode#26807).
|
|
178
181
|
const processList: number[] = (this._ptyNative as IWinptyNative).getProcessList(this._pid);
|
|
182
|
+
(this._ptyNative as IWinptyNative).kill(this._pid, this._innerPid);
|
|
179
183
|
processList.forEach(pid => {
|
|
180
184
|
try {
|
|
181
185
|
process.kill(pid);
|
|
@@ -202,11 +206,12 @@ export class WindowsPtyAgent {
|
|
|
202
206
|
});
|
|
203
207
|
}
|
|
204
208
|
|
|
205
|
-
public get exitCode(): number {
|
|
209
|
+
public get exitCode(): number | undefined {
|
|
206
210
|
if (this._useConpty) {
|
|
207
211
|
return this._exitCode;
|
|
208
212
|
}
|
|
209
|
-
|
|
213
|
+
const winptyExitCode = (this._ptyNative as IWinptyNative).getExitCode(this._innerPid);
|
|
214
|
+
return winptyExitCode === -1 ? undefined : winptyExitCode;
|
|
210
215
|
}
|
|
211
216
|
|
|
212
217
|
private _getWindowsBuildNumber(): number {
|
|
@@ -65,7 +65,7 @@ function pollForProcessTreeSize(pid: number, size: number, intervalMs: number =
|
|
|
65
65
|
})[0]);
|
|
66
66
|
const list: IWindowsProcessTreeResult[] = [];
|
|
67
67
|
while (openList.length) {
|
|
68
|
-
const current = openList.shift()
|
|
68
|
+
const current = openList.shift()!;
|
|
69
69
|
ps.filter(p => p.ppid === current.pid).map(p => {
|
|
70
70
|
return { name: p.name, pid: p.pid };
|
|
71
71
|
}).forEach(p => openList.push(p));
|
|
@@ -81,7 +81,6 @@ function pollForProcessTreeSize(pid: number, size: number, intervalMs: number =
|
|
|
81
81
|
if (tries * intervalMs >= timeoutMs) {
|
|
82
82
|
clearInterval(interval);
|
|
83
83
|
assert.fail(`Bad process state, expected: ${size}, actual: ${list.length}`);
|
|
84
|
-
resolve();
|
|
85
84
|
}
|
|
86
85
|
});
|
|
87
86
|
}, intervalMs);
|
|
@@ -89,125 +88,127 @@ function pollForProcessTreeSize(pid: number, size: number, intervalMs: number =
|
|
|
89
88
|
}
|
|
90
89
|
|
|
91
90
|
if (process.platform === 'win32') {
|
|
92
|
-
|
|
93
|
-
describe(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
// Add done call to deferred function queue to ensure the kill call has completed
|
|
98
|
-
(<any>term)._defer(done);
|
|
99
|
-
});
|
|
100
|
-
it('should kill the process tree', function (done: Mocha.Done): void {
|
|
101
|
-
this.timeout(5000);
|
|
102
|
-
const term = new WindowsTerminal('cmd.exe', [], {});
|
|
103
|
-
// Start sub-processes
|
|
104
|
-
term.write('powershell.exe\r');
|
|
105
|
-
term.write('notepad.exe\r');
|
|
106
|
-
term.write('node.exe\r');
|
|
107
|
-
pollForProcessTreeSize(term.pid, 4, 500, 5000).then(list => {
|
|
108
|
-
assert.equal(list[0].name, 'cmd.exe');
|
|
109
|
-
assert.equal(list[1].name, 'powershell.exe');
|
|
110
|
-
assert.equal(list[2].name, 'notepad.exe');
|
|
111
|
-
assert.equal(list[3].name, 'node.exe');
|
|
91
|
+
[true, false].forEach((useConpty) => {
|
|
92
|
+
describe(`WindowsTerminal (useConpty = ${useConpty})`, () => {
|
|
93
|
+
describe('kill', () => {
|
|
94
|
+
it('should not crash parent process', (done) => {
|
|
95
|
+
const term = new WindowsTerminal('cmd.exe', [], { useConpty });
|
|
112
96
|
term.kill();
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
97
|
+
// Add done call to deferred function queue to ensure the kill call has completed
|
|
98
|
+
(<any>term)._defer(done);
|
|
99
|
+
});
|
|
100
|
+
it('should kill the process tree', function (done: Mocha.Done): void {
|
|
101
|
+
this.timeout(5000);
|
|
102
|
+
const term = new WindowsTerminal('cmd.exe', [], { useConpty });
|
|
103
|
+
// Start sub-processes
|
|
104
|
+
term.write('powershell.exe\r');
|
|
105
|
+
term.write('notepad.exe\r');
|
|
106
|
+
term.write('node.exe\r');
|
|
107
|
+
pollForProcessTreeSize(term.pid, 4, 500, 5000).then(list => {
|
|
108
|
+
assert.strictEqual(list[0].name.toLowerCase(), 'cmd.exe');
|
|
109
|
+
assert.strictEqual(list[1].name.toLowerCase(), 'powershell.exe');
|
|
110
|
+
assert.strictEqual(list[2].name.toLowerCase(), 'notepad.exe');
|
|
111
|
+
assert.strictEqual(list[3].name.toLowerCase(), 'node.exe');
|
|
112
|
+
term.kill();
|
|
113
|
+
const desiredState: IProcessState = {};
|
|
114
|
+
desiredState[list[0].pid] = false;
|
|
115
|
+
desiredState[list[1].pid] = false;
|
|
116
|
+
desiredState[list[2].pid] = true;
|
|
117
|
+
desiredState[list[3].pid] = false;
|
|
118
|
+
pollForProcessState(desiredState).then(() => {
|
|
119
|
+
// Kill notepad before done
|
|
120
|
+
process.kill(list[2].pid);
|
|
121
|
+
done();
|
|
122
|
+
});
|
|
122
123
|
});
|
|
123
124
|
});
|
|
124
125
|
});
|
|
125
|
-
});
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
127
|
+
describe('resize', () => {
|
|
128
|
+
it('should throw a non-native exception when resizing an invalid value', () => {
|
|
129
|
+
const term = new WindowsTerminal('cmd.exe', [], { useConpty });
|
|
130
|
+
assert.throws(() => term.resize(-1, -1));
|
|
131
|
+
assert.throws(() => term.resize(0, 0));
|
|
132
|
+
assert.doesNotThrow(() => term.resize(1, 1));
|
|
133
|
+
});
|
|
134
|
+
it('should throw a non-native exception when resizing a killed terminal', (done) => {
|
|
135
|
+
const term = new WindowsTerminal('cmd.exe', [], { useConpty });
|
|
136
|
+
(<any>term)._defer(() => {
|
|
137
|
+
term.on('exit', () => {
|
|
138
|
+
assert.throws(() => term.resize(1, 1));
|
|
139
|
+
done();
|
|
140
|
+
});
|
|
141
|
+
term.destroy();
|
|
140
142
|
});
|
|
141
|
-
term.destroy();
|
|
142
143
|
});
|
|
143
144
|
});
|
|
144
|
-
});
|
|
145
145
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
146
|
+
describe('Args as CommandLine', () => {
|
|
147
|
+
it('should not fail running a file containing a space in the path', (done) => {
|
|
148
|
+
const spaceFolder = path.resolve(__dirname, '..', 'fixtures', 'space folder');
|
|
149
|
+
if (!fs.existsSync(spaceFolder)) {
|
|
150
|
+
fs.mkdirSync(spaceFolder);
|
|
151
|
+
}
|
|
152
152
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
153
|
+
const cmdCopiedPath = path.resolve(spaceFolder, 'cmd.exe');
|
|
154
|
+
const data = fs.readFileSync(`${process.env.windir}\\System32\\cmd.exe`);
|
|
155
|
+
fs.writeFileSync(cmdCopiedPath, data);
|
|
156
156
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
157
|
+
if (!fs.existsSync(cmdCopiedPath)) {
|
|
158
|
+
// Skip test if git bash isn't installed
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const term = new WindowsTerminal(cmdCopiedPath, '/c echo "hello world"', { useConpty });
|
|
162
|
+
let result = '';
|
|
163
|
+
term.on('data', (data) => {
|
|
164
|
+
result += data;
|
|
165
|
+
});
|
|
166
|
+
term.on('exit', () => {
|
|
167
|
+
assert.ok(result.indexOf('hello world') >= 1);
|
|
168
|
+
done();
|
|
169
|
+
});
|
|
169
170
|
});
|
|
170
171
|
});
|
|
171
|
-
});
|
|
172
172
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
173
|
+
describe('env', () => {
|
|
174
|
+
it('should set environment variables of the shell', (done) => {
|
|
175
|
+
const term = new WindowsTerminal('cmd.exe', '/C echo %FOO%', { env: { FOO: 'BAR' }});
|
|
176
|
+
let result = '';
|
|
177
|
+
term.on('data', (data) => {
|
|
178
|
+
result += data;
|
|
179
|
+
});
|
|
180
|
+
term.on('exit', () => {
|
|
181
|
+
assert.ok(result.indexOf('BAR') >= 0);
|
|
182
|
+
done();
|
|
183
|
+
});
|
|
183
184
|
});
|
|
184
185
|
});
|
|
185
|
-
});
|
|
186
186
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
187
|
+
describe('On close', () => {
|
|
188
|
+
it('should return process zero exit codes', (done) => {
|
|
189
|
+
const term = new WindowsTerminal('cmd.exe', '/C exit', { useConpty });
|
|
190
|
+
term.on('exit', (code) => {
|
|
191
|
+
assert.strictEqual(code, 0);
|
|
192
|
+
done();
|
|
193
|
+
});
|
|
193
194
|
});
|
|
194
|
-
});
|
|
195
195
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
196
|
+
it('should return process non-zero exit codes', (done) => {
|
|
197
|
+
const term = new WindowsTerminal('cmd.exe', '/C exit 2', { useConpty });
|
|
198
|
+
term.on('exit', (code) => {
|
|
199
|
+
assert.strictEqual(code, 2);
|
|
200
|
+
done();
|
|
201
|
+
});
|
|
201
202
|
});
|
|
202
203
|
});
|
|
203
|
-
});
|
|
204
204
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
205
|
+
describe('Write', () => {
|
|
206
|
+
it('should accept input', (done) => {
|
|
207
|
+
const term = new WindowsTerminal('cmd.exe', '', { useConpty });
|
|
208
|
+
term.write('exit\r');
|
|
209
|
+
term.on('exit', () => {
|
|
210
|
+
done();
|
|
211
|
+
});
|
|
211
212
|
});
|
|
212
213
|
});
|
|
213
214
|
});
|