node-pty 1.1.0-beta4 → 1.1.0-beta40

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 (56) hide show
  1. package/README.md +6 -4
  2. package/binding.gyp +10 -20
  3. package/deps/winpty/src/agent/Agent.cc +11 -3
  4. package/deps/winpty/src/winpty.gyp +29 -1
  5. package/lib/conpty_console_list_agent.js +3 -7
  6. package/lib/conpty_console_list_agent.js.map +1 -1
  7. package/lib/index.js +2 -1
  8. package/lib/index.js.map +1 -1
  9. package/lib/terminal.js.map +1 -1
  10. package/lib/terminal.test.js.map +1 -1
  11. package/lib/unixTerminal.js +7 -44
  12. package/lib/unixTerminal.js.map +1 -1
  13. package/lib/unixTerminal.test.js +3 -5
  14. package/lib/unixTerminal.test.js.map +1 -1
  15. package/lib/utils.js +23 -1
  16. package/lib/utils.js.map +1 -1
  17. package/lib/windowsConoutConnection.js +6 -3
  18. package/lib/windowsConoutConnection.js.map +1 -1
  19. package/lib/windowsPtyAgent.js +45 -48
  20. package/lib/windowsPtyAgent.js.map +1 -1
  21. package/lib/windowsTerminal.js +1 -1
  22. package/lib/windowsTerminal.js.map +1 -1
  23. package/lib/windowsTerminal.test.js +40 -26
  24. package/lib/windowsTerminal.test.js.map +1 -1
  25. package/lib/worker/conoutSocketWorker.js.map +1 -1
  26. package/package.json +7 -2
  27. package/scripts/gen-compile-commands.js +8 -0
  28. package/scripts/{publish.js → increment-version.js} +7 -15
  29. package/scripts/post-install.js +48 -10
  30. package/scripts/prebuild.js +35 -0
  31. package/src/conpty_console_list_agent.ts +2 -6
  32. package/src/index.ts +2 -1
  33. package/src/interfaces.ts +2 -1
  34. package/src/native.d.ts +5 -5
  35. package/src/terminal.test.ts +1 -1
  36. package/src/terminal.ts +2 -2
  37. package/src/unix/pty.cc +244 -284
  38. package/src/unixTerminal.test.ts +3 -6
  39. package/src/unixTerminal.ts +9 -36
  40. package/src/utils.ts +20 -0
  41. package/src/win/conpty.cc +268 -187
  42. package/src/win/conpty.h +41 -0
  43. package/src/win/conpty_console_list.cc +16 -15
  44. package/src/win/path_util.cc +39 -36
  45. package/src/win/path_util.h +5 -2
  46. package/src/win/winpty.cc +108 -151
  47. package/src/windowsConoutConnection.ts +6 -3
  48. package/src/windowsPtyAgent.ts +42 -43
  49. package/src/windowsTerminal.test.ts +46 -33
  50. package/src/windowsTerminal.ts +3 -3
  51. package/src/worker/conoutSocketWorker.ts +2 -2
  52. package/third_party/conpty/1.23.251008001/win10-arm64/OpenConsole.exe +0 -0
  53. package/third_party/conpty/1.23.251008001/win10-arm64/conpty.dll +0 -0
  54. package/third_party/conpty/1.23.251008001/win10-x64/OpenConsole.exe +0 -0
  55. package/third_party/conpty/1.23.251008001/win10-x64/conpty.dll +0 -0
  56. package/typings/node-pty.d.ts +9 -1
@@ -37,9 +37,12 @@ export class ConoutConnection implements IDisposable {
37
37
  public get onReady(): IEvent<void> { return this._onReady.event; }
38
38
 
39
39
  constructor(
40
- private _conoutPipeName: string
40
+ private _conoutPipeName: string,
41
+ private _useConptyDll: boolean
41
42
  ) {
42
- const workerData: IWorkerData = { conoutPipeName: _conoutPipeName };
43
+ const workerData: IWorkerData = {
44
+ conoutPipeName: _conoutPipeName
45
+ };
43
46
  const scriptPath = __dirname.replace('node_modules.asar', 'node_modules.asar.unpacked');
44
47
  this._worker = new Worker(join(scriptPath, 'worker/conoutSocketWorker.js'), { workerData });
45
48
  this._worker.on('message', (message: ConoutWorkerMessage) => {
@@ -54,7 +57,7 @@ export class ConoutConnection implements IDisposable {
54
57
  }
55
58
 
56
59
  dispose(): void {
57
- if (this._isDisposed) {
60
+ if (!this._useConptyDll && this._isDisposed) {
58
61
  return;
59
62
  }
60
63
  this._isDisposed = true;
@@ -7,10 +7,11 @@
7
7
  import * as fs from 'fs';
8
8
  import * as os from 'os';
9
9
  import * as path from 'path';
10
+ import { fork } from 'child_process';
10
11
  import { Socket } from 'net';
11
12
  import { ArgvOrCommandLine } from './types';
12
- import { fork } from 'child_process';
13
13
  import { ConoutConnection } from './windowsConoutConnection';
14
+ import { loadNativeModule } from './utils';
14
15
 
15
16
  let conptyNative: IConptyNative;
16
17
  let winptyNative: IWinptyNative;
@@ -54,6 +55,7 @@ export class WindowsPtyAgent {
54
55
  rows: number,
55
56
  debug: boolean,
56
57
  private _useConpty: boolean | undefined,
58
+ private _useConptyDll: boolean = false,
57
59
  conptyInheritCursor: boolean = false
58
60
  ) {
59
61
  if (this._useConpty === undefined || this._useConpty === true) {
@@ -61,31 +63,11 @@ export class WindowsPtyAgent {
61
63
  }
62
64
  if (this._useConpty) {
63
65
  if (!conptyNative) {
64
- try {
65
- conptyNative = require('../build/Release/conpty.node');
66
- } catch (outerError) {
67
- try {
68
- conptyNative = require('../build/Debug/conpty.node');
69
- } catch (innerError) {
70
- console.error('innerError', innerError);
71
- // Re-throw the exception from the Release require if the Debug require fails as well
72
- throw outerError;
73
- }
74
- }
66
+ conptyNative = loadNativeModule('conpty').module;
75
67
  }
76
68
  } else {
77
69
  if (!winptyNative) {
78
- try {
79
- winptyNative = require('../build/Release/pty.node');
80
- } catch (outerError) {
81
- try {
82
- winptyNative = require('../build/Debug/pty.node');
83
- } catch (innerError) {
84
- console.error('innerError', innerError);
85
- // Re-throw the exception from the Release require if the Debug require fails as well
86
- throw outerError;
87
- }
88
- }
70
+ winptyNative = loadNativeModule('pty').module;
89
71
  }
90
72
  }
91
73
  this._ptyNative = this._useConpty ? conptyNative : winptyNative;
@@ -99,7 +81,7 @@ export class WindowsPtyAgent {
99
81
  // Open pty session.
100
82
  let term: IConptyProcess | IWinptyProcess;
101
83
  if (this._useConpty) {
102
- term = (this._ptyNative as IConptyNative).startProcess(file, cols, rows, debug, this._generatePipeName(), conptyInheritCursor);
84
+ term = (this._ptyNative as IConptyNative).startProcess(file, cols, rows, debug, this._generatePipeName(), conptyInheritCursor, this._useConptyDll);
103
85
  } else {
104
86
  term = (this._ptyNative as IWinptyNative).startProcess(file, commandLine, env, cwd, cols, rows, debug);
105
87
  this._pid = (term as IWinptyProcess).pid;
@@ -117,7 +99,7 @@ export class WindowsPtyAgent {
117
99
  this._outSocket = new Socket();
118
100
  this._outSocket.setEncoding('utf8');
119
101
  // The conout socket must be ready out on another thread to avoid deadlocks
120
- this._conoutSocketWorker = new ConoutConnection(term.conout);
102
+ this._conoutSocketWorker = new ConoutConnection(term.conout, this._useConptyDll);
121
103
  this._conoutSocketWorker.onReady(() => {
122
104
  this._conoutSocketWorker.connectSocket(this._outSocket);
123
105
  });
@@ -134,7 +116,7 @@ export class WindowsPtyAgent {
134
116
  this._inSocket.setEncoding('utf8');
135
117
 
136
118
  if (this._useConpty) {
137
- const connect = (this._ptyNative as IConptyNative).connect(this._pty, commandLine, cwd, env, c => this._$onProcessExit(c));
119
+ const connect = (this._ptyNative as IConptyNative).connect(this._pty, commandLine, cwd, env, this._useConptyDll, c => this._$onProcessExit(c));
138
120
  this._innerPid = connect.pid;
139
121
  }
140
122
  }
@@ -144,33 +126,43 @@ export class WindowsPtyAgent {
144
126
  if (this._exitCode !== undefined) {
145
127
  throw new Error('Cannot resize a pty that has already exited');
146
128
  }
147
- this._ptyNative.resize(this._pty, cols, rows);
129
+ (this._ptyNative as IConptyNative).resize(this._pty, cols, rows, this._useConptyDll);
148
130
  return;
149
131
  }
150
- this._ptyNative.resize(this._pid, cols, rows);
132
+ (this._ptyNative as IWinptyNative).resize(this._pid, cols, rows);
151
133
  }
152
134
 
153
135
  public clear(): void {
154
136
  if (this._useConpty) {
155
- (this._ptyNative as IConptyNative).clear(this._pty);
137
+ (this._ptyNative as IConptyNative).clear(this._pty, this._useConptyDll);
156
138
  }
157
139
  }
158
140
 
159
141
  public kill(): void {
160
- this._inSocket.readable = false;
161
- this._outSocket.readable = false;
162
142
  // Tell the agent to kill the pty, this releases handles to the process
163
143
  if (this._useConpty) {
164
- this._getConsoleProcessList().then(consoleProcessList => {
165
- consoleProcessList.forEach((pid: number) => {
166
- try {
167
- process.kill(pid);
168
- } catch (e) {
169
- // Ignore if process cannot be found (kill ESRCH error)
170
- }
144
+ if (!this._useConptyDll) {
145
+ this._inSocket.readable = false;
146
+ this._outSocket.readable = false;
147
+ this._getConsoleProcessList().then(consoleProcessList => {
148
+ consoleProcessList.forEach((pid: number) => {
149
+ try {
150
+ process.kill(pid);
151
+ } catch (e) {
152
+ // Ignore if process cannot be found (kill ESRCH error)
153
+ }
154
+ });
171
155
  });
172
- (this._ptyNative as IConptyNative).kill(this._pty);
173
- });
156
+ (this._ptyNative as IConptyNative).kill(this._pty, this._useConptyDll);
157
+ this._conoutSocketWorker.dispose();
158
+ } else {
159
+ // Close the input write handle to signal the end of session.
160
+ this._inSocket.destroy();
161
+ (this._ptyNative as IConptyNative).kill(this._pty, this._useConptyDll);
162
+ this._outSocket.on('data', () => {
163
+ this._conoutSocketWorker.dispose();
164
+ });
165
+ }
174
166
  } else {
175
167
  // Because pty.kill closes the handle, it will kill most processes by itself.
176
168
  // Process IDs can be reused as soon as all handles to them are
@@ -188,7 +180,6 @@ export class WindowsPtyAgent {
188
180
  }
189
181
  });
190
182
  }
191
- this._conoutSocketWorker.dispose();
192
183
  }
193
184
 
194
185
  private _getConsoleProcessList(): Promise<number[]> {
@@ -232,11 +223,16 @@ export class WindowsPtyAgent {
232
223
  */
233
224
  private _$onProcessExit(exitCode: number): void {
234
225
  this._exitCode = exitCode;
235
- this._flushDataAndCleanUp();
236
- this._outSocket.on('data', () => this._flushDataAndCleanUp());
226
+ if (!this._useConptyDll) {
227
+ this._flushDataAndCleanUp();
228
+ this._outSocket.on('data', () => this._flushDataAndCleanUp());
229
+ }
237
230
  }
238
231
 
239
232
  private _flushDataAndCleanUp(): void {
233
+ if (this._useConptyDll) {
234
+ return;
235
+ }
240
236
  if (this._closeTimeout) {
241
237
  clearTimeout(this._closeTimeout);
242
238
  }
@@ -244,6 +240,9 @@ export class WindowsPtyAgent {
244
240
  }
245
241
 
246
242
  private _cleanUpProcess(): void {
243
+ if (this._useConptyDll) {
244
+ return;
245
+ }
247
246
  this._inSocket.readable = false;
248
247
  this._outSocket.readable = false;
249
248
  this._outSocket.destroy();
@@ -26,13 +26,16 @@ function pollForProcessState(desiredState: IProcessState, intervalMs: number = 1
26
26
  psList({ all: true }).then(ps => {
27
27
  let success = true;
28
28
  const pids = Object.keys(desiredState).map(k => parseInt(k, 10));
29
+ console.log('expected pids', JSON.stringify(pids));
29
30
  pids.forEach(pid => {
30
31
  if (desiredState[pid]) {
31
32
  if (!ps.some(p => p.pid === pid)) {
33
+ console.log(`pid ${pid} does not exist`);
32
34
  success = false;
33
35
  }
34
36
  } else {
35
37
  if (ps.some(p => p.pid === pid)) {
38
+ console.log(`pid ${pid} still exists`);
36
39
  success = false;
37
40
  }
38
41
  }
@@ -71,6 +74,7 @@ function pollForProcessTreeSize(pid: number, size: number, intervalMs: number =
71
74
  }).forEach(p => openList.push(p));
72
75
  list.push(current);
73
76
  }
77
+ console.log('list', JSON.stringify(list));
74
78
  const success = list.length === size;
75
79
  if (success) {
76
80
  clearInterval(interval);
@@ -88,53 +92,57 @@ function pollForProcessTreeSize(pid: number, size: number, intervalMs: number =
88
92
  }
89
93
 
90
94
  if (process.platform === 'win32') {
91
- [true, false].forEach((useConpty) => {
92
- describe(`WindowsTerminal (useConpty = ${useConpty})`, () => {
95
+ [[false, false], [true, false], [true, true]].forEach(([useConpty, useConptyDll]) => {
96
+ describe(`WindowsTerminal (useConpty = ${useConpty}, useConptyDll = ${useConptyDll})`, () => {
93
97
  describe('kill', () => {
94
- it('should not crash parent process', (done) => {
95
- const term = new WindowsTerminal('cmd.exe', [], { useConpty });
98
+ it('should not crash parent process', function (done) {
99
+ this.timeout(20000);
100
+ const term = new WindowsTerminal('cmd.exe', [], { useConpty, useConptyDll });
101
+ term.on('exit', () => done());
96
102
  term.kill();
97
- // Add done call to deferred function queue to ensure the kill call has completed
98
- (<any>term)._defer(done);
99
103
  });
100
104
  it('should kill the process tree', function (done: Mocha.Done): void {
101
- this.timeout(5000);
102
- const term = new WindowsTerminal('cmd.exe', [], { useConpty });
105
+ this.timeout(20000);
106
+ const term = new WindowsTerminal('cmd.exe', [], { useConpty, useConptyDll });
103
107
  // Start sub-processes
104
108
  term.write('powershell.exe\r');
105
- term.write('notepad.exe\r');
106
109
  term.write('node.exe\r');
107
- pollForProcessTreeSize(term.pid, 4, 500, 5000).then(list => {
110
+ console.log('start poll for tree size');
111
+ pollForProcessTreeSize(term.pid, 3, 500, 5000).then(list => {
108
112
  assert.strictEqual(list[0].name.toLowerCase(), 'cmd.exe');
109
113
  assert.strictEqual(list[1].name.toLowerCase(), 'powershell.exe');
110
- assert.strictEqual(list[2].name.toLowerCase(), 'notepad.exe');
111
- assert.strictEqual(list[3].name.toLowerCase(), 'node.exe');
114
+ assert.strictEqual(list[2].name.toLowerCase(), 'node.exe');
112
115
  term.kill();
113
116
  const desiredState: IProcessState = {};
114
117
  desiredState[list[0].pid] = false;
115
118
  desiredState[list[1].pid] = false;
116
- desiredState[list[2].pid] = true;
117
- desiredState[list[3].pid] = false;
118
- pollForProcessState(desiredState).then(() => {
119
- // Kill notepad before done
120
- process.kill(list[2].pid);
121
- done();
119
+ desiredState[list[2].pid] = false;
120
+ term.on('exit', () => {
121
+ pollForProcessState(desiredState, 1000, 5000).then(() => {
122
+ done();
123
+ });
122
124
  });
123
125
  });
124
126
  });
125
127
  });
126
128
 
127
129
  describe('resize', () => {
128
- it('should throw a non-native exception when resizing an invalid value', () => {
129
- const term = new WindowsTerminal('cmd.exe', [], { useConpty });
130
+ it('should throw a non-native exception when resizing an invalid value', function(done) {
131
+ this.timeout(20000);
132
+ const term = new WindowsTerminal('cmd.exe', [], { useConpty, useConptyDll });
130
133
  assert.throws(() => term.resize(-1, -1));
131
134
  assert.throws(() => term.resize(0, 0));
132
135
  assert.doesNotThrow(() => term.resize(1, 1));
136
+ term.on('exit', () => {
137
+ done();
138
+ });
139
+ term.kill();
133
140
  });
134
- it('should throw a non-native exception when resizing a killed terminal', (done) => {
135
- const term = new WindowsTerminal('cmd.exe', [], { useConpty });
141
+ it('should throw a non-native exception when resizing a killed terminal', function(done) {
142
+ this.timeout(20000);
143
+ const term = new WindowsTerminal('cmd.exe', [], { useConpty, useConptyDll });
136
144
  (<any>term)._defer(() => {
137
- term.on('exit', () => {
145
+ term.once('exit', () => {
138
146
  assert.throws(() => term.resize(1, 1));
139
147
  done();
140
148
  });
@@ -144,7 +152,8 @@ if (process.platform === 'win32') {
144
152
  });
145
153
 
146
154
  describe('Args as CommandLine', () => {
147
- it('should not fail running a file containing a space in the path', (done) => {
155
+ it('should not fail running a file containing a space in the path', function (done) {
156
+ this.timeout(10000);
148
157
  const spaceFolder = path.resolve(__dirname, '..', 'fixtures', 'space folder');
149
158
  if (!fs.existsSync(spaceFolder)) {
150
159
  fs.mkdirSync(spaceFolder);
@@ -158,7 +167,7 @@ if (process.platform === 'win32') {
158
167
  // Skip test if git bash isn't installed
159
168
  return;
160
169
  }
161
- const term = new WindowsTerminal(cmdCopiedPath, '/c echo "hello world"', { useConpty });
170
+ const term = new WindowsTerminal(cmdCopiedPath, '/c echo "hello world"', { useConpty, useConptyDll });
162
171
  let result = '';
163
172
  term.on('data', (data) => {
164
173
  result += data;
@@ -171,8 +180,9 @@ if (process.platform === 'win32') {
171
180
  });
172
181
 
173
182
  describe('env', () => {
174
- it('should set environment variables of the shell', (done) => {
175
- const term = new WindowsTerminal('cmd.exe', '/C echo %FOO%', { env: { FOO: 'BAR' }});
183
+ it('should set environment variables of the shell', function (done) {
184
+ this.timeout(10000);
185
+ const term = new WindowsTerminal('cmd.exe', '/C echo %FOO%', { useConpty, useConptyDll, env: { FOO: 'BAR' }});
176
186
  let result = '';
177
187
  term.on('data', (data) => {
178
188
  result += data;
@@ -185,16 +195,18 @@ if (process.platform === 'win32') {
185
195
  });
186
196
 
187
197
  describe('On close', () => {
188
- it('should return process zero exit codes', (done) => {
189
- const term = new WindowsTerminal('cmd.exe', '/C exit', { useConpty });
198
+ it('should return process zero exit codes', function (done) {
199
+ this.timeout(10000);
200
+ const term = new WindowsTerminal('cmd.exe', '/C exit', { useConpty, useConptyDll });
190
201
  term.on('exit', (code) => {
191
202
  assert.strictEqual(code, 0);
192
203
  done();
193
204
  });
194
205
  });
195
206
 
196
- it('should return process non-zero exit codes', (done) => {
197
- const term = new WindowsTerminal('cmd.exe', '/C exit 2', { useConpty });
207
+ it('should return process non-zero exit codes', function (done) {
208
+ this.timeout(10000);
209
+ const term = new WindowsTerminal('cmd.exe', '/C exit 2', { useConpty, useConptyDll });
198
210
  term.on('exit', (code) => {
199
211
  assert.strictEqual(code, 2);
200
212
  done();
@@ -203,8 +215,9 @@ if (process.platform === 'win32') {
203
215
  });
204
216
 
205
217
  describe('Write', () => {
206
- it('should accept input', (done) => {
207
- const term = new WindowsTerminal('cmd.exe', '', { useConpty });
218
+ it('should accept input', function (done) {
219
+ this.timeout(10000);
220
+ const term = new WindowsTerminal('cmd.exe', '', { useConpty, useConptyDll });
208
221
  term.write('exit\r');
209
222
  term.on('exit', () => {
210
223
  done();
@@ -48,7 +48,7 @@ export class WindowsTerminal extends Terminal {
48
48
  this._deferreds = [];
49
49
 
50
50
  // Create new termal.
51
- this._agent = new WindowsPtyAgent(file, args, parsedEnv, cwd, this._cols, this._rows, false, opt.useConpty, opt.conptyInheritCursor);
51
+ this._agent = new WindowsPtyAgent(file, args, parsedEnv, cwd, this._cols, this._rows, false, opt.useConpty, opt.useConptyDll, opt.conptyInheritCursor);
52
52
  this._socket = this._agent.outSocket;
53
53
 
54
54
  // Not available until `ready` event emitted.
@@ -123,11 +123,11 @@ export class WindowsTerminal extends Terminal {
123
123
  this._forwardEvents();
124
124
  }
125
125
 
126
- protected _write(data: string): void {
126
+ protected _write(data: string | Buffer): void {
127
127
  this._defer(this._doWrite, data);
128
128
  }
129
129
 
130
- private _doWrite(data: string): void {
130
+ private _doWrite(data: string | Buffer): void {
131
131
  this._agent.inSocket.write(data);
132
132
  }
133
133
 
@@ -6,7 +6,8 @@ import { parentPort, workerData } from 'worker_threads';
6
6
  import { Socket, createServer } from 'net';
7
7
  import { ConoutWorkerMessage, IWorkerData, getWorkerPipeName } from '../shared/conout';
8
8
 
9
- const conoutPipeName = (workerData as IWorkerData).conoutPipeName;
9
+ const { conoutPipeName } = (workerData as IWorkerData);
10
+
10
11
  const conoutSocket = new Socket();
11
12
  conoutSocket.setEncoding('utf8');
12
13
  conoutSocket.connect(conoutPipeName, () => {
@@ -14,7 +15,6 @@ conoutSocket.connect(conoutPipeName, () => {
14
15
  conoutSocket.pipe(workerSocket);
15
16
  });
16
17
  server.listen(getWorkerPipeName(conoutPipeName));
17
-
18
18
  if (!parentPort) {
19
19
  throw new Error('worker_threads parentPort is null');
20
20
  }
@@ -95,6 +95,14 @@ declare module 'node-pty' {
95
95
  */
96
96
  useConpty?: boolean;
97
97
 
98
+ /**
99
+ * (EXPERIMENTAL)
100
+ *
101
+ * Whether to use the conpty.dll shipped with the node-pty package instead of the one built into
102
+ * Windows. Defaults to false.
103
+ */
104
+ useConptyDll?: boolean;
105
+
98
106
  /**
99
107
  * Whether to use PSEUDOCONSOLE_INHERIT_CURSOR in conpty.
100
108
  * @see https://docs.microsoft.com/en-us/windows/console/createpseudoconsole
@@ -165,7 +173,7 @@ declare module 'node-pty' {
165
173
  * Writes data to the pty.
166
174
  * @param data The data to write.
167
175
  */
168
- write(data: string): void;
176
+ write(data: string | Buffer): void;
169
177
 
170
178
  /**
171
179
  * Kills the pty.