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.
- package/README.md +7 -3
- package/binding.gyp +69 -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/unixTerminal.js +26 -4
- package/lib/unixTerminal.js.map +1 -1
- package/lib/unixTerminal.test.js +127 -18
- package/lib/unixTerminal.test.js.map +1 -1
- package/lib/windowsConoutConnection.js +2 -1
- package/lib/windowsConoutConnection.js.map +1 -1
- package/lib/windowsPtyAgent.js +3 -0
- package/lib/windowsPtyAgent.js.map +1 -1
- package/lib/windowsTerminal.js +15 -3
- package/lib/windowsTerminal.js.map +1 -1
- package/lib/windowsTerminal.test.js +4 -5
- 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 +2 -3
- package/scripts/post-install.js +1 -0
- package/src/conpty_console_list_agent.ts +1 -1
- package/src/interfaces.ts +3 -2
- package/src/native.d.ts +1 -1
- package/src/terminal.ts +20 -20
- package/src/tsconfig.json +1 -3
- package/src/unix/comms.h +5 -0
- package/src/unix/pty.cc +146 -172
- package/src/unix/spawn-helper.cc +70 -0
- package/src/unixTerminal.test.ts +145 -25
- package/src/unixTerminal.ts +24 -11
- package/src/win/conpty.cc +34 -22
- package/src/win/path_util.cc +1 -1
- package/src/windowsConoutConnection.ts +3 -2
- package/src/windowsPtyAgent.ts +5 -5
- package/src/windowsTerminal.test.ts +5 -6
- package/src/windowsTerminal.ts +17 -4
- package/src/worker/conoutSocketWorker.ts +4 -0
- package/typings/node-pty.d.ts +9 -3
- package/deps/winpty/.gitignore +0 -15
- package/deps/winpty/misc/.gitignore +0 -2
- package/scripts/install.js +0 -18
package/src/unixTerminal.test.ts
CHANGED
|
@@ -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(
|
|
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.
|
|
39
|
-
assert.
|
|
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.
|
|
52
|
+
assert.strictEqual(typeof data, 'object');
|
|
49
53
|
assert.ok(data instanceof Buffer);
|
|
50
|
-
assert.
|
|
51
|
-
assert.
|
|
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(
|
|
61
|
+
const term = new UnixTerminal(undefined, ['-c', 'echo "' + text + '"'], {
|
|
58
62
|
encoding: 'base64'
|
|
59
63
|
});
|
|
60
64
|
let buffer = '';
|
|
61
|
-
term.
|
|
62
|
-
assert.
|
|
65
|
+
term.onData((data) => {
|
|
66
|
+
assert.strictEqual(typeof data, 'string');
|
|
63
67
|
buffer += data;
|
|
64
68
|
});
|
|
65
|
-
term.
|
|
66
|
-
assert.
|
|
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
|
|
78
|
-
term.master
|
|
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
|
|
90
|
+
term.slave!.on('data', (data) => {
|
|
87
91
|
slavebuf += data;
|
|
88
92
|
});
|
|
89
93
|
|
|
90
94
|
let masterbuf = '';
|
|
91
|
-
term.master
|
|
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
|
|
104
|
-
term.master
|
|
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.
|
|
144
|
-
assert.
|
|
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.
|
|
185
|
-
assert.
|
|
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.
|
|
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.
|
|
237
|
-
assert.
|
|
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
|
}
|
package/src/unixTerminal.ts
CHANGED
|
@@ -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
|
|
63
|
-
const gid = opt.gid
|
|
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 =
|
|
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
|
-
|
|
315
|
-
|
|
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
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
-
|
|
407
|
-
pfnResizePseudoConsole
|
|
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
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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(
|
|
438
|
+
PFNCLOSEPSEUDOCONSOLE const pfnClosePseudoConsole = (PFNCLOSEPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ClosePseudoConsole");
|
|
439
|
+
if (pfnClosePseudoConsole)
|
|
440
|
+
{
|
|
441
|
+
pfnClosePseudoConsole(handle->hpc);
|
|
442
|
+
}
|
|
435
443
|
}
|
|
436
|
-
}
|
|
437
444
|
|
|
438
|
-
|
|
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
|
}
|
package/src/win/path_util.cc
CHANGED
|
@@ -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 (
|
|
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
|
-
|
|
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:
|
package/src/windowsPtyAgent.ts
CHANGED
|
@@ -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.
|
|
109
|
-
assert.
|
|
110
|
-
assert.
|
|
111
|
-
assert.
|
|
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;
|
package/src/windowsTerminal.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
158
|
+
this._deferNoArgs(() => {
|
|
159
159
|
this.kill();
|
|
160
160
|
});
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
public kill(signal?: string): void {
|
|
164
|
-
this.
|
|
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
|
|
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
|
});
|
package/typings/node-pty.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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;
|
package/deps/winpty/.gitignore
DELETED
package/scripts/install.js
DELETED
|
@@ -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
|
-
});
|