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.
Files changed (45) hide show
  1. package/README.md +5 -4
  2. package/binding.gyp +70 -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 +13 -6
  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 +98 -97
  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 +6 -5
  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 +85 -38
  33. package/src/win/conpty_console_list.cc +3 -3
  34. package/src/win/path_util.cc +22 -3
  35. package/src/win/path_util.h +1 -0
  36. package/src/win/winpty.cc +118 -54
  37. package/src/windowsConoutConnection.ts +1 -1
  38. package/src/windowsPtyAgent.ts +16 -11
  39. package/src/windowsTerminal.test.ts +100 -99
  40. package/src/windowsTerminal.ts +23 -4
  41. package/src/worker/conoutSocketWorker.ts +4 -0
  42. package/typings/node-pty.d.ts +11 -20
  43. package/deps/winpty/.gitignore +0 -15
  44. package/deps/winpty/misc/.gitignore +0 -2
  45. 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
@@ -178,8 +179,8 @@ static NAN_METHOD(PtyStartProcess) {
178
179
  }
179
180
 
180
181
  const std::wstring filename(path_util::to_wstring(Nan::Utf8String(info[0])));
181
- const SHORT cols = info[1]->Uint32Value(Nan::GetCurrentContext()).FromJust();
182
- const SHORT rows = info[2]->Uint32Value(Nan::GetCurrentContext()).FromJust();
182
+ const SHORT cols = static_cast<SHORT>(info[1]->Uint32Value(Nan::GetCurrentContext()).FromJust());
183
+ const SHORT rows = static_cast<SHORT>(info[2]->Uint32Value(Nan::GetCurrentContext()).FromJust());
183
184
  const bool debug = Nan::To<bool>(info[3]).FromJust();
184
185
  const std::wstring pipeName(path_util::to_wstring(Nan::Utf8String(info[4])));
185
186
  const bool inheritCursor = Nan::To<bool>(info[5]).FromJust();
@@ -193,12 +194,10 @@ static NAN_METHOD(PtyStartProcess) {
193
194
  shellpath = filename;
194
195
  }
195
196
 
196
- std::string shellpath_(shellpath.begin(), shellpath.end());
197
-
198
197
  if (shellpath.empty() || !path_util::file_exists(shellpath)) {
199
- std::stringstream why;
200
- why << "File not found: " << shellpath_;
201
- Nan::ThrowError(why.str().c_str());
198
+ std::wstringstream why;
199
+ why << "File not found: " << shellpath;
200
+ Nan::ThrowError(path_util::from_wstring(why.str().c_str()));
202
201
  return;
203
202
  }
204
203
 
@@ -222,14 +221,20 @@ static NAN_METHOD(PtyStartProcess) {
222
221
  return;
223
222
  }
224
223
 
225
- Nan::Set(marshal, Nan::New<v8::String>("fd").ToLocalChecked(), Nan::New<v8::Number>(-1));
226
- {
227
- std::string coninPipeNameStr(inName.begin(), inName.end());
228
- Nan::Set(marshal, Nan::New<v8::String>("conin").ToLocalChecked(), Nan::New<v8::String>(coninPipeNameStr).ToLocalChecked());
229
-
230
- std::string conoutPipeNameStr(outName.begin(), outName.end());
231
- Nan::Set(marshal, Nan::New<v8::String>("conout").ToLocalChecked(), Nan::New<v8::String>(conoutPipeNameStr).ToLocalChecked());
224
+ std::string inNameStr(path_util::from_wstring(inName.c_str()));
225
+ if (inNameStr.empty()) {
226
+ Nan::ThrowError("Failed to initialize conpty conin");
227
+ return;
228
+ }
229
+ std::string outNameStr(path_util::from_wstring(outName.c_str()));
230
+ if (outNameStr.empty()) {
231
+ Nan::ThrowError("Failed to initialize conpty conout");
232
+ return;
232
233
  }
234
+
235
+ Nan::Set(marshal, Nan::New<v8::String>("fd").ToLocalChecked(), Nan::New<v8::Number>(-1));
236
+ Nan::Set(marshal, Nan::New<v8::String>("conin").ToLocalChecked(), Nan::New<v8::String>(inNameStr).ToLocalChecked());
237
+ Nan::Set(marshal, Nan::New<v8::String>("conout").ToLocalChecked(), Nan::New<v8::String>(outNameStr).ToLocalChecked());
233
238
  info.GetReturnValue().Set(marshal);
234
239
  }
235
240
 
@@ -289,6 +294,13 @@ static NAN_METHOD(PtyConnect) {
289
294
  const v8::Local<v8::Array> envValues = info[3].As<v8::Array>();
290
295
  const v8::Local<v8::Function> exitCallback = v8::Local<v8::Function>::Cast(info[4]);
291
296
 
297
+ // Fetch pty handle from ID and start process
298
+ pty_baton* handle = get_pty_baton(id);
299
+ if (!handle) {
300
+ Nan::ThrowError("Invalid pty handle");
301
+ return;
302
+ }
303
+
292
304
  // Prepare command line
293
305
  std::unique_ptr<wchar_t[]> mutableCommandline = std::make_unique<wchar_t[]>(cmdline.length() + 1);
294
306
  HRESULT hr = StringCchCopyW(mutableCommandline.get(), cmdline.length() + 1, cmdline.c_str());
@@ -311,11 +323,8 @@ static NAN_METHOD(PtyConnect) {
311
323
  auto envV = vectorFromString(env);
312
324
  LPWSTR envArg = envV.empty() ? nullptr : envV.data();
313
325
 
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);
326
+ ConnectNamedPipe(handle->hIn, nullptr);
327
+ ConnectNamedPipe(handle->hOut, nullptr);
319
328
 
320
329
  // Attach the pseudoconsole to the client application we're creating
321
330
  STARTUPINFOEXW siEx{0};
@@ -391,20 +400,51 @@ static NAN_METHOD(PtyResize) {
391
400
  }
392
401
 
393
402
  int id = info[0]->Int32Value(Nan::GetCurrentContext()).FromJust();
394
- SHORT cols = info[1]->Uint32Value(Nan::GetCurrentContext()).FromJust();
395
- SHORT rows = info[2]->Uint32Value(Nan::GetCurrentContext()).FromJust();
403
+ SHORT cols = static_cast<SHORT>(info[1]->Uint32Value(Nan::GetCurrentContext()).FromJust());
404
+ SHORT rows = static_cast<SHORT>(info[2]->Uint32Value(Nan::GetCurrentContext()).FromJust());
396
405
 
397
406
  const pty_baton* handle = get_pty_baton(id);
398
407
 
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)
408
+ if (handle != nullptr) {
409
+ HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
410
+ bool fLoadedDll = hLibrary != nullptr;
411
+ if (fLoadedDll)
405
412
  {
406
- COORD size = {cols, rows};
407
- pfnResizePseudoConsole(handle->hpc, size);
413
+ PFNRESIZEPSEUDOCONSOLE const pfnResizePseudoConsole = (PFNRESIZEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ResizePseudoConsole");
414
+ if (pfnResizePseudoConsole)
415
+ {
416
+ COORD size = {cols, rows};
417
+ pfnResizePseudoConsole(handle->hpc, size);
418
+ }
419
+ }
420
+ }
421
+
422
+ return info.GetReturnValue().SetUndefined();
423
+ }
424
+
425
+ static NAN_METHOD(PtyClear) {
426
+ Nan::HandleScope scope;
427
+
428
+ if (info.Length() != 1 ||
429
+ !info[0]->IsNumber()) {
430
+ Nan::ThrowError("Usage: pty.clear(id)");
431
+ return;
432
+ }
433
+
434
+ int id = info[0]->Int32Value(Nan::GetCurrentContext()).FromJust();
435
+
436
+ const pty_baton* handle = get_pty_baton(id);
437
+
438
+ if (handle != nullptr) {
439
+ HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
440
+ bool fLoadedDll = hLibrary != nullptr;
441
+ if (fLoadedDll)
442
+ {
443
+ PFNCLEARPSEUDOCONSOLE const pfnClearPseudoConsole = (PFNCLEARPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ClearPseudoConsole");
444
+ if (pfnClearPseudoConsole)
445
+ {
446
+ pfnClearPseudoConsole(handle->hpc);
447
+ }
408
448
  }
409
449
  }
410
450
 
@@ -424,18 +464,24 @@ static NAN_METHOD(PtyKill) {
424
464
 
425
465
  const pty_baton* handle = get_pty_baton(id);
426
466
 
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)
467
+ if (handle != nullptr) {
468
+ HANDLE hLibrary = LoadLibraryExW(L"kernel32.dll", 0, 0);
469
+ bool fLoadedDll = hLibrary != nullptr;
470
+ if (fLoadedDll)
433
471
  {
434
- pfnClosePseudoConsole(handle->hpc);
472
+ PFNCLOSEPSEUDOCONSOLE const pfnClosePseudoConsole = (PFNCLOSEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ClosePseudoConsole");
473
+ if (pfnClosePseudoConsole)
474
+ {
475
+ pfnClosePseudoConsole(handle->hpc);
476
+ }
435
477
  }
436
- }
437
478
 
438
- CloseHandle(handle->hShell);
479
+ DisconnectNamedPipe(handle->hIn);
480
+ DisconnectNamedPipe(handle->hOut);
481
+ CloseHandle(handle->hIn);
482
+ CloseHandle(handle->hOut);
483
+ CloseHandle(handle->hShell);
484
+ }
439
485
 
440
486
  return info.GetReturnValue().SetUndefined();
441
487
  }
@@ -449,6 +495,7 @@ extern "C" void init(v8::Local<v8::Object> target) {
449
495
  Nan::SetMethod(target, "startProcess", PtyStartProcess);
450
496
  Nan::SetMethod(target, "connect", PtyConnect);
451
497
  Nan::SetMethod(target, "resize", PtyResize);
498
+ Nan::SetMethod(target, "clear", PtyClear);
452
499
  Nan::SetMethod(target, "kill", PtyKill);
453
500
  };
454
501
 
@@ -12,7 +12,7 @@ static NAN_METHOD(ApiConsoleProcessList) {
12
12
  return;
13
13
  }
14
14
 
15
- const SHORT pid = info[0]->Uint32Value(Nan::GetCurrentContext()).FromJust();
15
+ const DWORD pid = info[0]->Uint32Value(Nan::GetCurrentContext()).FromJust();
16
16
 
17
17
  if (!FreeConsole()) {
18
18
  Nan::ThrowError("FreeConsole failed");
@@ -21,10 +21,10 @@ static NAN_METHOD(ApiConsoleProcessList) {
21
21
  Nan::ThrowError("AttachConsole failed");
22
22
  }
23
23
  auto processList = std::vector<DWORD>(64);
24
- auto processCount = GetConsoleProcessList(&processList[0], processList.size());
24
+ auto processCount = GetConsoleProcessList(&processList[0], static_cast<DWORD>(processList.size()));
25
25
  if (processList.size() < processCount) {
26
26
  processList.resize(processCount);
27
- processCount = GetConsoleProcessList(&processList[0], processList.size());
27
+ processCount = GetConsoleProcessList(&processList[0], static_cast<DWORD>(processList.size()));
28
28
  }
29
29
  FreeConsole();
30
30
 
@@ -13,9 +13,28 @@ namespace path_util {
13
13
 
14
14
  const wchar_t* to_wstring(const Nan::Utf8String& str) {
15
15
  const char *bytes = *str;
16
- unsigned int sizeOfStr = MultiByteToWideChar(CP_UTF8, 0, bytes, -1, NULL, 0);
16
+ int sizeOfStr = MultiByteToWideChar(CP_UTF8, 0, bytes, -1, NULL, 0);
17
+ if (sizeOfStr <= 0) {
18
+ return L"";
19
+ }
17
20
  wchar_t *output = new wchar_t[sizeOfStr];
18
- MultiByteToWideChar(CP_UTF8, 0, bytes, -1, output, sizeOfStr);
21
+ int status = MultiByteToWideChar(CP_UTF8, 0, bytes, -1, output, sizeOfStr);
22
+ if (status == 0) {
23
+ return L"";
24
+ }
25
+ return output;
26
+ }
27
+
28
+ const char* from_wstring(const wchar_t* wstr) {
29
+ int bufferSize = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL);
30
+ if (bufferSize <= 0) {
31
+ return "";
32
+ }
33
+ char *output = new char[bufferSize];
34
+ int status = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, output, bufferSize, NULL, NULL);
35
+ if (status == 0) {
36
+ return "";
37
+ }
19
38
  return output;
20
39
  }
21
40
 
@@ -52,7 +71,7 @@ std::wstring get_shell_path(std::wstring filename) {
52
71
 
53
72
  const wchar_t *filename_ = filename.c_str();
54
73
 
55
- for (int i = 0; i < paths.size(); ++i) {
74
+ for (size_t i = 0; i < paths.size(); ++i) {
56
75
  std::wstring path = paths[i];
57
76
  wchar_t searchPath[MAX_PATH];
58
77
  ::PathCombineW(searchPath, const_cast<wchar_t*>(path.c_str()), filename_);
@@ -14,6 +14,7 @@
14
14
  namespace path_util {
15
15
 
16
16
  const wchar_t* to_wstring(const Nan::Utf8String& str);
17
+ const char* from_wstring(const wchar_t* wstr);
17
18
  bool file_exists(std::wstring filename);
18
19
  std::wstring get_shell_path(std::wstring filename);
19
20