node-pty 1.1.0-beta9 → 1.2.0-beta.1
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 +2 -0
- package/binding.gyp +7 -4
- package/deps/winpty/src/agent/Agent.cc +11 -3
- package/deps/winpty/src/winpty.gyp +29 -1
- package/lib/conpty_console_list_agent.js +3 -7
- package/lib/conpty_console_list_agent.js.map +1 -1
- package/lib/index.js +2 -1
- package/lib/index.js.map +1 -1
- package/lib/terminal.js.map +1 -1
- package/lib/terminal.test.js.map +1 -1
- package/lib/unixTerminal.js +78 -37
- package/lib/unixTerminal.js.map +1 -1
- package/lib/unixTerminal.test.js +3 -5
- package/lib/unixTerminal.test.js.map +1 -1
- package/lib/utils.js +23 -1
- package/lib/utils.js.map +1 -1
- package/lib/windowsConoutConnection.js +6 -3
- package/lib/windowsConoutConnection.js.map +1 -1
- package/lib/windowsPtyAgent.js +45 -48
- package/lib/windowsPtyAgent.js.map +1 -1
- package/lib/windowsTerminal.js +19 -21
- package/lib/windowsTerminal.js.map +1 -1
- package/lib/windowsTerminal.test.js +40 -26
- package/lib/windowsTerminal.test.js.map +1 -1
- package/lib/worker/conoutSocketWorker.js.map +1 -1
- package/package.json +6 -2
- 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/win32-arm64/conpty/OpenConsole.exe +0 -0
- package/prebuilds/win32-arm64/conpty/conpty.dll +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-arm64/pty.node +0 -0
- package/prebuilds/win32-arm64/pty.pdb +0 -0
- package/prebuilds/win32-arm64/winpty-agent.exe +0 -0
- package/prebuilds/win32-arm64/winpty-agent.pdb +0 -0
- package/prebuilds/win32-arm64/winpty.dll +0 -0
- package/prebuilds/win32-arm64/winpty.pdb +0 -0
- package/prebuilds/win32-x64/conpty/OpenConsole.exe +0 -0
- package/prebuilds/win32-x64/conpty/conpty.dll +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/prebuilds/win32-x64/pty.node +0 -0
- package/prebuilds/win32-x64/pty.pdb +0 -0
- package/prebuilds/win32-x64/winpty-agent.exe +0 -0
- package/prebuilds/win32-x64/winpty-agent.pdb +0 -0
- package/prebuilds/win32-x64/winpty.dll +0 -0
- package/prebuilds/win32-x64/winpty.pdb +0 -0
- package/scripts/gen-compile-commands.js +8 -0
- package/scripts/{publish.js → increment-version.js} +10 -18
- package/scripts/post-install.js +48 -10
- package/scripts/prebuild.js +34 -0
- package/src/conpty_console_list_agent.ts +2 -6
- package/src/index.ts +2 -1
- package/src/interfaces.ts +2 -1
- package/src/native.d.ts +5 -5
- package/src/terminal.test.ts +1 -1
- package/src/terminal.ts +2 -2
- package/src/unix/pty.cc +28 -11
- package/src/unixTerminal.test.ts +3 -6
- package/src/unixTerminal.ts +101 -34
- package/src/utils.ts +20 -0
- package/src/win/conpty.cc +141 -55
- package/src/win/conpty.h +41 -0
- package/src/win/path_util.cc +23 -24
- package/src/windowsConoutConnection.ts +6 -3
- package/src/windowsPtyAgent.ts +42 -43
- package/src/windowsTerminal.test.ts +46 -33
- package/src/windowsTerminal.ts +24 -29
- package/src/worker/conoutSocketWorker.ts +2 -2
- package/third_party/conpty/1.23.251008001/win10-arm64/OpenConsole.exe +0 -0
- package/third_party/conpty/1.23.251008001/win10-arm64/conpty.dll +0 -0
- package/third_party/conpty/1.23.251008001/win10-x64/OpenConsole.exe +0 -0
- package/third_party/conpty/1.23.251008001/win10-x64/conpty.dll +0 -0
- package/typings/node-pty.d.ts +9 -1
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025, Microsoft Corporation (MIT License).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { execSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
console.log(`\x1b[32m> Generating compile_commands.json...\x1b[0m`);
|
|
8
|
+
execSync('npx --offline node-gyp configure -- -f compile_commands_json');
|
|
@@ -7,30 +7,19 @@ const fs = require('fs');
|
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const packageJson = require('../package.json');
|
|
9
9
|
|
|
10
|
-
// Setup auth
|
|
11
|
-
fs.writeFileSync(`${process.env['HOME']}/.npmrc`, `//registry.npmjs.org/:_authToken=${process.env['NPM_AUTH_TOKEN']}`);
|
|
12
|
-
|
|
13
10
|
// Determine if this is a stable or beta release
|
|
14
11
|
const publishedVersions = getPublishedVersions();
|
|
15
|
-
const isStableRelease = publishedVersions.
|
|
12
|
+
const isStableRelease = !publishedVersions.includes(packageJson.version);
|
|
16
13
|
|
|
17
14
|
// Get the next version
|
|
18
|
-
|
|
19
|
-
console.log(`
|
|
15
|
+
const nextVersion = isStableRelease ? packageJson.version : getNextBetaVersion();
|
|
16
|
+
console.log(`Setting version to ${nextVersion}`);
|
|
20
17
|
|
|
21
18
|
// Set the version in package.json
|
|
22
19
|
const packageJsonFile = path.resolve(__dirname, '..', 'package.json');
|
|
23
20
|
packageJson.version = nextVersion;
|
|
24
21
|
fs.writeFileSync(packageJsonFile, JSON.stringify(packageJson, null, 2));
|
|
25
22
|
|
|
26
|
-
// Publish
|
|
27
|
-
const args = ['publish'];
|
|
28
|
-
if (!isStableRelease) {
|
|
29
|
-
args.push('--tag', 'beta');
|
|
30
|
-
}
|
|
31
|
-
const result = cp.spawn('npm', args, { stdio: 'inherit' });
|
|
32
|
-
result.on('exit', code => process.exit(code));
|
|
33
|
-
|
|
34
23
|
function getNextBetaVersion() {
|
|
35
24
|
if (!/^[0-9]+\.[0-9]+\.[0-9]+$/.exec(packageJson.version)) {
|
|
36
25
|
console.error('The package.json version must be of the form x.y.z');
|
|
@@ -41,7 +30,7 @@ function getNextBetaVersion() {
|
|
|
41
30
|
const nextStableVersion = `${stableVersion[0]}.${parseInt(stableVersion[1]) + 1}.0`;
|
|
42
31
|
const publishedVersions = getPublishedVersions(nextStableVersion, tag);
|
|
43
32
|
if (publishedVersions.length === 0) {
|
|
44
|
-
return `${nextStableVersion}-${tag}1`;
|
|
33
|
+
return `${nextStableVersion}-${tag}.1`;
|
|
45
34
|
}
|
|
46
35
|
const latestPublishedVersion = publishedVersions.sort((a, b) => {
|
|
47
36
|
const aVersion = parseInt(a.substr(a.search(/[0-9]+$/)));
|
|
@@ -49,14 +38,17 @@ function getNextBetaVersion() {
|
|
|
49
38
|
return aVersion > bVersion ? -1 : 1;
|
|
50
39
|
})[0];
|
|
51
40
|
const latestTagVersion = parseInt(latestPublishedVersion.substr(latestPublishedVersion.search(/[0-9]+$/)), 10);
|
|
52
|
-
return `${nextStableVersion}-${tag}
|
|
41
|
+
return `${nextStableVersion}-${tag}.${latestTagVersion + 1}`;
|
|
53
42
|
}
|
|
54
43
|
|
|
55
44
|
function getPublishedVersions(version, tag) {
|
|
56
|
-
const
|
|
45
|
+
const isWin32 = process.platform === 'win32';
|
|
46
|
+
const versionsProcess = isWin32 ?
|
|
47
|
+
cp.spawnSync('npm.cmd', ['view', packageJson.name, 'versions', '--json'], { shell: true }) :
|
|
48
|
+
cp.spawnSync('npm', ['view', packageJson.name, 'versions', '--json']);
|
|
57
49
|
const versionsJson = JSON.parse(versionsProcess.stdout);
|
|
58
50
|
if (tag) {
|
|
59
|
-
return versionsJson.filter(v => !v.search(new RegExp(`${version}-${tag}[0-9]+`)));
|
|
51
|
+
return versionsJson.filter(v => !v.search(new RegExp(`${version}-${tag}\.[0-9]+`)));
|
|
60
52
|
}
|
|
61
53
|
return versionsJson;
|
|
62
54
|
}
|
package/scripts/post-install.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
var path = require('path');
|
|
1
|
+
//@ts-check
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const RELEASE_DIR = path.join(__dirname, '../build/Release');
|
|
8
|
+
const BUILD_FILES = [
|
|
6
9
|
path.join(RELEASE_DIR, 'conpty.node'),
|
|
7
10
|
path.join(RELEASE_DIR, 'conpty.pdb'),
|
|
8
11
|
path.join(RELEASE_DIR, 'conpty_console_list.node'),
|
|
@@ -15,14 +18,19 @@ var BUILD_FILES = [
|
|
|
15
18
|
path.join(RELEASE_DIR, 'winpty.dll'),
|
|
16
19
|
path.join(RELEASE_DIR, 'winpty.pdb')
|
|
17
20
|
];
|
|
21
|
+
const CONPTY_DIR = path.join(__dirname, '../third_party/conpty');
|
|
22
|
+
const CONPTY_SUPPORTED_ARCH = ['x64', 'arm64'];
|
|
23
|
+
|
|
24
|
+
console.log('\x1b[32m> Cleaning release folder...\x1b[0m');
|
|
18
25
|
|
|
19
|
-
|
|
26
|
+
/** @param {string} folder */
|
|
27
|
+
function cleanFolderRecursive(folder) {
|
|
20
28
|
var files = [];
|
|
21
|
-
if(
|
|
29
|
+
if (fs.existsSync(folder)) {
|
|
22
30
|
files = fs.readdirSync(folder);
|
|
23
31
|
files.forEach(function(file,index) {
|
|
24
32
|
var curPath = path.join(folder, file);
|
|
25
|
-
if(fs.lstatSync(curPath).isDirectory()) { // recurse
|
|
33
|
+
if (fs.lstatSync(curPath).isDirectory()) { // recurse
|
|
26
34
|
cleanFolderRecursive(curPath);
|
|
27
35
|
fs.rmdirSync(curPath);
|
|
28
36
|
} else if (BUILD_FILES.indexOf(curPath) < 0){ // delete file
|
|
@@ -36,7 +44,37 @@ try {
|
|
|
36
44
|
cleanFolderRecursive(RELEASE_DIR);
|
|
37
45
|
} catch(e) {
|
|
38
46
|
console.log(e);
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log(`\x1b[32m> Moving conpty.dll...\x1b[0m`);
|
|
51
|
+
if (os.platform() !== 'win32') {
|
|
52
|
+
console.log(' SKIPPED (not Windows)');
|
|
53
|
+
} else {
|
|
54
|
+
let windowsArch;
|
|
55
|
+
if (process.env.npm_config_arch) {
|
|
56
|
+
windowsArch = process.env.npm_config_arch;
|
|
57
|
+
console.log(` Using $npm_config_arch: ${windowsArch}`);
|
|
58
|
+
} else {
|
|
59
|
+
windowsArch = os.arch();
|
|
60
|
+
console.log(` Using os.arch(): ${windowsArch}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!CONPTY_SUPPORTED_ARCH.includes(windowsArch)) {
|
|
64
|
+
console.log(` SKIPPED (unsupported architecture ${windowsArch})`);
|
|
65
|
+
} else {
|
|
66
|
+
const versionFolder = fs.readdirSync(CONPTY_DIR)[0];
|
|
67
|
+
console.log(` Found version ${versionFolder}`);
|
|
68
|
+
const sourceFolder = path.join(CONPTY_DIR, versionFolder, `win10-${windowsArch}`);
|
|
69
|
+
const destFolder = path.join(RELEASE_DIR, 'conpty');
|
|
70
|
+
fs.mkdirSync(destFolder, { recursive: true });
|
|
71
|
+
for (const file of ['conpty.dll', 'OpenConsole.exe']) {
|
|
72
|
+
const sourceFile = path.join(sourceFolder, file);
|
|
73
|
+
const destFile = path.join(destFolder, file);
|
|
74
|
+
console.log(` Copying ${sourceFile} -> ${destFile}`);
|
|
75
|
+
fs.copyFileSync(sourceFile, destFile);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
42
78
|
}
|
|
79
|
+
|
|
80
|
+
process.exit(0);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* This script checks for the prebuilt binaries for the current platform and
|
|
8
|
+
* architecture. It exits with 0 if prebuilds are found and 1 if not.
|
|
9
|
+
*
|
|
10
|
+
* If npm_config_build_from_source is set then it removes the prebuilds for the
|
|
11
|
+
* current platform so they are not loaded at runtime.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* node scripts/prebuild.js
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const PREBUILDS_ROOT = path.join(__dirname, '..', 'prebuilds');
|
|
18
|
+
const PREBUILD_DIR = path.join(__dirname, '..', 'prebuilds', `${process.platform}-${process.arch}`);
|
|
19
|
+
|
|
20
|
+
// Do not use prebuilds when npm_config_build_from_source is set
|
|
21
|
+
if (process.env.npm_config_build_from_source === 'true') {
|
|
22
|
+
console.log('\x1b[33m> Removing prebuilds and rebuilding because npm_config_build_from_source is set\x1b[0m');
|
|
23
|
+
fs.rmSync(PREBUILDS_ROOT, { recursive: true, force: true });
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Check whether the correct prebuilt files exist
|
|
28
|
+
console.log('\x1b[32m> Checking prebuilds...\x1b[0m');
|
|
29
|
+
if (!fs.existsSync(PREBUILD_DIR)) {
|
|
30
|
+
console.log(`\x1b[33m> Rebuilding because directory ${PREBUILD_DIR} does not exist\x1b[0m`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
process.exit(0);
|
|
@@ -6,13 +6,9 @@
|
|
|
6
6
|
* single console attached to a process.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
try {
|
|
11
|
-
getConsoleProcessList = require('../build/Release/conpty_console_list.node').getConsoleProcessList;
|
|
12
|
-
} catch (err) {
|
|
13
|
-
getConsoleProcessList = require('../build/Debug/conpty_console_list.node').getConsoleProcessList;
|
|
14
|
-
}
|
|
9
|
+
import { loadNativeModule } from './utils';
|
|
15
10
|
|
|
11
|
+
const getConsoleProcessList = loadNativeModule('conpty_console_list').module.getConsoleProcessList;
|
|
16
12
|
const shellPid = parseInt(process.argv[2], 10);
|
|
17
13
|
const consoleProcessList = getConsoleProcessList(shellPid);
|
|
18
14
|
process.send!({ consoleProcessList });
|
package/src/index.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { ITerminal, IPtyOpenOptions, IPtyForkOptions, IWindowsPtyForkOptions } from './interfaces';
|
|
8
8
|
import { ArgvOrCommandLine } from './types';
|
|
9
|
+
import { loadNativeModule } from './utils';
|
|
9
10
|
|
|
10
11
|
let terminalCtor: any;
|
|
11
12
|
if (process.platform === 'win32') {
|
|
@@ -48,4 +49,4 @@ export function open(options: IPtyOpenOptions): ITerminal {
|
|
|
48
49
|
* Expose the native API when not Windows, note that this is not public API and
|
|
49
50
|
* could be removed at any time.
|
|
50
51
|
*/
|
|
51
|
-
export const native = (process.platform !== 'win32' ?
|
|
52
|
+
export const native = (process.platform !== 'win32' ? loadNativeModule('pty').module : null);
|
package/src/interfaces.ts
CHANGED
|
@@ -22,7 +22,7 @@ export interface ITerminal {
|
|
|
22
22
|
* Writes data to the socket.
|
|
23
23
|
* @param data The data to write.
|
|
24
24
|
*/
|
|
25
|
-
write(data: string): void;
|
|
25
|
+
write(data: string | Buffer): void;
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Resize the pty.
|
|
@@ -119,6 +119,7 @@ export interface IPtyForkOptions extends IBasePtyForkOptions {
|
|
|
119
119
|
|
|
120
120
|
export interface IWindowsPtyForkOptions extends IBasePtyForkOptions {
|
|
121
121
|
useConpty?: boolean;
|
|
122
|
+
useConptyDll?: boolean;
|
|
122
123
|
conptyInheritCursor?: boolean;
|
|
123
124
|
}
|
|
124
125
|
|
package/src/native.d.ts
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
interface IConptyNative {
|
|
6
|
-
startProcess(file: string, cols: number, rows: number, debug: boolean, pipeName: string, conptyInheritCursor: boolean): IConptyProcess;
|
|
7
|
-
connect(ptyId: number, commandLine: string, cwd: string, env: string[], onExitCallback: (exitCode: number) => void): { pid: number };
|
|
8
|
-
resize(ptyId: number, cols: number, rows: number): void;
|
|
9
|
-
clear(ptyId: number): void;
|
|
10
|
-
kill(ptyId: number): void;
|
|
6
|
+
startProcess(file: string, cols: number, rows: number, debug: boolean, pipeName: string, conptyInheritCursor: boolean, useConptyDll: boolean): IConptyProcess;
|
|
7
|
+
connect(ptyId: number, commandLine: string, cwd: string, env: string[], useConptyDll: boolean, onExitCallback: (exitCode: number) => void): { pid: number };
|
|
8
|
+
resize(ptyId: number, cols: number, rows: number, useConptyDll: boolean): void;
|
|
9
|
+
clear(ptyId: number, useConptyDll: boolean): void;
|
|
10
|
+
kill(ptyId: number, useConptyDll: boolean): void;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
interface IWinptyNative {
|
package/src/terminal.test.ts
CHANGED
|
@@ -23,7 +23,7 @@ class TestTerminal extends Terminal {
|
|
|
23
23
|
public checkType<T>(name: string, value: T, type: string, allowArray: boolean = false): void {
|
|
24
24
|
this._checkType(name, value, type, allowArray);
|
|
25
25
|
}
|
|
26
|
-
protected _write(data: string): void {
|
|
26
|
+
protected _write(data: string | Buffer): void {
|
|
27
27
|
throw new Error('Method not implemented.');
|
|
28
28
|
}
|
|
29
29
|
public resize(cols: number, rows: number): void {
|
package/src/terminal.ts
CHANGED
|
@@ -74,9 +74,9 @@ export abstract class Terminal implements ITerminal {
|
|
|
74
74
|
this._checkType('encoding', opt.encoding ? opt.encoding : undefined, 'string');
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
protected abstract _write(data: string): void;
|
|
77
|
+
protected abstract _write(data: string | Buffer): void;
|
|
78
78
|
|
|
79
|
-
public write(data: string): void {
|
|
79
|
+
public write(data: string | Buffer): void {
|
|
80
80
|
if (this.handleFlowControl) {
|
|
81
81
|
// PAUSE/RESUME messages are not forwarded to the pty
|
|
82
82
|
if (data === this._flowControlPause) {
|
package/src/unix/pty.cc
CHANGED
|
@@ -41,6 +41,10 @@
|
|
|
41
41
|
#include <util.h>
|
|
42
42
|
#elif defined(__FreeBSD__)
|
|
43
43
|
#include <libutil.h>
|
|
44
|
+
#include <termios.h>
|
|
45
|
+
#elif defined(__OpenBSD__)
|
|
46
|
+
#include <util.h>
|
|
47
|
+
#include <termios.h>
|
|
44
48
|
#endif
|
|
45
49
|
|
|
46
50
|
/* Some platforms name VWERASE and VDISCARD differently */
|
|
@@ -189,9 +193,22 @@ void SetupExitCallback(Napi::Env env, Napi::Function cb, pid_t pid) {
|
|
|
189
193
|
exit_event->signal_code = WTERMSIG(stat_loc);
|
|
190
194
|
}
|
|
191
195
|
auto status = tsfn.BlockingCall(exit_event, callback); // In main thread
|
|
192
|
-
|
|
196
|
+
switch (status) {
|
|
197
|
+
case napi_closing:
|
|
198
|
+
break;
|
|
199
|
+
|
|
200
|
+
case napi_queue_full:
|
|
201
|
+
Napi::Error::Fatal("SetupExitCallback", "Queue was full");
|
|
193
202
|
|
|
194
|
-
|
|
203
|
+
case napi_ok:
|
|
204
|
+
if (tsfn.Release() != napi_ok) {
|
|
205
|
+
Napi::Error::Fatal("SetupExitCallback", "ThreadSafeFunction.Release() failed");
|
|
206
|
+
}
|
|
207
|
+
break;
|
|
208
|
+
|
|
209
|
+
default:
|
|
210
|
+
Napi::Error::Fatal("SetupExitCallback", "ThreadSafeFunction.BlockingCall() failed");
|
|
211
|
+
}
|
|
195
212
|
});
|
|
196
213
|
}
|
|
197
214
|
|
|
@@ -353,7 +370,7 @@ Napi::Value PtyFork(const Napi::CallbackInfo& info) {
|
|
|
353
370
|
int err = -1;
|
|
354
371
|
pty_posix_spawn(argv, env, term, &winp, &master, &pid, &err);
|
|
355
372
|
if (err != 0) {
|
|
356
|
-
throw Napi::Error::New(napiEnv, "
|
|
373
|
+
throw Napi::Error::New(napiEnv, std::string("pty_posix_spawn failed with error: ") + std::to_string(err) + " (" + strerror(err) + ")");
|
|
357
374
|
}
|
|
358
375
|
if (pty_nonblock(master) == -1) {
|
|
359
376
|
throw Napi::Error::New(napiEnv, "Could not set master fd to nonblocking.");
|
|
@@ -431,10 +448,10 @@ Napi::Value PtyFork(const Napi::CallbackInfo& info) {
|
|
|
431
448
|
}
|
|
432
449
|
#endif
|
|
433
450
|
|
|
434
|
-
Napi::Object obj = Napi::Object::New(napiEnv);
|
|
435
|
-
obj.Set("fd", Napi::Number::New(napiEnv, master));
|
|
436
|
-
obj.Set("pid", Napi::Number::New(napiEnv, pid));
|
|
437
|
-
obj.Set("pty", Napi::String::New(napiEnv, ptsname(master)));
|
|
451
|
+
Napi::Object obj = Napi::Object::New(napiEnv);
|
|
452
|
+
obj.Set("fd", Napi::Number::New(napiEnv, master));
|
|
453
|
+
obj.Set("pid", Napi::Number::New(napiEnv, pid));
|
|
454
|
+
obj.Set("pty", Napi::String::New(napiEnv, ptsname(master)));
|
|
438
455
|
|
|
439
456
|
// Set up process exit callback.
|
|
440
457
|
Napi::Function cb = info[10].As<Napi::Function>();
|
|
@@ -475,10 +492,10 @@ Napi::Value PtyOpen(const Napi::CallbackInfo& info) {
|
|
|
475
492
|
throw Napi::Error::New(env, "Could not set slave fd to nonblocking.");
|
|
476
493
|
}
|
|
477
494
|
|
|
478
|
-
Napi::Object obj = Napi::Object::New(env);
|
|
479
|
-
obj.Set("master", Napi::Number::New(env, master));
|
|
480
|
-
obj.Set("slave", Napi::Number::New(env, slave));
|
|
481
|
-
obj.Set("pty", Napi::String::New(env, ptsname(master)));
|
|
495
|
+
Napi::Object obj = Napi::Object::New(env);
|
|
496
|
+
obj.Set("master", Napi::Number::New(env, master));
|
|
497
|
+
obj.Set("slave", Napi::Number::New(env, slave));
|
|
498
|
+
obj.Set("pty", Napi::String::New(env, ptsname(master)));
|
|
482
499
|
|
|
483
500
|
return obj;
|
|
484
501
|
}
|
package/src/unixTerminal.test.ts
CHANGED
|
@@ -272,10 +272,9 @@ if (process.platform !== 'win32') {
|
|
|
272
272
|
ptyProcess.on('data', function (data) {
|
|
273
273
|
if (ptyProcess.process === 'Python') {
|
|
274
274
|
console.log('title', ptyProcess.process);
|
|
275
|
+
console.log('ready', ptyProcess.pid);
|
|
275
276
|
}
|
|
276
277
|
});
|
|
277
|
-
setTimeout(() => null, 500);
|
|
278
|
-
console.log('ready', ptyProcess.pid);
|
|
279
278
|
`;
|
|
280
279
|
const p = cp.spawn('node', ['-e', data]);
|
|
281
280
|
let sub = '';
|
|
@@ -283,12 +282,10 @@ if (process.platform !== 'win32') {
|
|
|
283
282
|
p.stdout.on('data', (data) => {
|
|
284
283
|
if (!data.toString().indexOf('title')) {
|
|
285
284
|
sub = data.toString().split(' ')[1].slice(0, -1);
|
|
286
|
-
setTimeout(() => {
|
|
287
|
-
process.kill(parseInt(pid), 'SIGINT');
|
|
288
|
-
p.kill('SIGINT');
|
|
289
|
-
}, 200);
|
|
290
285
|
} else if (!data.toString().indexOf('ready')) {
|
|
291
286
|
pid = data.toString().split(' ')[1].slice(0, -1);
|
|
287
|
+
process.kill(parseInt(pid), 'SIGINT');
|
|
288
|
+
p.kill('SIGINT');
|
|
292
289
|
}
|
|
293
290
|
});
|
|
294
291
|
p.on('exit', () => {
|
package/src/unixTerminal.ts
CHANGED
|
@@ -3,29 +3,18 @@
|
|
|
3
3
|
* Copyright (c) 2016, Daniel Imms (MIT License).
|
|
4
4
|
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
|
5
5
|
*/
|
|
6
|
+
import * as fs from 'fs';
|
|
6
7
|
import * as net from 'net';
|
|
7
8
|
import * as path from 'path';
|
|
9
|
+
import * as tty from 'tty';
|
|
8
10
|
import { Terminal, DEFAULT_COLS, DEFAULT_ROWS } from './terminal';
|
|
9
11
|
import { IProcessEnv, IPtyForkOptions, IPtyOpenOptions } from './interfaces';
|
|
10
|
-
import { ArgvOrCommandLine } from './types';
|
|
11
|
-
import { assign } from './utils';
|
|
12
|
-
|
|
13
|
-
let pty: IUnixNative;
|
|
14
|
-
let helperPath: string;
|
|
15
|
-
try {
|
|
16
|
-
pty = require('../build/Release/pty.node');
|
|
17
|
-
helperPath = '../build/Release/spawn-helper';
|
|
18
|
-
} catch (outerError) {
|
|
19
|
-
try {
|
|
20
|
-
pty = require('../build/Debug/pty.node');
|
|
21
|
-
helperPath = '../build/Debug/spawn-helper';
|
|
22
|
-
} catch (innerError) {
|
|
23
|
-
console.error('innerError', innerError);
|
|
24
|
-
// Re-throw the exception from the Release require if the Debug require fails as well
|
|
25
|
-
throw outerError;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
12
|
+
import { ArgvOrCommandLine, IDisposable } from './types';
|
|
13
|
+
import { assign, loadNativeModule } from './utils';
|
|
28
14
|
|
|
15
|
+
const native = loadNativeModule('pty');
|
|
16
|
+
const pty: IUnixNative = native.module;
|
|
17
|
+
let helperPath = native.dir + '/spawn-helper';
|
|
29
18
|
helperPath = path.resolve(__dirname, helperPath);
|
|
30
19
|
helperPath = helperPath.replace('app.asar', 'app.asar.unpacked');
|
|
31
20
|
helperPath = helperPath.replace('node_modules.asar', 'node_modules.asar.unpacked');
|
|
@@ -46,6 +35,9 @@ export class UnixTerminal extends Terminal {
|
|
|
46
35
|
|
|
47
36
|
private _boundClose: boolean = false;
|
|
48
37
|
private _emittedClose: boolean = false;
|
|
38
|
+
|
|
39
|
+
private _writeStream: CustomWriteStream;
|
|
40
|
+
|
|
49
41
|
private _master: net.Socket | undefined;
|
|
50
42
|
private _slave: net.Socket | undefined;
|
|
51
43
|
|
|
@@ -113,10 +105,11 @@ export class UnixTerminal extends Terminal {
|
|
|
113
105
|
// fork
|
|
114
106
|
const term = pty.fork(file, args, parsedEnv, cwd, this._cols, this._rows, uid, gid, (encoding === 'utf8'), helperPath, onexit);
|
|
115
107
|
|
|
116
|
-
this._socket = new
|
|
108
|
+
this._socket = new tty.ReadStream(term.fd);
|
|
117
109
|
if (encoding !== null) {
|
|
118
110
|
this._socket.setEncoding(encoding);
|
|
119
111
|
}
|
|
112
|
+
this._writeStream = new CustomWriteStream(term.fd, (encoding || undefined) as BufferEncoding);
|
|
120
113
|
|
|
121
114
|
// setup
|
|
122
115
|
this._socket.on('error', (err: any) => {
|
|
@@ -173,8 +166,8 @@ export class UnixTerminal extends Terminal {
|
|
|
173
166
|
this._forwardEvents();
|
|
174
167
|
}
|
|
175
168
|
|
|
176
|
-
protected _write(data: string): void {
|
|
177
|
-
this.
|
|
169
|
+
protected _write(data: string | Buffer): void {
|
|
170
|
+
this._writeStream.write(data);
|
|
178
171
|
}
|
|
179
172
|
|
|
180
173
|
/* Accessors */
|
|
@@ -203,13 +196,13 @@ export class UnixTerminal extends Terminal {
|
|
|
203
196
|
// open
|
|
204
197
|
const term: IUnixOpenProcess = pty.open(cols, rows);
|
|
205
198
|
|
|
206
|
-
self._master = new
|
|
199
|
+
self._master = new tty.ReadStream(term.master);
|
|
207
200
|
if (encoding !== null) {
|
|
208
201
|
self._master.setEncoding(encoding);
|
|
209
202
|
}
|
|
210
203
|
self._master.resume();
|
|
211
204
|
|
|
212
|
-
self._slave = new
|
|
205
|
+
self._slave = new tty.ReadStream(term.slave);
|
|
213
206
|
if (encoding !== null) {
|
|
214
207
|
self._slave.setEncoding(encoding);
|
|
215
208
|
}
|
|
@@ -250,6 +243,7 @@ export class UnixTerminal extends Terminal {
|
|
|
250
243
|
});
|
|
251
244
|
|
|
252
245
|
this._socket.destroy();
|
|
246
|
+
this._writeStream.dispose();
|
|
253
247
|
}
|
|
254
248
|
|
|
255
249
|
public kill(signal?: string): void {
|
|
@@ -264,7 +258,7 @@ export class UnixTerminal extends Terminal {
|
|
|
264
258
|
public get process(): string {
|
|
265
259
|
if (process.platform === 'darwin') {
|
|
266
260
|
const title = pty.process(this._fd);
|
|
267
|
-
return (title !== 'kernel_task'
|
|
261
|
+
return (title !== 'kernel_task') ? title : this._file;
|
|
268
262
|
}
|
|
269
263
|
|
|
270
264
|
return pty.process(this._fd, this._pty) || this._file;
|
|
@@ -305,17 +299,90 @@ export class UnixTerminal extends Terminal {
|
|
|
305
299
|
}
|
|
306
300
|
}
|
|
307
301
|
|
|
302
|
+
interface IWriteTask {
|
|
303
|
+
/** The buffer being written. */
|
|
304
|
+
buffer: Buffer;
|
|
305
|
+
/** The current offset of not yet written data. */
|
|
306
|
+
offset: number;
|
|
307
|
+
}
|
|
308
|
+
|
|
308
309
|
/**
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
*
|
|
310
|
+
* A custom write stream that writes directly to a file descriptor with proper
|
|
311
|
+
* handling of backpressure and errors. This avoids some event loop exhaustion
|
|
312
|
+
* issues that can occur when using the standard APIs in Node.
|
|
312
313
|
*/
|
|
313
|
-
class
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
314
|
+
class CustomWriteStream implements IDisposable {
|
|
315
|
+
|
|
316
|
+
private readonly _writeQueue: IWriteTask[] = [];
|
|
317
|
+
private _writeImmediate: NodeJS.Immediate | undefined;
|
|
318
|
+
|
|
319
|
+
constructor(
|
|
320
|
+
private readonly _fd: number,
|
|
321
|
+
private readonly _encoding: BufferEncoding
|
|
322
|
+
) {
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
dispose(): void {
|
|
326
|
+
clearImmediate(this._writeImmediate);
|
|
327
|
+
this._writeImmediate = undefined;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
write(data: string | Buffer): void {
|
|
331
|
+
// Writes are put in a queue and processed asynchronously in order to handle
|
|
332
|
+
// backpressure from the kernel buffer.
|
|
333
|
+
const buffer = typeof data === 'string'
|
|
334
|
+
? Buffer.from(data, this._encoding)
|
|
335
|
+
: Buffer.from(data);
|
|
336
|
+
|
|
337
|
+
if (buffer.byteLength !== 0) {
|
|
338
|
+
this._writeQueue.push({ buffer, offset: 0 });
|
|
339
|
+
if (this._writeQueue.length === 1) {
|
|
340
|
+
this._processWriteQueue();
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
private _processWriteQueue(): void {
|
|
346
|
+
this._writeImmediate = undefined;
|
|
347
|
+
|
|
348
|
+
if (this._writeQueue.length === 0) {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const task = this._writeQueue[0];
|
|
353
|
+
|
|
354
|
+
// Write to the underlying file descriptor and handle it directly, rather
|
|
355
|
+
// than using the `net.Socket`/`tty.WriteStream` wrappers which swallow and
|
|
356
|
+
// mask errors like EAGAIN and can cause the thread to block indefinitely.
|
|
357
|
+
fs.write(this._fd, task.buffer, task.offset, (err, written) => {
|
|
358
|
+
if (err) {
|
|
359
|
+
if ('code' in err && err.code === 'EAGAIN') {
|
|
360
|
+
// `setImmediate` is used to yield to the event loop and re-attempt
|
|
361
|
+
// the write later.
|
|
362
|
+
this._writeImmediate = setImmediate(() => this._processWriteQueue());
|
|
363
|
+
} else {
|
|
364
|
+
// Stop processing immediately on unexpected error and log
|
|
365
|
+
this._writeQueue.length = 0;
|
|
366
|
+
console.error('Unhandled pty write error', err);
|
|
367
|
+
}
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
task.offset += written;
|
|
372
|
+
if (task.offset >= task.buffer.byteLength) {
|
|
373
|
+
this._writeQueue.shift();
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Since there is more room in the kernel buffer, we can continue to write
|
|
377
|
+
// until we hit EAGAIN or exhaust the queue.
|
|
378
|
+
//
|
|
379
|
+
// Note that old versions of bash, like v3.2 which ships in macOS, appears
|
|
380
|
+
// to have a bug in its readline implementation that causes data
|
|
381
|
+
// corruption when writes to the pty happens too quickly. Instead of
|
|
382
|
+
// trying to workaround that we just accept it so that large pastes are as
|
|
383
|
+
// fast as possible.
|
|
384
|
+
// Context: https://github.com/microsoft/node-pty/issues/833
|
|
385
|
+
this._processWriteQueue();
|
|
386
|
+
});
|
|
320
387
|
}
|
|
321
388
|
}
|
package/src/utils.ts
CHANGED
|
@@ -7,3 +7,23 @@ export function assign(target: any, ...sources: any[]): any {
|
|
|
7
7
|
sources.forEach(source => Object.keys(source).forEach(key => target[key] = source[key]));
|
|
8
8
|
return target;
|
|
9
9
|
}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
export function loadNativeModule(name: string): {dir: string, module: any} {
|
|
13
|
+
// Check build, debug, and then prebuilds.
|
|
14
|
+
const dirs = ['build/Release', 'build/Debug', `prebuilds/${process.platform}-${process.arch}`];
|
|
15
|
+
// Check relative to the parent dir for unbundled and then the current dir for bundled
|
|
16
|
+
const relative = ['..', '.'];
|
|
17
|
+
let lastError: unknown;
|
|
18
|
+
for (const d of dirs) {
|
|
19
|
+
for (const r of relative) {
|
|
20
|
+
const dir = `${r}/${d}/`;
|
|
21
|
+
try {
|
|
22
|
+
return { dir, module: require(`${dir}/${name}.node`) };
|
|
23
|
+
} catch (e) {
|
|
24
|
+
lastError = e;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
throw new Error(`Failed to load native module: ${name}.node, checked: ${dirs.join(', ')}: ${lastError}`);
|
|
29
|
+
}
|