node-pty 0.11.0-beta3 → 0.11.0-beta30

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 +7 -3
  2. package/binding.gyp +69 -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/unixTerminal.js +26 -4
  7. package/lib/unixTerminal.js.map +1 -1
  8. package/lib/unixTerminal.test.js +127 -18
  9. package/lib/unixTerminal.test.js.map +1 -1
  10. package/lib/windowsConoutConnection.js +2 -1
  11. package/lib/windowsConoutConnection.js.map +1 -1
  12. package/lib/windowsPtyAgent.js +3 -0
  13. package/lib/windowsPtyAgent.js.map +1 -1
  14. package/lib/windowsTerminal.js +15 -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 +2 -3
  21. package/scripts/post-install.js +1 -0
  22. package/src/conpty_console_list_agent.ts +1 -1
  23. package/src/interfaces.ts +3 -2
  24. package/src/native.d.ts +1 -1
  25. package/src/terminal.ts +20 -20
  26. package/src/tsconfig.json +1 -3
  27. package/src/unix/comms.h +5 -0
  28. package/src/unix/pty.cc +146 -172
  29. package/src/unix/spawn-helper.cc +70 -0
  30. package/src/unixTerminal.test.ts +145 -25
  31. package/src/unixTerminal.ts +24 -11
  32. package/src/win/conpty.cc +34 -22
  33. package/src/win/path_util.cc +1 -1
  34. package/src/windowsConoutConnection.ts +3 -2
  35. package/src/windowsPtyAgent.ts +5 -5
  36. package/src/windowsTerminal.test.ts +5 -6
  37. package/src/windowsTerminal.ts +17 -4
  38. package/src/worker/conoutSocketWorker.ts +4 -0
  39. package/typings/node-pty.d.ts +9 -3
  40. package/deps/winpty/.gitignore +0 -15
  41. package/deps/winpty/misc/.gitignore +0 -2
  42. package/scripts/install.js +0 -18
@@ -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,116 @@ 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
+ it('should handle exec() errors', (done) => {
259
+ try {
260
+ new UnixTerminal('/bin/bogus.exe', []);
261
+ done(new Error('should have failed'));
262
+ } catch {
263
+ done();
264
+ }
265
+ });
266
+ it('should handle chdir() errors', (done) => {
267
+ try {
268
+ new UnixTerminal('/bin/echo', [], { cwd: '/nowhere' });
269
+ done(new Error('should have failed'));
270
+ } catch (e) {
271
+ assert.strictEqual((e as any).toString(), 'Error: chdir() failed: No such file or directory');
272
+ done();
273
+ }
274
+ });
275
+ it('should handle setuid() errors', (done) => {
276
+ try {
277
+ new UnixTerminal('/bin/echo', [], { uid: 999999 });
278
+ done(new Error('should have failed'));
279
+ } catch (e) {
280
+ assert.strictEqual((e as any).toString(), 'Error: setuid() failed: Operation not permitted');
281
+ done();
282
+ }
283
+ });
284
+ if (process.platform === 'linux') {
285
+ it('should not close on exec when closeFDs is not defined', (done) => {
286
+ const data = `
287
+ var pty = require('./lib/index');
288
+ var ptyProcess = pty.spawn('node', ['-e', 'setTimeout(() => console.log("hello from terminal"), 300);']);
289
+ ptyProcess.on('data', function (data) {
290
+ console.log(data);
291
+ });
292
+ setTimeout(() => null, 500);
293
+ console.log('ready', ptyProcess.pid);
294
+ `;
295
+ const buffer: string[] = [];
296
+ const readFd = fs.openSync(FIXTURES_PATH, 'r');
297
+ const p = cp.spawn('node', ['-e', data], {
298
+ stdio: ['ignore', 'pipe', 'pipe', readFd]
299
+ });
300
+ let sub = '';
301
+ p.stdout!.on('data', (data) => {
302
+ if (!data.toString().indexOf('ready')) {
303
+ sub = data.toString().split(' ')[1].slice(0, -1);
304
+ try {
305
+ fs.statSync(`/proc/${sub}/fd/${readFd}`);
306
+ } catch (_) {
307
+ done('not reachable');
308
+ }
309
+ setTimeout(() => {
310
+ process.kill(parseInt(sub), 'SIGINT'); // SIGINT to child
311
+ p.kill('SIGINT'); // SIGINT to parent
312
+ }, 200);
313
+ } else {
314
+ buffer.push(data.toString().replace(/^\s+|\s+$/g, ''));
315
+ }
316
+ });
317
+ p.on('close', () => {
318
+ done();
319
+ });
320
+ });
321
+ it('should close on exec when closeFDs is true', (done) => {
322
+ const data = `
323
+ var pty = require('./lib/index');
324
+ var ptyProcess = pty.spawn('node', ['-e', 'setTimeout(() => console.log("hello from terminal"), 300);'], {
325
+ closeFDs: true
326
+ });
327
+ ptyProcess.on('data', function (data) {
328
+ console.log(data);
329
+ });
330
+ setTimeout(() => null, 500);
331
+ console.log('ready', ptyProcess.pid);
332
+ `;
333
+ const buffer: string[] = [];
334
+ const readFd = fs.openSync(FIXTURES_PATH, 'r');
335
+ const p = cp.spawn('node', ['-e', data], {
336
+ stdio: ['ignore', 'pipe', 'pipe', readFd]
337
+ });
338
+ let sub = '';
339
+ p.stdout!.on('data', (data) => {
340
+ if (!data.toString().indexOf('ready')) {
341
+ sub = data.toString().split(' ')[1].slice(0, -1);
342
+ try {
343
+ fs.statSync(`/proc/${sub}/fd/${readFd}`);
344
+ done('not reachable');
345
+ } catch (error) {
346
+ assert.notStrictEqual(error.message.indexOf('ENOENT'), -1);
347
+ }
348
+ setTimeout(() => {
349
+ process.kill(parseInt(sub), 'SIGINT'); // SIGINT to child
350
+ p.kill('SIGINT'); // SIGINT to parent
351
+ }, 200);
352
+ } else {
353
+ buffer.push(data.toString().replace(/^\s+|\s+$/g, ''));
354
+ }
355
+ });
356
+ p.on('close', () => {
357
+ done();
358
+ });
359
+ });
360
+ }
361
+ });
242
362
  });
243
363
  }
@@ -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,9 @@ 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;
72
+ const closeFDs = opt.closeFDs || false;
64
73
  const env: IProcessEnv = assign({}, opt.env);
65
74
 
66
75
  if (opt.env === process.env) {
@@ -86,7 +95,7 @@ export class UnixTerminal extends Terminal {
86
95
  // From macOS High Sierra 10.13.2 sometimes the socket never gets
87
96
  // closed. A timeout is applied here to avoid the terminal never being
88
97
  // destroyed when this occurs.
89
- let timeout = setTimeout(() => {
98
+ let timeout: NodeJS.Timeout | null = setTimeout(() => {
90
99
  timeout = null;
91
100
  // Destroying the socket now will cause the close event to fire
92
101
  this._socket.destroy();
@@ -103,7 +112,7 @@ export class UnixTerminal extends Terminal {
103
112
  };
104
113
 
105
114
  // fork
106
- const term = pty.fork(file, args, parsedEnv, cwd, this._cols, this._rows, uid, gid, (encoding === 'utf8'), onexit);
115
+ const term = pty.fork(file, args, parsedEnv, cwd, this._cols, this._rows, uid, gid, (encoding === 'utf8'), closeFDs, onexit, helperPath);
107
116
 
108
117
  this._socket = new PipeSocket(term.fd);
109
118
  if (encoding !== null) {
@@ -169,6 +178,10 @@ export class UnixTerminal extends Terminal {
169
178
  this._socket.write(data);
170
179
  }
171
180
 
181
+ /* Accessors */
182
+ get fd(): number { return this._fd; }
183
+ get ptsName(): string { return this._pty; }
184
+
172
185
  /**
173
186
  * openpty
174
187
  */
@@ -204,7 +217,7 @@ export class UnixTerminal extends Terminal {
204
217
  self._slave.resume();
205
218
 
206
219
  self._socket = self._master;
207
- self._pid = null;
220
+ self._pid = -1;
208
221
  self._fd = term.master;
209
222
  self._pty = term.pty;
210
223
 
package/src/win/conpty.cc CHANGED
@@ -289,6 +289,13 @@ static NAN_METHOD(PtyConnect) {
289
289
  const v8::Local<v8::Array> envValues = info[3].As<v8::Array>();
290
290
  const v8::Local<v8::Function> exitCallback = v8::Local<v8::Function>::Cast(info[4]);
291
291
 
292
+ // Fetch pty handle from ID and start process
293
+ pty_baton* handle = get_pty_baton(id);
294
+ if (!handle) {
295
+ Nan::ThrowError("Invalid pty handle");
296
+ return;
297
+ }
298
+
292
299
  // Prepare command line
293
300
  std::unique_ptr<wchar_t[]> mutableCommandline = std::make_unique<wchar_t[]>(cmdline.length() + 1);
294
301
  HRESULT hr = StringCchCopyW(mutableCommandline.get(), cmdline.length() + 1, cmdline.c_str());
@@ -311,11 +318,8 @@ static NAN_METHOD(PtyConnect) {
311
318
  auto envV = vectorFromString(env);
312
319
  LPWSTR envArg = envV.empty() ? nullptr : envV.data();
313
320
 
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);
321
+ ConnectNamedPipe(handle->hIn, nullptr);
322
+ ConnectNamedPipe(handle->hOut, nullptr);
319
323
 
320
324
  // Attach the pseudoconsole to the client application we're creating
321
325
  STARTUPINFOEXW siEx{0};
@@ -396,15 +400,17 @@ static NAN_METHOD(PtyResize) {
396
400
 
397
401
  const pty_baton* handle = get_pty_baton(id);
398
402
 
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)
403
+ if (handle != nullptr) {
404
+ HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
405
+ bool fLoadedDll = hLibrary != nullptr;
406
+ if (fLoadedDll)
405
407
  {
406
- COORD size = {cols, rows};
407
- pfnResizePseudoConsole(handle->hpc, size);
408
+ PFNRESIZEPSEUDOCONSOLE const pfnResizePseudoConsole = (PFNRESIZEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ResizePseudoConsole");
409
+ if (pfnResizePseudoConsole)
410
+ {
411
+ COORD size = {cols, rows};
412
+ pfnResizePseudoConsole(handle->hpc, size);
413
+ }
408
414
  }
409
415
  }
410
416
 
@@ -424,18 +430,24 @@ static NAN_METHOD(PtyKill) {
424
430
 
425
431
  const pty_baton* handle = get_pty_baton(id);
426
432
 
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)
433
+ if (handle != nullptr) {
434
+ HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
435
+ bool fLoadedDll = hLibrary != nullptr;
436
+ if (fLoadedDll)
433
437
  {
434
- pfnClosePseudoConsole(handle->hpc);
438
+ PFNCLOSEPSEUDOCONSOLE const pfnClosePseudoConsole = (PFNCLOSEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ClosePseudoConsole");
439
+ if (pfnClosePseudoConsole)
440
+ {
441
+ pfnClosePseudoConsole(handle->hpc);
442
+ }
435
443
  }
436
- }
437
444
 
438
- CloseHandle(handle->hShell);
445
+ DisconnectNamedPipe(handle->hIn);
446
+ DisconnectNamedPipe(handle->hOut);
447
+ CloseHandle(handle->hIn);
448
+ CloseHandle(handle->hOut);
449
+ CloseHandle(handle->hShell);
450
+ }
439
451
 
440
452
  return info.GetReturnValue().SetUndefined();
441
453
  }
@@ -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>();
@@ -40,7 +40,8 @@ export class ConoutConnection implements IDisposable {
40
40
  private _conoutPipeName: string
41
41
  ) {
42
42
  const workerData: IWorkerData = { conoutPipeName: _conoutPipeName };
43
- this._worker = new Worker(join(__dirname, 'worker/conoutSocketWorker.js'), { workerData });
43
+ const scriptPath = __dirname.replace('node_modules.asar', 'node_modules.asar.unpacked');
44
+ this._worker = new Worker(join(scriptPath, 'worker/conoutSocketWorker.js'), { workerData });
44
45
  this._worker.on('message', (message: ConoutWorkerMessage) => {
45
46
  switch (message) {
46
47
  case ConoutWorkerMessage.READY:
@@ -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
 
@@ -202,7 +202,7 @@ export class WindowsPtyAgent {
202
202
  });
203
203
  }
204
204
 
205
- public get exitCode(): number {
205
+ public get exitCode(): number | undefined {
206
206
  if (this._useConpty) {
207
207
  return this._exitCode;
208
208
  }
@@ -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,7 +147,7 @@ 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;
@@ -155,13 +155,13 @@ export class WindowsTerminal extends Terminal {
155
155
  }
156
156
 
157
157
  public destroy(): void {
158
- this._defer(() => {
158
+ this._deferNoArgs(() => {
159
159
  this.kill();
160
160
  });
161
161
  }
162
162
 
163
163
  public kill(signal?: string): void {
164
- this._defer(() => {
164
+ this._deferNoArgs(() => {
165
165
  if (signal) {
166
166
  throw new Error('Signals not supported on windows.');
167
167
  }
@@ -170,7 +170,20 @@ export class WindowsTerminal extends Terminal {
170
170
  });
171
171
  }
172
172
 
173
- private _defer<A extends any>(deferredFn: (arg?: A) => void, arg?: A): void {
173
+ private _deferNoArgs<A>(deferredFn: () => void): void {
174
+ // If the terminal is ready, execute.
175
+ if (this._isReady) {
176
+ deferredFn.call(this);
177
+ return;
178
+ }
179
+
180
+ // Queue until terminal is ready.
181
+ this._deferreds.push({
182
+ run: () => deferredFn.call(this)
183
+ });
184
+ }
185
+
186
+ private _defer<A>(deferredFn: (arg: A) => void, arg: A): void {
174
187
  // If the terminal is ready, execute.
175
188
  if (this._isReady) {
176
189
  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
  });
@@ -78,11 +78,17 @@ declare module 'node-pty' {
78
78
 
79
79
  export interface IPtyForkOptions extends IBasePtyForkOptions {
80
80
  /**
81
- * Security warning: use this option with great caution, as opened file descriptors
82
- * with higher privileges might leak to the child program.
81
+ * Security warning: use this option with great caution unless `closeFDs` is also set,
82
+ * as opened file descriptors with higher privileges might leak to the child program.
83
83
  */
84
84
  uid?: number;
85
85
  gid?: number;
86
+
87
+ /**
88
+ * Close all open file after spawning the child process (UNIX only).
89
+ * Carries a performance penalty on Linux.
90
+ */
91
+ closeFDs?: boolean;
86
92
  }
87
93
 
88
94
  export interface IWindowsPtyForkOptions extends IBasePtyForkOptions {
@@ -165,7 +171,7 @@ declare module 'node-pty' {
165
171
 
166
172
  /**
167
173
  * Resizes the dimensions of the pty.
168
- * @param columns THe number of columns to use.
174
+ * @param columns The number of columns to use.
169
175
  * @param rows The number of rows to use.
170
176
  */
171
177
  resize(columns: number, rows: number): void;
@@ -1,15 +0,0 @@
1
- *.sln
2
- *.suo
3
- *.vcxproj
4
- *.vcxproj.filters
5
- *.pyc
6
- winpty.sdf
7
- winpty.opensdf
8
- /config.mk
9
- /build
10
- /build-gyp
11
- /build-libpty
12
- /ship/packages
13
- /src/Default
14
- /src/Release
15
- /src/gen
@@ -1,2 +0,0 @@
1
- *.exe
2
- UnixEcho
@@ -1,18 +0,0 @@
1
- 'use strict'
2
-
3
- const os = require('os');
4
- const path = require('path');
5
- const spawn = require('child_process').spawn;
6
-
7
- const gypArgs = ['rebuild'];
8
- if (process.env.NODE_PTY_DEBUG) {
9
- gypArgs.push('--debug');
10
- }
11
- const gypProcess = spawn(os.platform() === 'win32' ? 'node-gyp.cmd' : 'node-gyp', gypArgs, {
12
- cwd: path.join(__dirname, '..'),
13
- stdio: 'inherit'
14
- });
15
-
16
- gypProcess.on('exit', function (code) {
17
- process.exit(code);
18
- });