node-pty 1.2.0-beta.7 → 1.2.0-beta.9
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/binding.gyp +20 -18
- package/lib/unixTerminal.js +5 -2
- package/lib/windowsTerminal.js +1 -1
- package/package.json +6 -5
- package/prebuilds/darwin-arm64/pty.node +0 -0
- package/prebuilds/darwin-arm64/spawn-helper +0 -0
- package/prebuilds/darwin-x64/pty.node +0 -0
- package/prebuilds/darwin-x64/spawn-helper +0 -0
- package/prebuilds/linux-arm64/pty.node +0 -0
- package/prebuilds/linux-x64/pty.node +0 -0
- package/prebuilds/win32-arm64/conpty.node +0 -0
- package/prebuilds/win32-arm64/conpty.pdb +0 -0
- package/prebuilds/win32-arm64/conpty_console_list.node +0 -0
- package/prebuilds/win32-arm64/conpty_console_list.pdb +0 -0
- package/prebuilds/win32-x64/conpty.node +0 -0
- package/prebuilds/win32-x64/conpty.pdb +0 -0
- package/prebuilds/win32-x64/conpty_console_list.node +0 -0
- package/prebuilds/win32-x64/conpty_console_list.pdb +0 -0
- package/src/unix/pty.cc +7 -5
- package/typings/node-pty.d.ts +4 -1
- package/deps/.editorconfig +0 -2
- package/lib/conpty_console_list_agent.js.map +0 -1
- package/lib/eventEmitter2.js.map +0 -1
- package/lib/eventEmitter2.test.js +0 -30
- package/lib/eventEmitter2.test.js.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/interfaces.js.map +0 -1
- package/lib/shared/conout.js.map +0 -1
- package/lib/terminal.js.map +0 -1
- package/lib/terminal.test.js +0 -139
- package/lib/terminal.test.js.map +0 -1
- package/lib/testUtils.test.js +0 -28
- package/lib/testUtils.test.js.map +0 -1
- package/lib/types.js.map +0 -1
- package/lib/unixTerminal.js.map +0 -1
- package/lib/unixTerminal.test.js +0 -383
- package/lib/unixTerminal.test.js.map +0 -1
- package/lib/utils.js.map +0 -1
- package/lib/windowsConoutConnection.js.map +0 -1
- package/lib/windowsPtyAgent.js.map +0 -1
- package/lib/windowsPtyAgent.test.js +0 -90
- package/lib/windowsPtyAgent.test.js.map +0 -1
- package/lib/windowsTerminal.js.map +0 -1
- package/lib/windowsTerminal.test.js +0 -218
- package/lib/windowsTerminal.test.js.map +0 -1
- package/lib/worker/conoutSocketWorker.js.map +0 -1
- package/scripts/gen-compile-commands.js +0 -8
- package/scripts/increment-version.js +0 -54
- package/scripts/linux/checksums.txt +0 -2
- package/scripts/linux/install-sysroot.js +0 -158
- package/scripts/linux/verify-glibc-requirements.sh +0 -36
- package/src/conpty_console_list_agent.ts +0 -15
- package/src/eventEmitter2.test.ts +0 -30
- package/src/eventEmitter2.ts +0 -48
- package/src/index.ts +0 -52
- package/src/interfaces.ts +0 -138
- package/src/native.d.ts +0 -37
- package/src/shared/conout.ts +0 -15
- package/src/terminal.test.ts +0 -119
- package/src/terminal.ts +0 -211
- package/src/testUtils.test.ts +0 -23
- package/src/tsconfig.json +0 -22
- package/src/types.ts +0 -15
- package/src/unixTerminal.test.ts +0 -402
- package/src/unixTerminal.ts +0 -388
- package/src/utils.ts +0 -29
- package/src/windowsConoutConnection.ts +0 -82
- package/src/windowsPtyAgent.test.ts +0 -94
- package/src/windowsPtyAgent.ts +0 -261
- package/src/windowsTerminal.test.ts +0 -229
- package/src/windowsTerminal.ts +0 -203
- package/src/worker/conoutSocketWorker.ts +0 -22
package/src/windowsPtyAgent.ts
DELETED
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (c) 2012-2015, Christopher Jeffrey, Peter Sunde (MIT License)
|
|
3
|
-
* Copyright (c) 2016, Daniel Imms (MIT License).
|
|
4
|
-
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import * as fs from 'fs';
|
|
8
|
-
import * as path from 'path';
|
|
9
|
-
import { fork } from 'child_process';
|
|
10
|
-
import { Socket } from 'net';
|
|
11
|
-
import { ArgvOrCommandLine } from './types';
|
|
12
|
-
import { ConoutConnection } from './windowsConoutConnection';
|
|
13
|
-
import { loadNativeModule } from './utils';
|
|
14
|
-
|
|
15
|
-
let conptyNative: IConptyNative;
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* The amount of time to wait for additional data after the conpty shell process has exited before
|
|
19
|
-
* shutting down the socket. The timer will be reset if a new data event comes in after the timer
|
|
20
|
-
* has started.
|
|
21
|
-
*/
|
|
22
|
-
const FLUSH_DATA_INTERVAL = 1000;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* This agent sits between the WindowsTerminal class and provides an interface for conpty.
|
|
26
|
-
*/
|
|
27
|
-
export class WindowsPtyAgent {
|
|
28
|
-
private _inSocket: Socket;
|
|
29
|
-
private _outSocket: Socket;
|
|
30
|
-
private _innerPid: number = 0;
|
|
31
|
-
private _closeTimeout: NodeJS.Timer | undefined;
|
|
32
|
-
private _exitCode: number | undefined;
|
|
33
|
-
private _conoutSocketWorker: ConoutConnection;
|
|
34
|
-
|
|
35
|
-
private _fd: any;
|
|
36
|
-
private _pty: number;
|
|
37
|
-
private _ptyNative: IConptyNative;
|
|
38
|
-
|
|
39
|
-
public get inSocket(): Socket { return this._inSocket; }
|
|
40
|
-
public get outSocket(): Socket { return this._outSocket; }
|
|
41
|
-
public get fd(): any { return this._fd; }
|
|
42
|
-
public get innerPid(): number { return this._innerPid; }
|
|
43
|
-
public get pty(): number { return this._pty; }
|
|
44
|
-
|
|
45
|
-
constructor(
|
|
46
|
-
file: string,
|
|
47
|
-
args: ArgvOrCommandLine,
|
|
48
|
-
env: string[],
|
|
49
|
-
cwd: string,
|
|
50
|
-
cols: number,
|
|
51
|
-
rows: number,
|
|
52
|
-
debug: boolean,
|
|
53
|
-
private _useConptyDll: boolean = false,
|
|
54
|
-
conptyInheritCursor: boolean = false
|
|
55
|
-
) {
|
|
56
|
-
if (!conptyNative) {
|
|
57
|
-
conptyNative = loadNativeModule('conpty').module;
|
|
58
|
-
}
|
|
59
|
-
this._ptyNative = conptyNative;
|
|
60
|
-
|
|
61
|
-
// Sanitize input variable.
|
|
62
|
-
cwd = path.resolve(cwd);
|
|
63
|
-
|
|
64
|
-
// Compose command line
|
|
65
|
-
const commandLine = argsToCommandLine(file, args);
|
|
66
|
-
|
|
67
|
-
// Open pty session.
|
|
68
|
-
const term: IConptyProcess = conptyNative.startProcess(file, cols, rows, debug, this._generatePipeName(), conptyInheritCursor, this._useConptyDll);
|
|
69
|
-
|
|
70
|
-
// Not available on windows.
|
|
71
|
-
this._fd = term.fd;
|
|
72
|
-
|
|
73
|
-
// Generated incremental number that has no real purpose besides using it
|
|
74
|
-
// as a terminal id.
|
|
75
|
-
this._pty = term.pty;
|
|
76
|
-
|
|
77
|
-
// Create terminal pipe IPC channel and forward to a local unix socket.
|
|
78
|
-
this._outSocket = new Socket();
|
|
79
|
-
this._outSocket.setEncoding('utf8');
|
|
80
|
-
// The conout socket must be ready out on another thread to avoid deadlocks
|
|
81
|
-
this._conoutSocketWorker = new ConoutConnection(term.conout, this._useConptyDll);
|
|
82
|
-
this._conoutSocketWorker.onReady(() => {
|
|
83
|
-
this._conoutSocketWorker.connectSocket(this._outSocket);
|
|
84
|
-
});
|
|
85
|
-
this._outSocket.on('connect', () => {
|
|
86
|
-
this._outSocket.emit('ready_datapipe');
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
const inSocketFD = fs.openSync(term.conin, 'w');
|
|
90
|
-
this._inSocket = new Socket({
|
|
91
|
-
fd: inSocketFD,
|
|
92
|
-
readable: false,
|
|
93
|
-
writable: true
|
|
94
|
-
});
|
|
95
|
-
this._inSocket.setEncoding('utf8');
|
|
96
|
-
|
|
97
|
-
const connect = conptyNative.connect(this._pty, commandLine, cwd, env, this._useConptyDll, c => this._$onProcessExit(c));
|
|
98
|
-
this._innerPid = connect.pid;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
public resize(cols: number, rows: number): void {
|
|
102
|
-
if (this._exitCode !== undefined) {
|
|
103
|
-
throw new Error('Cannot resize a pty that has already exited');
|
|
104
|
-
}
|
|
105
|
-
this._ptyNative.resize(this._pty, cols, rows, this._useConptyDll);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
public clear(): void {
|
|
109
|
-
this._ptyNative.clear(this._pty, this._useConptyDll);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
public kill(): void {
|
|
113
|
-
// Tell the agent to kill the pty, this releases handles to the process
|
|
114
|
-
if (!this._useConptyDll) {
|
|
115
|
-
this._inSocket.readable = false;
|
|
116
|
-
this._outSocket.readable = false;
|
|
117
|
-
this._getConsoleProcessList().then(consoleProcessList => {
|
|
118
|
-
consoleProcessList.forEach((pid: number) => {
|
|
119
|
-
try {
|
|
120
|
-
process.kill(pid);
|
|
121
|
-
} catch (e) {
|
|
122
|
-
// Ignore if process cannot be found (kill ESRCH error)
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
this._ptyNative.kill(this._pty, this._useConptyDll);
|
|
127
|
-
this._conoutSocketWorker.dispose();
|
|
128
|
-
} else {
|
|
129
|
-
// Close the input write handle to signal the end of session.
|
|
130
|
-
this._inSocket.destroy();
|
|
131
|
-
this._ptyNative.kill(this._pty, this._useConptyDll);
|
|
132
|
-
this._outSocket.on('data', () => {
|
|
133
|
-
this._conoutSocketWorker.dispose();
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
private _getConsoleProcessList(): Promise<number[]> {
|
|
139
|
-
return new Promise<number[]>(resolve => {
|
|
140
|
-
const agent = fork(path.join(__dirname, 'conpty_console_list_agent'), [ this._innerPid.toString() ]);
|
|
141
|
-
agent.on('message', message => {
|
|
142
|
-
clearTimeout(timeout);
|
|
143
|
-
resolve(message.consoleProcessList);
|
|
144
|
-
});
|
|
145
|
-
const timeout = setTimeout(() => {
|
|
146
|
-
// Something went wrong, just send back the shell PID
|
|
147
|
-
agent.kill();
|
|
148
|
-
resolve([ this._innerPid ]);
|
|
149
|
-
}, 5000);
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
public get exitCode(): number | undefined {
|
|
154
|
-
return this._exitCode;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
private _generatePipeName(): string {
|
|
158
|
-
return `conpty-${Math.random() * 10000000}`;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Triggered from the native side when a contpy process exits.
|
|
163
|
-
*/
|
|
164
|
-
private _$onProcessExit(exitCode: number): void {
|
|
165
|
-
this._exitCode = exitCode;
|
|
166
|
-
if (!this._useConptyDll) {
|
|
167
|
-
this._flushDataAndCleanUp();
|
|
168
|
-
this._outSocket.on('data', () => this._flushDataAndCleanUp());
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
private _flushDataAndCleanUp(): void {
|
|
173
|
-
if (this._useConptyDll) {
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
if (this._closeTimeout) {
|
|
177
|
-
clearTimeout(this._closeTimeout);
|
|
178
|
-
}
|
|
179
|
-
this._closeTimeout = setTimeout(() => this._cleanUpProcess(), FLUSH_DATA_INTERVAL);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
private _cleanUpProcess(): void {
|
|
183
|
-
if (this._useConptyDll) {
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
this._inSocket.readable = false;
|
|
187
|
-
this._outSocket.readable = false;
|
|
188
|
-
this._outSocket.destroy();
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Convert argc/argv into a Win32 command-line following the escaping convention
|
|
193
|
-
// documented on MSDN (e.g. see CommandLineToArgvW documentation). Copied from
|
|
194
|
-
// winpty project.
|
|
195
|
-
export function argsToCommandLine(file: string, args: ArgvOrCommandLine): string {
|
|
196
|
-
if (isCommandLine(args)) {
|
|
197
|
-
if (args.length === 0) {
|
|
198
|
-
return file;
|
|
199
|
-
}
|
|
200
|
-
return `${argsToCommandLine(file, [])} ${args}`;
|
|
201
|
-
}
|
|
202
|
-
const argv = [file];
|
|
203
|
-
Array.prototype.push.apply(argv, args);
|
|
204
|
-
let result = '';
|
|
205
|
-
for (let argIndex = 0; argIndex < argv.length; argIndex++) {
|
|
206
|
-
if (argIndex > 0) {
|
|
207
|
-
result += ' ';
|
|
208
|
-
}
|
|
209
|
-
const arg = argv[argIndex];
|
|
210
|
-
// if it is empty or it contains whitespace and is not already quoted
|
|
211
|
-
const hasLopsidedEnclosingQuote = xOr((arg[0] !== '"'), (arg[arg.length - 1] !== '"'));
|
|
212
|
-
const hasNoEnclosingQuotes = ((arg[0] !== '"') && (arg[arg.length - 1] !== '"'));
|
|
213
|
-
const quote =
|
|
214
|
-
arg === '' ||
|
|
215
|
-
(arg.indexOf(' ') !== -1 ||
|
|
216
|
-
arg.indexOf('\t') !== -1) &&
|
|
217
|
-
((arg.length > 1) &&
|
|
218
|
-
(hasLopsidedEnclosingQuote || hasNoEnclosingQuotes));
|
|
219
|
-
if (quote) {
|
|
220
|
-
result += '\"';
|
|
221
|
-
}
|
|
222
|
-
let bsCount = 0;
|
|
223
|
-
for (let i = 0; i < arg.length; i++) {
|
|
224
|
-
const p = arg[i];
|
|
225
|
-
if (p === '\\') {
|
|
226
|
-
bsCount++;
|
|
227
|
-
} else if (p === '"') {
|
|
228
|
-
result += repeatText('\\', bsCount * 2 + 1);
|
|
229
|
-
result += '"';
|
|
230
|
-
bsCount = 0;
|
|
231
|
-
} else {
|
|
232
|
-
result += repeatText('\\', bsCount);
|
|
233
|
-
bsCount = 0;
|
|
234
|
-
result += p;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
if (quote) {
|
|
238
|
-
result += repeatText('\\', bsCount * 2);
|
|
239
|
-
result += '\"';
|
|
240
|
-
} else {
|
|
241
|
-
result += repeatText('\\', bsCount);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
return result;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
function isCommandLine(args: ArgvOrCommandLine): args is string {
|
|
248
|
-
return typeof args === 'string';
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function repeatText(text: string, count: number): string {
|
|
252
|
-
let result = '';
|
|
253
|
-
for (let i = 0; i < count; i++) {
|
|
254
|
-
result += text;
|
|
255
|
-
}
|
|
256
|
-
return result;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function xOr(arg1: boolean, arg2: boolean): boolean {
|
|
260
|
-
return ((arg1 && !arg2) || (!arg1 && arg2));
|
|
261
|
-
}
|
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (c) 2017, Daniel Imms (MIT License).
|
|
3
|
-
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import * as fs from 'fs';
|
|
7
|
-
import * as assert from 'assert';
|
|
8
|
-
import { WindowsTerminal } from './windowsTerminal';
|
|
9
|
-
import * as path from 'path';
|
|
10
|
-
import * as psList from 'ps-list';
|
|
11
|
-
|
|
12
|
-
interface IProcessState {
|
|
13
|
-
// Whether the PID must exist or must not exist
|
|
14
|
-
[pid: number]: boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface IWindowsProcessTreeResult {
|
|
18
|
-
name: string;
|
|
19
|
-
pid: number;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function pollForProcessState(desiredState: IProcessState, intervalMs: number = 100, timeoutMs: number = 2000): Promise<void> {
|
|
23
|
-
return new Promise<void>(resolve => {
|
|
24
|
-
let tries = 0;
|
|
25
|
-
const interval = setInterval(() => {
|
|
26
|
-
psList({ all: true }).then(ps => {
|
|
27
|
-
let success = true;
|
|
28
|
-
const pids = Object.keys(desiredState).map(k => parseInt(k, 10));
|
|
29
|
-
console.log('expected pids', JSON.stringify(pids));
|
|
30
|
-
pids.forEach(pid => {
|
|
31
|
-
if (desiredState[pid]) {
|
|
32
|
-
if (!ps.some(p => p.pid === pid)) {
|
|
33
|
-
console.log(`pid ${pid} does not exist`);
|
|
34
|
-
success = false;
|
|
35
|
-
}
|
|
36
|
-
} else {
|
|
37
|
-
if (ps.some(p => p.pid === pid)) {
|
|
38
|
-
console.log(`pid ${pid} still exists`);
|
|
39
|
-
success = false;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
if (success) {
|
|
44
|
-
clearInterval(interval);
|
|
45
|
-
resolve();
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
tries++;
|
|
49
|
-
if (tries * intervalMs >= timeoutMs) {
|
|
50
|
-
clearInterval(interval);
|
|
51
|
-
const processListing = pids.map(k => `${k}: ${desiredState[k]}`).join('\n');
|
|
52
|
-
assert.fail(`Bad process state, expected:\n${processListing}`);
|
|
53
|
-
resolve();
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
}, intervalMs);
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function pollForProcessTreeSize(pid: number, size: number, intervalMs: number = 100, timeoutMs: number = 2000): Promise<IWindowsProcessTreeResult[]> {
|
|
61
|
-
return new Promise<IWindowsProcessTreeResult[]>(resolve => {
|
|
62
|
-
let tries = 0;
|
|
63
|
-
const interval = setInterval(() => {
|
|
64
|
-
psList({ all: true }).then(ps => {
|
|
65
|
-
const openList: IWindowsProcessTreeResult[] = [];
|
|
66
|
-
openList.push(ps.filter(p => p.pid === pid).map(p => {
|
|
67
|
-
return { name: p.name, pid: p.pid };
|
|
68
|
-
})[0]);
|
|
69
|
-
const list: IWindowsProcessTreeResult[] = [];
|
|
70
|
-
while (openList.length) {
|
|
71
|
-
const current = openList.shift()!;
|
|
72
|
-
ps.filter(p => p.ppid === current.pid).map(p => {
|
|
73
|
-
return { name: p.name, pid: p.pid };
|
|
74
|
-
}).forEach(p => openList.push(p));
|
|
75
|
-
list.push(current);
|
|
76
|
-
}
|
|
77
|
-
console.log('list', JSON.stringify(list));
|
|
78
|
-
const success = list.length === size;
|
|
79
|
-
if (success) {
|
|
80
|
-
clearInterval(interval);
|
|
81
|
-
resolve(list);
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
tries++;
|
|
85
|
-
if (tries * intervalMs >= timeoutMs) {
|
|
86
|
-
clearInterval(interval);
|
|
87
|
-
assert.fail(`Bad process state, expected: ${size}, actual: ${list.length}`);
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
}, intervalMs);
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (process.platform === 'win32') {
|
|
95
|
-
[false, true].forEach((useConptyDll) => {
|
|
96
|
-
describe(`WindowsTerminal (useConptyDll = ${useConptyDll})`, () => {
|
|
97
|
-
describe('kill', () => {
|
|
98
|
-
it('should not crash parent process', function (done) {
|
|
99
|
-
this.timeout(20000);
|
|
100
|
-
const term = new WindowsTerminal('cmd.exe', [], { useConptyDll });
|
|
101
|
-
term.on('exit', () => done());
|
|
102
|
-
term.kill();
|
|
103
|
-
});
|
|
104
|
-
it('should kill the process tree', function (done: Mocha.Done): void {
|
|
105
|
-
this.timeout(20000);
|
|
106
|
-
const term = new WindowsTerminal('cmd.exe', [], { useConptyDll });
|
|
107
|
-
// Start sub-processes
|
|
108
|
-
term.write('powershell.exe\r');
|
|
109
|
-
term.write('node.exe\r');
|
|
110
|
-
console.log('start poll for tree size');
|
|
111
|
-
pollForProcessTreeSize(term.pid, 3, 500, 5000).then(list => {
|
|
112
|
-
assert.strictEqual(list[0].name.toLowerCase(), 'cmd.exe');
|
|
113
|
-
assert.strictEqual(list[1].name.toLowerCase(), 'powershell.exe');
|
|
114
|
-
assert.strictEqual(list[2].name.toLowerCase(), 'node.exe');
|
|
115
|
-
term.kill();
|
|
116
|
-
const desiredState: IProcessState = {};
|
|
117
|
-
desiredState[list[0].pid] = false;
|
|
118
|
-
desiredState[list[1].pid] = false;
|
|
119
|
-
desiredState[list[2].pid] = false;
|
|
120
|
-
term.on('exit', () => {
|
|
121
|
-
pollForProcessState(desiredState, 1000, 5000).then(() => {
|
|
122
|
-
done();
|
|
123
|
-
}).catch(done);
|
|
124
|
-
});
|
|
125
|
-
}).catch(done);
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
describe('resize', () => {
|
|
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', [], { useConptyDll });
|
|
133
|
-
assert.throws(() => term.resize(-1, -1));
|
|
134
|
-
assert.throws(() => term.resize(0, 0));
|
|
135
|
-
assert.doesNotThrow(() => term.resize(1, 1));
|
|
136
|
-
term.on('exit', () => {
|
|
137
|
-
done();
|
|
138
|
-
});
|
|
139
|
-
term.kill();
|
|
140
|
-
});
|
|
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', [], { useConptyDll });
|
|
144
|
-
(<any>term)._defer(() => {
|
|
145
|
-
term.once('exit', () => {
|
|
146
|
-
assert.throws(() => term.resize(1, 1));
|
|
147
|
-
done();
|
|
148
|
-
});
|
|
149
|
-
term.destroy();
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
describe('Args as CommandLine', () => {
|
|
155
|
-
it('should not fail running a file containing a space in the path', function (done) {
|
|
156
|
-
this.timeout(10000);
|
|
157
|
-
const spaceFolder = path.resolve(__dirname, '..', 'fixtures', 'space folder');
|
|
158
|
-
if (!fs.existsSync(spaceFolder)) {
|
|
159
|
-
fs.mkdirSync(spaceFolder);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const cmdCopiedPath = path.resolve(spaceFolder, 'cmd.exe');
|
|
163
|
-
const data = fs.readFileSync(`${process.env.windir}\\System32\\cmd.exe`);
|
|
164
|
-
fs.writeFileSync(cmdCopiedPath, data);
|
|
165
|
-
|
|
166
|
-
if (!fs.existsSync(cmdCopiedPath)) {
|
|
167
|
-
// Skip test if git bash isn't installed
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
const term = new WindowsTerminal(cmdCopiedPath, '/c echo "hello world"', { useConptyDll });
|
|
171
|
-
let result = '';
|
|
172
|
-
term.on('data', (data) => {
|
|
173
|
-
result += data;
|
|
174
|
-
});
|
|
175
|
-
term.on('exit', () => {
|
|
176
|
-
assert.ok(result.indexOf('hello world') >= 1);
|
|
177
|
-
done();
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
describe('env', () => {
|
|
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%', { useConptyDll, env: { FOO: 'BAR' }});
|
|
186
|
-
let result = '';
|
|
187
|
-
term.on('data', (data) => {
|
|
188
|
-
result += data;
|
|
189
|
-
});
|
|
190
|
-
term.on('exit', () => {
|
|
191
|
-
assert.ok(result.indexOf('BAR') >= 0);
|
|
192
|
-
done();
|
|
193
|
-
});
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
describe('On close', () => {
|
|
198
|
-
it('should return process zero exit codes', function (done) {
|
|
199
|
-
this.timeout(10000);
|
|
200
|
-
const term = new WindowsTerminal('cmd.exe', '/C exit', { useConptyDll });
|
|
201
|
-
term.on('exit', (code) => {
|
|
202
|
-
assert.strictEqual(code, 0);
|
|
203
|
-
done();
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
|
|
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', { useConptyDll });
|
|
210
|
-
term.on('exit', (code) => {
|
|
211
|
-
assert.strictEqual(code, 2);
|
|
212
|
-
done();
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
describe('Write', () => {
|
|
218
|
-
it('should accept input', function (done) {
|
|
219
|
-
this.timeout(10000);
|
|
220
|
-
const term = new WindowsTerminal('cmd.exe', '', { useConptyDll });
|
|
221
|
-
term.write('exit\r');
|
|
222
|
-
term.on('exit', () => {
|
|
223
|
-
done();
|
|
224
|
-
});
|
|
225
|
-
});
|
|
226
|
-
});
|
|
227
|
-
});
|
|
228
|
-
});
|
|
229
|
-
}
|
package/src/windowsTerminal.ts
DELETED
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (c) 2012-2015, Christopher Jeffrey, Peter Sunde (MIT License)
|
|
3
|
-
* Copyright (c) 2016, Daniel Imms (MIT License).
|
|
4
|
-
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { Socket } from 'net';
|
|
8
|
-
import { Terminal, DEFAULT_COLS, DEFAULT_ROWS } from './terminal';
|
|
9
|
-
import { WindowsPtyAgent } from './windowsPtyAgent';
|
|
10
|
-
import { IPtyOpenOptions, IWindowsPtyForkOptions } from './interfaces';
|
|
11
|
-
import { ArgvOrCommandLine } from './types';
|
|
12
|
-
import { assign } from './utils';
|
|
13
|
-
|
|
14
|
-
const DEFAULT_FILE = 'cmd.exe';
|
|
15
|
-
const DEFAULT_NAME = 'Windows Shell';
|
|
16
|
-
|
|
17
|
-
export class WindowsTerminal extends Terminal {
|
|
18
|
-
private _isReady: boolean;
|
|
19
|
-
private _deferreds: { run: () => void }[];
|
|
20
|
-
private _agent: WindowsPtyAgent;
|
|
21
|
-
|
|
22
|
-
constructor(file?: string, args?: ArgvOrCommandLine, opt?: IWindowsPtyForkOptions) {
|
|
23
|
-
super(opt);
|
|
24
|
-
|
|
25
|
-
this._checkType('args', args, 'string', true);
|
|
26
|
-
|
|
27
|
-
// Initialize arguments
|
|
28
|
-
args = args || [];
|
|
29
|
-
file = file || DEFAULT_FILE;
|
|
30
|
-
opt = opt || {};
|
|
31
|
-
opt.env = opt.env || process.env;
|
|
32
|
-
|
|
33
|
-
if (opt.encoding) {
|
|
34
|
-
console.warn('Setting encoding on Windows is not supported');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const env = assign({}, opt.env);
|
|
38
|
-
this._cols = opt.cols || DEFAULT_COLS;
|
|
39
|
-
this._rows = opt.rows || DEFAULT_ROWS;
|
|
40
|
-
const cwd = opt.cwd || process.cwd();
|
|
41
|
-
const name = opt.name || env.TERM || DEFAULT_NAME;
|
|
42
|
-
const parsedEnv = this._parseEnv(env);
|
|
43
|
-
|
|
44
|
-
// If the terminal is ready
|
|
45
|
-
this._isReady = false;
|
|
46
|
-
|
|
47
|
-
// Functions that need to run after `ready` event is emitted.
|
|
48
|
-
this._deferreds = [];
|
|
49
|
-
|
|
50
|
-
// Create new termal.
|
|
51
|
-
this._agent = new WindowsPtyAgent(file, args, parsedEnv, cwd, this._cols, this._rows, false, opt.useConptyDll, opt.conptyInheritCursor);
|
|
52
|
-
this._socket = this._agent.outSocket;
|
|
53
|
-
|
|
54
|
-
// Not available until `ready` event emitted.
|
|
55
|
-
this._pid = this._agent.innerPid;
|
|
56
|
-
this._fd = this._agent.fd;
|
|
57
|
-
this._pty = this._agent.pty;
|
|
58
|
-
|
|
59
|
-
// The forked windows terminal is not available until `ready` event is
|
|
60
|
-
// emitted.
|
|
61
|
-
this._socket.on('ready_datapipe', () => {
|
|
62
|
-
|
|
63
|
-
// Run deferreds and set ready state once the first data event is received.
|
|
64
|
-
this._socket.once('data', () => {
|
|
65
|
-
// Wait until the first data event is fired then we can run deferreds.
|
|
66
|
-
if (!this._isReady) {
|
|
67
|
-
// Terminal is now ready and we can avoid having to defer method
|
|
68
|
-
// calls.
|
|
69
|
-
this._isReady = true;
|
|
70
|
-
|
|
71
|
-
// Execute all deferred methods
|
|
72
|
-
this._deferreds.forEach(fn => {
|
|
73
|
-
// NB! In order to ensure that `this` has all its references
|
|
74
|
-
// updated any variable that need to be available in `this` before
|
|
75
|
-
// the deferred is run has to be declared above this forEach
|
|
76
|
-
// statement.
|
|
77
|
-
fn.run();
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
// Reset
|
|
81
|
-
this._deferreds = [];
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// Shutdown if `error` event is emitted.
|
|
86
|
-
this._socket.on('error', err => {
|
|
87
|
-
// Close terminal session.
|
|
88
|
-
this._close();
|
|
89
|
-
|
|
90
|
-
// EIO, happens when someone closes our child process: the only process
|
|
91
|
-
// in the terminal.
|
|
92
|
-
// node < 0.6.14: errno 5
|
|
93
|
-
// node >= 0.6.14: read EIO
|
|
94
|
-
if ((<any>err).code) {
|
|
95
|
-
if (~(<any>err).code.indexOf('errno 5') || ~(<any>err).code.indexOf('EIO')) return;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Throw anything else.
|
|
99
|
-
if (this.listeners('error').length < 2) {
|
|
100
|
-
throw err;
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
// Cleanup after the socket is closed.
|
|
105
|
-
this._socket.on('close', () => {
|
|
106
|
-
this.emit('exit', this._agent.exitCode);
|
|
107
|
-
this._close();
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
this._file = file;
|
|
113
|
-
this._name = name;
|
|
114
|
-
|
|
115
|
-
this._readable = true;
|
|
116
|
-
this._writable = true;
|
|
117
|
-
|
|
118
|
-
this._forwardEvents();
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
protected _write(data: string | Buffer): void {
|
|
122
|
-
this._defer(this._doWrite, data);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
private _doWrite(data: string | Buffer): void {
|
|
126
|
-
this._agent.inSocket.write(data);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* openpty
|
|
131
|
-
*/
|
|
132
|
-
|
|
133
|
-
public static open(options?: IPtyOpenOptions): void {
|
|
134
|
-
throw new Error('open() not supported on windows, use Fork() instead.');
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* TTY
|
|
139
|
-
*/
|
|
140
|
-
|
|
141
|
-
public resize(cols: number, rows: number): void {
|
|
142
|
-
if (cols <= 0 || rows <= 0 || isNaN(cols) || isNaN(rows) || cols === Infinity || rows === Infinity) {
|
|
143
|
-
throw new Error('resizing must be done using positive cols and rows');
|
|
144
|
-
}
|
|
145
|
-
this._deferNoArgs(() => {
|
|
146
|
-
this._agent.resize(cols, rows);
|
|
147
|
-
this._cols = cols;
|
|
148
|
-
this._rows = rows;
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
public clear(): void {
|
|
153
|
-
this._deferNoArgs(() => {
|
|
154
|
-
this._agent.clear();
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
public destroy(): void {
|
|
159
|
-
this._deferNoArgs(() => {
|
|
160
|
-
this.kill();
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
public kill(signal?: string): void {
|
|
165
|
-
this._deferNoArgs(() => {
|
|
166
|
-
if (signal) {
|
|
167
|
-
throw new Error('Signals not supported on windows.');
|
|
168
|
-
}
|
|
169
|
-
this._close();
|
|
170
|
-
this._agent.kill();
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
private _deferNoArgs<A>(deferredFn: () => void): void {
|
|
175
|
-
// If the terminal is ready, execute.
|
|
176
|
-
if (this._isReady) {
|
|
177
|
-
deferredFn.call(this);
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Queue until terminal is ready.
|
|
182
|
-
this._deferreds.push({
|
|
183
|
-
run: () => deferredFn.call(this)
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
private _defer<A>(deferredFn: (arg: A) => void, arg: A): void {
|
|
188
|
-
// If the terminal is ready, execute.
|
|
189
|
-
if (this._isReady) {
|
|
190
|
-
deferredFn.call(this, arg);
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Queue until terminal is ready.
|
|
195
|
-
this._deferreds.push({
|
|
196
|
-
run: () => deferredFn.call(this, arg)
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
public get process(): string { return this._name; }
|
|
201
|
-
public get master(): Socket { throw new Error('master is not supported on Windows'); }
|
|
202
|
-
public get slave(): Socket { throw new Error('slave is not supported on Windows'); }
|
|
203
|
-
}
|