node-pty 0.11.0-beta8 → 1.0.0

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.
Files changed (42) hide show
  1. package/README.md +6 -4
  2. package/binding.gyp +68 -29
  3. package/lib/conpty_console_list_agent.js.map +1 -1
  4. package/lib/terminal.js +10 -4
  5. package/lib/terminal.js.map +1 -1
  6. package/lib/terminal.test.js +3 -0
  7. package/lib/terminal.test.js.map +1 -1
  8. package/lib/unixTerminal.js +30 -4
  9. package/lib/unixTerminal.js.map +1 -1
  10. package/lib/unixTerminal.test.js +147 -18
  11. package/lib/unixTerminal.test.js.map +1 -1
  12. package/lib/windowsPtyAgent.js +8 -0
  13. package/lib/windowsPtyAgent.js.map +1 -1
  14. package/lib/windowsTerminal.js +21 -3
  15. package/lib/windowsTerminal.js.map +1 -1
  16. package/lib/windowsTerminal.test.js +4 -5
  17. package/lib/windowsTerminal.test.js.map +1 -1
  18. package/lib/worker/conoutSocketWorker.js +3 -0
  19. package/lib/worker/conoutSocketWorker.js.map +1 -1
  20. package/package.json +3 -4
  21. package/scripts/post-install.js +1 -0
  22. package/src/conpty_console_list_agent.ts +1 -1
  23. package/src/interfaces.ts +8 -2
  24. package/src/native.d.ts +3 -1
  25. package/src/terminal.test.ts +3 -0
  26. package/src/terminal.ts +21 -20
  27. package/src/tsconfig.json +1 -3
  28. package/src/unix/pty.cc +297 -205
  29. package/src/unix/spawn-helper.cc +23 -0
  30. package/src/unixTerminal.test.ts +117 -25
  31. package/src/unixTerminal.ts +31 -11
  32. package/src/win/conpty.cc +65 -22
  33. package/src/win/path_util.cc +1 -1
  34. package/src/windowsConoutConnection.ts +1 -1
  35. package/src/windowsPtyAgent.ts +11 -5
  36. package/src/windowsTerminal.test.ts +5 -6
  37. package/src/windowsTerminal.ts +23 -4
  38. package/src/worker/conoutSocketWorker.ts +4 -0
  39. package/typings/node-pty.d.ts +11 -20
  40. package/deps/winpty/.gitignore +0 -15
  41. package/deps/winpty/misc/.gitignore +0 -2
  42. package/scripts/install.js +0 -18
@@ -0,0 +1,23 @@
1
+ #include <errno.h>
2
+ #include <fcntl.h>
3
+ #include <string.h>
4
+ #include <unistd.h>
5
+
6
+ int main (int argc, char** argv) {
7
+ char *slave_path = ttyname(STDIN_FILENO);
8
+ // open implicit attaches a process to a terminal device if:
9
+ // - process has no controlling terminal yet
10
+ // - O_NOCTTY is not set
11
+ close(open(slave_path, O_RDWR));
12
+
13
+ char *cwd = argv[1];
14
+ char *file = argv[2];
15
+ argv = &argv[2];
16
+
17
+ if (strlen(cwd) && chdir(cwd) == -1) {
18
+ _exit(1);
19
+ }
20
+
21
+ execvp(file, argv);
22
+ return 1;
23
+ }
@@ -7,6 +7,9 @@ import { UnixTerminal } from './unixTerminal';
7
7
  import * as assert from 'assert';
8
8
  import * as cp from 'child_process';
9
9
  import * as path from 'path';
10
+ import * as tty from 'tty';
11
+ import * as fs from 'fs';
12
+ import { constants } from 'os';
10
13
  import { pollUntil } from './testUtils.test';
11
14
 
12
15
  const FIXTURES_PATH = path.normalize(path.join(__dirname, '..', 'fixtures', 'utf8-character.txt'));
@@ -16,7 +19,7 @@ if (process.platform !== 'win32') {
16
19
  describe('Constructor', () => {
17
20
  it('should set a valid pts name', () => {
18
21
  const term = new UnixTerminal('/bin/bash', [], {});
19
- let regExp: RegExp;
22
+ let regExp: RegExp | undefined;
20
23
  if (process.platform === 'linux') {
21
24
  // https://linux.die.net/man/4/pts
22
25
  regExp = /^\/dev\/pts\/\d+$/;
@@ -26,8 +29,9 @@ if (process.platform !== 'win32') {
26
29
  regExp = /^\/dev\/tty[p-sP-S][a-z0-9]+$/;
27
30
  }
28
31
  if (regExp) {
29
- assert.ok(regExp.test((<any>term)._pty), '"' + (<any>term)._pty + '" should match ' + regExp.toString());
32
+ assert.ok(regExp.test(term.ptsName), '"' + term.ptsName + '" should match ' + regExp.toString());
30
33
  }
34
+ assert.ok(tty.isatty(term.fd));
31
35
  });
32
36
  });
33
37
 
@@ -35,8 +39,8 @@ if (process.platform !== 'win32') {
35
39
  it('should default to utf8', (done) => {
36
40
  const term = new UnixTerminal('/bin/bash', [ '-c', `cat "${FIXTURES_PATH}"` ]);
37
41
  term.on('data', (data) => {
38
- assert.equal(typeof data, 'string');
39
- assert.equal(data, '\u00E6');
42
+ assert.strictEqual(typeof data, 'string');
43
+ assert.strictEqual(data, '\u00E6');
40
44
  done();
41
45
  });
42
46
  });
@@ -45,25 +49,25 @@ if (process.platform !== 'win32') {
45
49
  encoding: null
46
50
  });
47
51
  term.on('data', (data) => {
48
- assert.equal(typeof data, 'object');
52
+ assert.strictEqual(typeof data, 'object');
49
53
  assert.ok(data instanceof Buffer);
50
- assert.equal(0xC3, data[0]);
51
- assert.equal(0xA6, data[1]);
54
+ assert.strictEqual(0xC3, data[0]);
55
+ assert.strictEqual(0xA6, data[1]);
52
56
  done();
53
57
  });
54
58
  });
55
59
  it('should support other encodings', (done) => {
56
60
  const text = 'test æ!';
57
- const term = new UnixTerminal(null, ['-c', 'echo "' + text + '"'], {
61
+ const term = new UnixTerminal(undefined, ['-c', 'echo "' + text + '"'], {
58
62
  encoding: 'base64'
59
63
  });
60
64
  let buffer = '';
61
- term.on('data', (data) => {
62
- assert.equal(typeof data, 'string');
65
+ term.onData((data) => {
66
+ assert.strictEqual(typeof data, 'string');
63
67
  buffer += data;
64
68
  });
65
- term.on('exit', () => {
66
- assert.equal(Buffer.alloc(8, buffer, 'base64').toString().replace('\r', '').replace('\n', ''), text);
69
+ term.onExit(() => {
70
+ assert.strictEqual(Buffer.alloc(8, buffer, 'base64').toString().replace('\r', '').replace('\n', ''), text);
67
71
  done();
68
72
  });
69
73
  });
@@ -74,8 +78,8 @@ if (process.platform !== 'win32') {
74
78
 
75
79
  afterEach(() => {
76
80
  if (term) {
77
- term.slave.destroy();
78
- term.master.destroy();
81
+ term.slave!.destroy();
82
+ term.master!.destroy();
79
83
  }
80
84
  });
81
85
 
@@ -83,12 +87,12 @@ if (process.platform !== 'win32') {
83
87
  term = UnixTerminal.open({});
84
88
 
85
89
  let slavebuf = '';
86
- term.slave.on('data', (data) => {
90
+ term.slave!.on('data', (data) => {
87
91
  slavebuf += data;
88
92
  });
89
93
 
90
94
  let masterbuf = '';
91
- term.master.on('data', (data) => {
95
+ term.master!.on('data', (data) => {
92
96
  masterbuf += data;
93
97
  });
94
98
 
@@ -100,8 +104,19 @@ if (process.platform !== 'win32') {
100
104
  return false;
101
105
  }, 200, 10);
102
106
 
103
- term.slave.write('slave\n');
104
- term.master.write('master\n');
107
+ term.slave!.write('slave\n');
108
+ term.master!.write('master\n');
109
+ });
110
+ });
111
+ describe('close', () => {
112
+ const term = new UnixTerminal('node');
113
+ it('should exit when terminal is destroyed programmatically', (done) => {
114
+ term.on('exit', (code, signal) => {
115
+ assert.strictEqual(code, 0);
116
+ assert.strictEqual(signal, constants.signals.SIGHUP);
117
+ done();
118
+ });
119
+ term.destroy();
105
120
  });
106
121
  });
107
122
  describe('signals in parent and child', () => {
@@ -140,8 +155,8 @@ if (process.platform !== 'win32') {
140
155
  });
141
156
  p.on('close', () => {
142
157
  // handlers in parent and child should have been triggered
143
- assert.equal(buffer.indexOf('SIGINT in child') !== -1, true);
144
- assert.equal(buffer.indexOf('SIGINT in parent') !== -1, true);
158
+ assert.strictEqual(buffer.indexOf('SIGINT in child') !== -1, true);
159
+ assert.strictEqual(buffer.indexOf('SIGINT in parent') !== -1, true);
145
160
  done();
146
161
  });
147
162
  });
@@ -181,8 +196,8 @@ if (process.platform !== 'win32') {
181
196
  });
182
197
  p.on('close', () => {
183
198
  // handlers in parent and child should have been triggered
184
- assert.equal(buffer.indexOf('should not be printed') !== -1, false);
185
- assert.equal(buffer.indexOf('SIGINT in parent') !== -1, true);
199
+ assert.strictEqual(buffer.indexOf('should not be printed') !== -1, false);
200
+ assert.strictEqual(buffer.indexOf('SIGINT in parent') !== -1, true);
186
201
  done();
187
202
  });
188
203
  });
@@ -201,7 +216,7 @@ if (process.platform !== 'win32') {
201
216
  });
202
217
  term.on('exit', () => {
203
218
  // no timeout in buffer
204
- assert.equal(buffer, '');
219
+ assert.strictEqual(buffer, '');
205
220
  done();
206
221
  });
207
222
  });
@@ -233,11 +248,88 @@ if (process.platform !== 'win32') {
233
248
  });
234
249
  term.on('exit', () => {
235
250
  // should have called both handlers and only once
236
- assert.equal(pHandlerCalled, 1);
237
- assert.equal(buffer, 'SIGUSR1 in child\r\n');
251
+ assert.strictEqual(pHandlerCalled, 1);
252
+ assert.strictEqual(buffer, 'SIGUSR1 in child\r\n');
238
253
  done();
239
254
  });
240
255
  });
241
256
  });
257
+ describe('spawn', () => {
258
+ if (process.platform === 'darwin') {
259
+ it('should return the name of the process', (done) => {
260
+ const term = new UnixTerminal('/bin/echo');
261
+ assert.strictEqual(term.process, '/bin/echo');
262
+ term.on('exit', () => done());
263
+ term.destroy();
264
+ });
265
+ it('should close on exec', (done) => {
266
+ const data = `
267
+ var pty = require('./lib/index');
268
+ var ptyProcess = pty.spawn('node', ['-e', 'setTimeout(() => console.log("hello from terminal"), 300);']);
269
+ ptyProcess.on('data', function (data) {
270
+ console.log(data);
271
+ });
272
+ setTimeout(() => null, 500);
273
+ console.log('ready', ptyProcess.pid);
274
+ `;
275
+ const buffer: string[] = [];
276
+ const readFd = fs.openSync(FIXTURES_PATH, 'r');
277
+ const p = cp.spawn('node', ['-e', data], {
278
+ stdio: ['ignore', 'pipe', 'pipe', readFd]
279
+ });
280
+ let sub = '';
281
+ p.stdout!.on('data', (data) => {
282
+ if (!data.toString().indexOf('ready')) {
283
+ sub = data.toString().split(' ')[1].slice(0, -1);
284
+ try {
285
+ fs.statSync(`/proc/${sub}/fd/${readFd}`);
286
+ done('not reachable');
287
+ } catch (error) {
288
+ assert.notStrictEqual(error.message.indexOf('ENOENT'), -1);
289
+ }
290
+ setTimeout(() => {
291
+ process.kill(parseInt(sub), 'SIGINT'); // SIGINT to child
292
+ p.kill('SIGINT'); // SIGINT to parent
293
+ }, 200);
294
+ } else {
295
+ buffer.push(data.toString().replace(/^\s+|\s+$/g, ''));
296
+ }
297
+ });
298
+ p.on('close', () => {
299
+ done();
300
+ });
301
+ });
302
+ }
303
+ it('should handle exec() errors', (done) => {
304
+ const term = new UnixTerminal('/bin/bogus.exe', []);
305
+ term.on('exit', (code, signal) => {
306
+ assert.strictEqual(code, 1);
307
+ done();
308
+ });
309
+ });
310
+ it('should handle chdir() errors', (done) => {
311
+ const term = new UnixTerminal('/bin/echo', [], { cwd: '/nowhere' });
312
+ term.on('exit', (code, signal) => {
313
+ assert.strictEqual(code, 1);
314
+ done();
315
+ });
316
+ });
317
+ it('should not leak child process', (done) => {
318
+ const count = cp.execSync('ps -ax | grep node | wc -l');
319
+ const term = new UnixTerminal('node', [ '-e', `
320
+ console.log('ready');
321
+ setTimeout(()=>console.log('timeout'), 200);`
322
+ ]);
323
+ term.on('data', async (data) => {
324
+ if (data === 'ready\r\n') {
325
+ process.kill(term.pid, 'SIGINT');
326
+ await setTimeout(() => null, 1000);
327
+ const newCount = cp.execSync('ps -ax | grep node | wc -l');
328
+ assert.strictEqual(count.toString(), newCount.toString());
329
+ done();
330
+ }
331
+ });
332
+ });
333
+ });
242
334
  });
243
335
  }
@@ -4,17 +4,21 @@
4
4
  * Copyright (c) 2018, Microsoft Corporation (MIT License).
5
5
  */
6
6
  import * as net from 'net';
7
+ import * as path from 'path';
7
8
  import { Terminal, DEFAULT_COLS, DEFAULT_ROWS } from './terminal';
8
9
  import { IProcessEnv, IPtyForkOptions, IPtyOpenOptions } from './interfaces';
9
10
  import { ArgvOrCommandLine } from './types';
10
11
  import { assign } from './utils';
11
12
 
12
13
  let pty: IUnixNative;
14
+ let helperPath: string;
13
15
  try {
14
16
  pty = require('../build/Release/pty.node');
17
+ helperPath = '../build/Release/spawn-helper';
15
18
  } catch (outerError) {
16
19
  try {
17
20
  pty = require('../build/Debug/pty.node');
21
+ helperPath = '../build/Debug/spawn-helper';
18
22
  } catch (innerError) {
19
23
  console.error('innerError', innerError);
20
24
  // Re-throw the exception from the Release require if the Debug require fails as well
@@ -22,6 +26,10 @@ try {
22
26
  }
23
27
  }
24
28
 
29
+ helperPath = path.resolve(__dirname, helperPath);
30
+ helperPath = helperPath.replace('app.asar', 'app.asar.unpacked');
31
+ helperPath = helperPath.replace('node_modules.asar', 'node_modules.asar.unpacked');
32
+
25
33
  const DEFAULT_FILE = 'sh';
26
34
  const DEFAULT_NAME = 'xterm';
27
35
  const DESTROY_SOCKET_TIMEOUT_MS = 200;
@@ -36,13 +44,13 @@ export class UnixTerminal extends Terminal {
36
44
  protected _readable: boolean;
37
45
  protected _writable: boolean;
38
46
 
39
- private _boundClose: boolean;
40
- private _emittedClose: boolean;
41
- private _master: net.Socket;
42
- private _slave: net.Socket;
47
+ private _boundClose: boolean = false;
48
+ private _emittedClose: boolean = false;
49
+ private _master: net.Socket | undefined;
50
+ private _slave: net.Socket | undefined;
43
51
 
44
- public get master(): net.Socket { return this._master; }
45
- public get slave(): net.Socket { return this._slave; }
52
+ public get master(): net.Socket | undefined { return this._master; }
53
+ public get slave(): net.Socket | undefined { return this._slave; }
46
54
 
47
55
  constructor(file?: string, args?: ArgvOrCommandLine, opt?: IPtyForkOptions) {
48
56
  super(opt);
@@ -59,8 +67,8 @@ export class UnixTerminal extends Terminal {
59
67
 
60
68
  this._cols = opt.cols || DEFAULT_COLS;
61
69
  this._rows = opt.rows || DEFAULT_ROWS;
62
- const uid = opt.uid || -1;
63
- const gid = opt.gid || -1;
70
+ const uid = opt.uid ?? -1;
71
+ const gid = opt.gid ?? -1;
64
72
  const env: IProcessEnv = assign({}, opt.env);
65
73
 
66
74
  if (opt.env === process.env) {
@@ -86,7 +94,7 @@ export class UnixTerminal extends Terminal {
86
94
  // From macOS High Sierra 10.13.2 sometimes the socket never gets
87
95
  // closed. A timeout is applied here to avoid the terminal never being
88
96
  // destroyed when this occurs.
89
- let timeout = setTimeout(() => {
97
+ let timeout: NodeJS.Timeout | null = setTimeout(() => {
90
98
  timeout = null;
91
99
  // Destroying the socket now will cause the close event to fire
92
100
  this._socket.destroy();
@@ -103,7 +111,7 @@ export class UnixTerminal extends Terminal {
103
111
  };
104
112
 
105
113
  // fork
106
- const term = pty.fork(file, args, parsedEnv, cwd, this._cols, this._rows, uid, gid, (encoding === 'utf8'), onexit);
114
+ const term = pty.fork(file, args, parsedEnv, cwd, this._cols, this._rows, uid, gid, (encoding === 'utf8'), helperPath, onexit);
107
115
 
108
116
  this._socket = new PipeSocket(term.fd);
109
117
  if (encoding !== null) {
@@ -169,6 +177,10 @@ export class UnixTerminal extends Terminal {
169
177
  this._socket.write(data);
170
178
  }
171
179
 
180
+ /* Accessors */
181
+ get fd(): number { return this._fd; }
182
+ get ptsName(): string { return this._pty; }
183
+
172
184
  /**
173
185
  * openpty
174
186
  */
@@ -204,7 +216,7 @@ export class UnixTerminal extends Terminal {
204
216
  self._slave.resume();
205
217
 
206
218
  self._socket = self._master;
207
- self._pid = null;
219
+ self._pid = -1;
208
220
  self._fd = term.master;
209
221
  self._pty = term.pty;
210
222
 
@@ -250,6 +262,10 @@ export class UnixTerminal extends Terminal {
250
262
  * Gets the name of the process.
251
263
  */
252
264
  public get process(): string {
265
+ if (process.platform === 'darwin') {
266
+ return pty.process(this._pid) || this._file;
267
+ }
268
+
253
269
  return pty.process(this._fd, this._pty) || this._file;
254
270
  }
255
271
 
@@ -266,6 +282,10 @@ export class UnixTerminal extends Terminal {
266
282
  this._rows = rows;
267
283
  }
268
284
 
285
+ public clear(): void {
286
+
287
+ }
288
+
269
289
  private _sanitizeEnv(env: IProcessEnv): void {
270
290
  // Make sure we didn't start our server from inside tmux.
271
291
  delete env['TMUX'];
package/src/win/conpty.cc CHANGED
@@ -34,6 +34,7 @@ extern "C" void init(v8::Local<v8::Object>);
34
34
  typedef VOID* HPCON;
35
35
  typedef HRESULT (__stdcall *PFNCREATEPSEUDOCONSOLE)(COORD c, HANDLE hIn, HANDLE hOut, DWORD dwFlags, HPCON* phpcon);
36
36
  typedef HRESULT (__stdcall *PFNRESIZEPSEUDOCONSOLE)(HPCON hpc, COORD newSize);
37
+ typedef HRESULT (__stdcall *PFNCLEARPSEUDOCONSOLE)(HPCON hpc);
37
38
  typedef void (__stdcall *PFNCLOSEPSEUDOCONSOLE)(HPCON hpc);
38
39
 
39
40
  #endif
@@ -289,6 +290,13 @@ static NAN_METHOD(PtyConnect) {
289
290
  const v8::Local<v8::Array> envValues = info[3].As<v8::Array>();
290
291
  const v8::Local<v8::Function> exitCallback = v8::Local<v8::Function>::Cast(info[4]);
291
292
 
293
+ // Fetch pty handle from ID and start process
294
+ pty_baton* handle = get_pty_baton(id);
295
+ if (!handle) {
296
+ Nan::ThrowError("Invalid pty handle");
297
+ return;
298
+ }
299
+
292
300
  // Prepare command line
293
301
  std::unique_ptr<wchar_t[]> mutableCommandline = std::make_unique<wchar_t[]>(cmdline.length() + 1);
294
302
  HRESULT hr = StringCchCopyW(mutableCommandline.get(), cmdline.length() + 1, cmdline.c_str());
@@ -311,11 +319,8 @@ static NAN_METHOD(PtyConnect) {
311
319
  auto envV = vectorFromString(env);
312
320
  LPWSTR envArg = envV.empty() ? nullptr : envV.data();
313
321
 
314
- // Fetch pty handle from ID and start process
315
- pty_baton* handle = get_pty_baton(id);
316
-
317
- BOOL success = ConnectNamedPipe(handle->hIn, nullptr);
318
- success = ConnectNamedPipe(handle->hOut, nullptr);
322
+ ConnectNamedPipe(handle->hIn, nullptr);
323
+ ConnectNamedPipe(handle->hOut, nullptr);
319
324
 
320
325
  // Attach the pseudoconsole to the client application we're creating
321
326
  STARTUPINFOEXW siEx{0};
@@ -396,15 +401,46 @@ static NAN_METHOD(PtyResize) {
396
401
 
397
402
  const pty_baton* handle = get_pty_baton(id);
398
403
 
399
- HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
400
- bool fLoadedDll = hLibrary != nullptr;
401
- if (fLoadedDll)
402
- {
403
- PFNRESIZEPSEUDOCONSOLE const pfnResizePseudoConsole = (PFNRESIZEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ResizePseudoConsole");
404
- if (pfnResizePseudoConsole)
404
+ if (handle != nullptr) {
405
+ HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
406
+ bool fLoadedDll = hLibrary != nullptr;
407
+ if (fLoadedDll)
408
+ {
409
+ PFNRESIZEPSEUDOCONSOLE const pfnResizePseudoConsole = (PFNRESIZEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ResizePseudoConsole");
410
+ if (pfnResizePseudoConsole)
411
+ {
412
+ COORD size = {cols, rows};
413
+ pfnResizePseudoConsole(handle->hpc, size);
414
+ }
415
+ }
416
+ }
417
+
418
+ return info.GetReturnValue().SetUndefined();
419
+ }
420
+
421
+ static NAN_METHOD(PtyClear) {
422
+ Nan::HandleScope scope;
423
+
424
+ if (info.Length() != 1 ||
425
+ !info[0]->IsNumber()) {
426
+ Nan::ThrowError("Usage: pty.clear(id)");
427
+ return;
428
+ }
429
+
430
+ int id = info[0]->Int32Value(Nan::GetCurrentContext()).FromJust();
431
+
432
+ const pty_baton* handle = get_pty_baton(id);
433
+
434
+ if (handle != nullptr) {
435
+ HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
436
+ bool fLoadedDll = hLibrary != nullptr;
437
+ if (fLoadedDll)
405
438
  {
406
- COORD size = {cols, rows};
407
- pfnResizePseudoConsole(handle->hpc, size);
439
+ PFNCLEARPSEUDOCONSOLE const pfnClearPseudoConsole = (PFNCLEARPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ClearPseudoConsole");
440
+ if (pfnClearPseudoConsole)
441
+ {
442
+ pfnClearPseudoConsole(handle->hpc);
443
+ }
408
444
  }
409
445
  }
410
446
 
@@ -424,18 +460,24 @@ static NAN_METHOD(PtyKill) {
424
460
 
425
461
  const pty_baton* handle = get_pty_baton(id);
426
462
 
427
- HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
428
- bool fLoadedDll = hLibrary != nullptr;
429
- if (fLoadedDll)
430
- {
431
- PFNCLOSEPSEUDOCONSOLE const pfnClosePseudoConsole = (PFNCLOSEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ClosePseudoConsole");
432
- if (pfnClosePseudoConsole)
463
+ if (handle != nullptr) {
464
+ HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
465
+ bool fLoadedDll = hLibrary != nullptr;
466
+ if (fLoadedDll)
433
467
  {
434
- pfnClosePseudoConsole(handle->hpc);
468
+ PFNCLOSEPSEUDOCONSOLE const pfnClosePseudoConsole = (PFNCLOSEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ClosePseudoConsole");
469
+ if (pfnClosePseudoConsole)
470
+ {
471
+ pfnClosePseudoConsole(handle->hpc);
472
+ }
435
473
  }
436
- }
437
474
 
438
- CloseHandle(handle->hShell);
475
+ DisconnectNamedPipe(handle->hIn);
476
+ DisconnectNamedPipe(handle->hOut);
477
+ CloseHandle(handle->hIn);
478
+ CloseHandle(handle->hOut);
479
+ CloseHandle(handle->hShell);
480
+ }
439
481
 
440
482
  return info.GetReturnValue().SetUndefined();
441
483
  }
@@ -449,6 +491,7 @@ extern "C" void init(v8::Local<v8::Object> target) {
449
491
  Nan::SetMethod(target, "startProcess", PtyStartProcess);
450
492
  Nan::SetMethod(target, "connect", PtyConnect);
451
493
  Nan::SetMethod(target, "resize", PtyResize);
494
+ Nan::SetMethod(target, "clear", PtyClear);
452
495
  Nan::SetMethod(target, "kill", PtyKill);
453
496
  };
454
497
 
@@ -52,7 +52,7 @@ std::wstring get_shell_path(std::wstring filename) {
52
52
 
53
53
  const wchar_t *filename_ = filename.c_str();
54
54
 
55
- for (int i = 0; i < paths.size(); ++i) {
55
+ for (size_t i = 0; i < paths.size(); ++i) {
56
56
  std::wstring path = paths[i];
57
57
  wchar_t searchPath[MAX_PATH];
58
58
  ::PathCombineW(searchPath, const_cast<wchar_t*>(path.c_str()), filename_);
@@ -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>();
@@ -29,10 +29,10 @@ 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 _innerPidHandle: number;
35
- private _closeTimeout: NodeJS.Timer;
32
+ private _pid: number = 0;
33
+ private _innerPid: number = 0;
34
+ private _innerPidHandle: number = 0;
35
+ private _closeTimeout: NodeJS.Timer | undefined;
36
36
  private _exitCode: number | undefined;
37
37
  private _conoutSocketWorker: ConoutConnection;
38
38
 
@@ -152,6 +152,12 @@ export class WindowsPtyAgent {
152
152
  this._ptyNative.resize(this._pid, cols, rows);
153
153
  }
154
154
 
155
+ public clear(): void {
156
+ if (this._useConpty) {
157
+ (this._ptyNative as IConptyNative).clear(this._pty);
158
+ }
159
+ }
160
+
155
161
  public kill(): void {
156
162
  this._inSocket.readable = false;
157
163
  this._outSocket.readable = false;
@@ -202,7 +208,7 @@ export class WindowsPtyAgent {
202
208
  });
203
209
  }
204
210
 
205
- public get exitCode(): number {
211
+ public get exitCode(): number | undefined {
206
212
  if (this._useConpty) {
207
213
  return this._exitCode;
208
214
  }
@@ -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);
@@ -105,10 +104,10 @@ if (process.platform === 'win32') {
105
104
  term.write('notepad.exe\r');
106
105
  term.write('node.exe\r');
107
106
  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');
107
+ assert.strictEqual(list[0].name.toLowerCase(), 'cmd.exe');
108
+ assert.strictEqual(list[1].name.toLowerCase(), 'powershell.exe');
109
+ assert.strictEqual(list[2].name.toLowerCase(), 'notepad.exe');
110
+ assert.strictEqual(list[3].name.toLowerCase(), 'node.exe');
112
111
  term.kill();
113
112
  const desiredState: IProcessState = {};
114
113
  desiredState[list[0].pid] = false;
@@ -147,21 +147,27 @@ export class WindowsTerminal extends Terminal {
147
147
  if (cols <= 0 || rows <= 0 || isNaN(cols) || isNaN(rows) || cols === Infinity || rows === Infinity) {
148
148
  throw new Error('resizing must be done using positive cols and rows');
149
149
  }
150
- this._defer(() => {
150
+ this._deferNoArgs(() => {
151
151
  this._agent.resize(cols, rows);
152
152
  this._cols = cols;
153
153
  this._rows = rows;
154
154
  });
155
155
  }
156
156
 
157
+ public clear(): void {
158
+ this._deferNoArgs(() => {
159
+ this._agent.clear();
160
+ });
161
+ }
162
+
157
163
  public destroy(): void {
158
- this._defer(() => {
164
+ this._deferNoArgs(() => {
159
165
  this.kill();
160
166
  });
161
167
  }
162
168
 
163
169
  public kill(signal?: string): void {
164
- this._defer(() => {
170
+ this._deferNoArgs(() => {
165
171
  if (signal) {
166
172
  throw new Error('Signals not supported on windows.');
167
173
  }
@@ -170,7 +176,20 @@ export class WindowsTerminal extends Terminal {
170
176
  });
171
177
  }
172
178
 
173
- private _defer<A extends any>(deferredFn: (arg?: A) => void, arg?: A): void {
179
+ private _deferNoArgs<A>(deferredFn: () => void): void {
180
+ // If the terminal is ready, execute.
181
+ if (this._isReady) {
182
+ deferredFn.call(this);
183
+ return;
184
+ }
185
+
186
+ // Queue until terminal is ready.
187
+ this._deferreds.push({
188
+ run: () => deferredFn.call(this)
189
+ });
190
+ }
191
+
192
+ private _defer<A>(deferredFn: (arg: A) => void, arg: A): void {
174
193
  // If the terminal is ready, execute.
175
194
  if (this._isReady) {
176
195
  deferredFn.call(this, arg);
@@ -14,5 +14,9 @@ conoutSocket.connect(conoutPipeName, () => {
14
14
  conoutSocket.pipe(workerSocket);
15
15
  });
16
16
  server.listen(getWorkerPipeName(conoutPipeName));
17
+
18
+ if (!parentPort) {
19
+ throw new Error('worker_threads parentPort is null');
20
+ }
17
21
  parentPort.postMessage(ConoutWorkerMessage.READY);
18
22
  });