@karinjs/node-pty
Version:
Use vite+tsup to recompile node-pty into esm, which is smaller in size.
908 lines (906 loc) • 25.7 kB
JavaScript
import { createRequire } from "node:module";
import { fileURLToPath } from "node:url";
import { dirname } from "node:path";
const require = createRequire(import.meta.url);
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
import { EventEmitter } from "events";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import { join } from "path";
import { Socket } from "net";
import { fork as fork$1 } from "child_process";
import { Worker } from "worker_threads";
import { C as ConoutWorkerMessage, g as getWorkerPipeName } from "./chunk/conout-DxH97NFz.js";
import * as tty from "tty";
function getAugmentedNamespace(n) {
if (Object.prototype.hasOwnProperty.call(n, "__esModule")) return n;
var f = n.default;
if (typeof f == "function") {
var a = function a2() {
if (this instanceof a2) {
return Reflect.construct(f, arguments, this.constructor);
}
return f.apply(this, arguments);
};
a.prototype = f.prototype;
} else a = {};
Object.defineProperty(a, "__esModule", { value: true });
Object.keys(n).forEach(function(k) {
var d = Object.getOwnPropertyDescriptor(n, k);
Object.defineProperty(a, k, d.get ? d : {
enumerable: true,
get: function() {
return n[k];
}
});
});
return a;
}
function commonjsRequire(path2) {
return require("../build/Release/pty.node");
}
class EventEmitter2 {
constructor() {
this._listeners = [];
}
get event() {
if (!this._event) {
this._event = (listener) => {
this._listeners.push(listener);
const disposable = {
dispose: () => {
for (let i = 0; i < this._listeners.length; i++) {
if (this._listeners[i] === listener) {
this._listeners.splice(i, 1);
return;
}
}
}
};
return disposable;
};
}
return this._event;
}
fire(data) {
const queue = [];
for (let i = 0; i < this._listeners.length; i++) {
queue.push(this._listeners[i]);
}
for (let i = 0; i < queue.length; i++) {
queue[i].call(void 0, data);
}
}
}
const DEFAULT_COLS = 80;
const DEFAULT_ROWS = 24;
const FLOW_CONTROL_PAUSE = "";
const FLOW_CONTROL_RESUME = "";
class Terminal {
constructor(opt) {
this._pid = 0;
this._fd = 0;
this._cols = 0;
this._rows = 0;
this._readable = false;
this._writable = false;
this._onData = new EventEmitter2();
this._onExit = new EventEmitter2();
this._internalee = new EventEmitter();
this.handleFlowControl = !!opt?.handleFlowControl;
this._flowControlPause = opt?.flowControlPause || FLOW_CONTROL_PAUSE;
this._flowControlResume = opt?.flowControlResume || FLOW_CONTROL_RESUME;
if (!opt) {
return;
}
this._checkType("name", opt.name ? opt.name : void 0, "string");
this._checkType("cols", opt.cols ? opt.cols : void 0, "number");
this._checkType("rows", opt.rows ? opt.rows : void 0, "number");
this._checkType("cwd", opt.cwd ? opt.cwd : void 0, "string");
this._checkType("env", opt.env ? opt.env : void 0, "object");
this._checkType("uid", opt.uid ? opt.uid : void 0, "number");
this._checkType("gid", opt.gid ? opt.gid : void 0, "number");
this._checkType("encoding", opt.encoding ? opt.encoding : void 0, "string");
}
get onData() {
return this._onData.event;
}
get onExit() {
return this._onExit.event;
}
get pid() {
return this._pid;
}
get cols() {
return this._cols;
}
get rows() {
return this._rows;
}
write(data) {
if (this.handleFlowControl) {
if (data === this._flowControlPause) {
this.pause();
return;
}
if (data === this._flowControlResume) {
this.resume();
return;
}
}
this._write(data);
}
_forwardEvents() {
this.on("data", (e) => this._onData.fire(e));
this.on("exit", (exitCode, signal) => this._onExit.fire({ exitCode, signal }));
}
_checkType(name, value, type, allowArray = false) {
if (value === void 0) {
return;
}
if (allowArray) {
if (Array.isArray(value)) {
value.forEach((v, i) => {
if (typeof v !== type) {
throw new Error(`${name}[${i}] must be a ${type} (not a ${typeof v[i]})`);
}
});
return;
}
}
if (typeof value !== type) {
throw new Error(`${name} must be a ${type} (not a ${typeof value})`);
}
}
/** See net.Socket.end */
end(data) {
this._socket.end(data);
}
/** See stream.Readable.pipe */
pipe(dest, options) {
return this._socket.pipe(dest, options);
}
/** See net.Socket.pause */
pause() {
return this._socket.pause();
}
/** See net.Socket.resume */
resume() {
return this._socket.resume();
}
/** See net.Socket.setEncoding */
setEncoding(encoding) {
if (this._socket._decoder) {
delete this._socket._decoder;
}
if (encoding) {
this._socket.setEncoding(encoding);
}
}
addListener(eventName, listener) {
this.on(eventName, listener);
}
on(eventName, listener) {
if (eventName === "close") {
this._internalee.on("close", listener);
return;
}
this._socket.on(eventName, listener);
}
emit(eventName, ...args) {
if (eventName === "close") {
return this._internalee.emit.apply(this._internalee, arguments);
}
return this._socket.emit.apply(this._socket, arguments);
}
listeners(eventName) {
return this._socket.listeners(eventName);
}
removeListener(eventName, listener) {
this._socket.removeListener(eventName, listener);
}
removeAllListeners(eventName) {
this._socket.removeAllListeners(eventName);
}
once(eventName, listener) {
this._socket.once(eventName, listener);
}
_close() {
this._socket.readable = false;
this.write = () => {
};
this.end = () => {
};
this._writable = false;
this._readable = false;
}
_parseEnv(env) {
const keys = Object.keys(env || {});
const pairs = [];
for (let i = 0; i < keys.length; i++) {
if (keys[i] === void 0) {
continue;
}
pairs.push(keys[i] + "=" + env[keys[i]]);
}
return pairs;
}
}
const FLUSH_DATA_INTERVAL$1 = 1e3;
class ConoutConnection {
constructor(_conoutPipeName) {
this._conoutPipeName = _conoutPipeName;
this._isDisposed = false;
this._onReady = new EventEmitter2();
const workerData = { conoutPipeName: _conoutPipeName };
const scriptPath = __dirname.replace("node_modules.asar", "node_modules.asar.unpacked");
this._worker = new Worker(join(scriptPath, "worker/conoutSocketWorker.js"), { workerData });
this._worker.on("message", (message) => {
switch (message) {
case ConoutWorkerMessage.READY:
this._onReady.fire();
return;
default:
console.warn("Unexpected ConoutWorkerMessage", message);
}
});
}
get onReady() {
return this._onReady.event;
}
dispose() {
if (this._isDisposed) {
return;
}
this._isDisposed = true;
this._drainDataAndClose();
}
connectSocket(socket) {
socket.connect(getWorkerPipeName(this._conoutPipeName));
}
_drainDataAndClose() {
if (this._drainTimeout) {
clearTimeout(this._drainTimeout);
}
this._drainTimeout = setTimeout(() => this._destroySocket(), FLUSH_DATA_INTERVAL$1);
}
async _destroySocket() {
await this._worker.terminate();
}
}
let conptyNative;
let winptyNative;
const FLUSH_DATA_INTERVAL = 1e3;
class WindowsPtyAgent {
constructor(file, args, env, cwd, cols, rows, debug, _useConpty, _useConptyDll = false, conptyInheritCursor = false) {
this._useConpty = _useConpty;
this._useConptyDll = _useConptyDll;
this._pid = 0;
this._innerPid = 0;
if (this._useConpty === void 0 || this._useConpty === true) {
this._useConpty = this._getWindowsBuildNumber() >= 18309;
}
if (this._useConpty) {
if (!conptyNative) {
try {
conptyNative = require("../build/Release/conpty.node");
} catch (outerError) {
try {
conptyNative = require("../build/Debug/conpty.node");
} catch (innerError) {
console.error("innerError", innerError);
throw outerError;
}
}
}
} else {
if (!winptyNative) {
try {
winptyNative = require("../build/Release/pty.node");
} catch (outerError) {
try {
winptyNative = require("../build/Debug/pty.node");
} catch (innerError) {
console.error("innerError", innerError);
throw outerError;
}
}
}
}
this._ptyNative = this._useConpty ? conptyNative : winptyNative;
cwd = path.resolve(cwd);
const commandLine = argsToCommandLine(file, args);
let term;
if (this._useConpty) {
term = this._ptyNative.startProcess(file, cols, rows, debug, this._generatePipeName(), conptyInheritCursor, this._useConptyDll);
} else {
term = this._ptyNative.startProcess(file, commandLine, env, cwd, cols, rows, debug);
this._pid = term.pid;
this._innerPid = term.innerPid;
}
this._fd = term.fd;
this._pty = term.pty;
this._outSocket = new Socket();
this._outSocket.setEncoding("utf8");
this._conoutSocketWorker = new ConoutConnection(term.conout);
this._conoutSocketWorker.onReady(() => {
this._conoutSocketWorker.connectSocket(this._outSocket);
});
this._outSocket.on("connect", () => {
this._outSocket.emit("ready_datapipe");
});
const inSocketFD = fs.openSync(term.conin, "w");
this._inSocket = new Socket({
fd: inSocketFD,
readable: false,
writable: true
});
this._inSocket.setEncoding("utf8");
if (this._useConpty) {
const connect = this._ptyNative.connect(this._pty, commandLine, cwd, env, (c) => this._$onProcessExit(c));
this._innerPid = connect.pid;
}
}
get inSocket() {
return this._inSocket;
}
get outSocket() {
return this._outSocket;
}
get fd() {
return this._fd;
}
get innerPid() {
return this._innerPid;
}
get pty() {
return this._pty;
}
resize(cols, rows) {
if (this._useConpty) {
if (this._exitCode !== void 0) {
throw new Error("Cannot resize a pty that has already exited");
}
this._ptyNative.resize(this._pty, cols, rows, this._useConptyDll);
return;
}
this._ptyNative.resize(this._pid, cols, rows);
}
clear() {
if (this._useConpty) {
this._ptyNative.clear(this._pty, this._useConptyDll);
}
}
kill() {
this._inSocket.readable = false;
this._outSocket.readable = false;
if (this._useConpty) {
this._getConsoleProcessList().then((consoleProcessList) => {
consoleProcessList.forEach((pid) => {
try {
process.kill(pid);
} catch (e) {
}
});
this._ptyNative.kill(this._pty, this._useConptyDll);
});
} else {
const processList = this._ptyNative.getProcessList(this._pid);
this._ptyNative.kill(this._pid, this._innerPid);
processList.forEach((pid) => {
try {
process.kill(pid);
} catch (e) {
}
});
}
this._conoutSocketWorker.dispose();
}
_getConsoleProcessList() {
return new Promise((resolve) => {
const agent = fork$1(path.join(__dirname, "conpty_console_list_agent"), [this._innerPid.toString()]);
agent.on("message", (message) => {
clearTimeout(timeout);
resolve(message.consoleProcessList);
});
const timeout = setTimeout(() => {
agent.kill();
resolve([this._innerPid]);
}, 5e3);
});
}
get exitCode() {
if (this._useConpty) {
return this._exitCode;
}
const winptyExitCode = this._ptyNative.getExitCode(this._innerPid);
return winptyExitCode === -1 ? void 0 : winptyExitCode;
}
_getWindowsBuildNumber() {
const osVersion = /(\d+)\.(\d+)\.(\d+)/g.exec(os.release());
let buildNumber = 0;
if (osVersion && osVersion.length === 4) {
buildNumber = parseInt(osVersion[3]);
}
return buildNumber;
}
_generatePipeName() {
return `conpty-${Math.random() * 1e7}`;
}
/**
* Triggered from the native side when a contpy process exits.
*/
_$onProcessExit(exitCode) {
this._exitCode = exitCode;
this._flushDataAndCleanUp();
this._outSocket.on("data", () => this._flushDataAndCleanUp());
}
_flushDataAndCleanUp() {
if (this._closeTimeout) {
clearTimeout(this._closeTimeout);
}
this._closeTimeout = setTimeout(() => this._cleanUpProcess(), FLUSH_DATA_INTERVAL);
}
_cleanUpProcess() {
this._inSocket.readable = false;
this._outSocket.readable = false;
this._outSocket.destroy();
}
}
function argsToCommandLine(file, args) {
if (isCommandLine(args)) {
if (args.length === 0) {
return file;
}
return `${argsToCommandLine(file, [])} ${args}`;
}
const argv = [file];
Array.prototype.push.apply(argv, args);
let result = "";
for (let argIndex = 0; argIndex < argv.length; argIndex++) {
if (argIndex > 0) {
result += " ";
}
const arg = argv[argIndex];
const hasLopsidedEnclosingQuote = xOr(arg[0] !== '"', arg[arg.length - 1] !== '"');
const hasNoEnclosingQuotes = arg[0] !== '"' && arg[arg.length - 1] !== '"';
const quote = arg === "" || (arg.indexOf(" ") !== -1 || arg.indexOf(" ") !== -1) && (arg.length > 1 && (hasLopsidedEnclosingQuote || hasNoEnclosingQuotes));
if (quote) {
result += '"';
}
let bsCount = 0;
for (let i = 0; i < arg.length; i++) {
const p = arg[i];
if (p === "\\") {
bsCount++;
} else if (p === '"') {
result += repeatText("\\", bsCount * 2 + 1);
result += '"';
bsCount = 0;
} else {
result += repeatText("\\", bsCount);
bsCount = 0;
result += p;
}
}
if (quote) {
result += repeatText("\\", bsCount * 2);
result += '"';
} else {
result += repeatText("\\", bsCount);
}
}
return result;
}
function isCommandLine(args) {
return typeof args === "string";
}
function repeatText(text, count) {
let result = "";
for (let i = 0; i < count; i++) {
result += text;
}
return result;
}
function xOr(arg1, arg2) {
return arg1 && !arg2 || !arg1 && arg2;
}
function assign(target, ...sources) {
sources.forEach((source) => Object.keys(source).forEach((key) => target[key] = source[key]));
return target;
}
const DEFAULT_FILE$1 = "cmd.exe";
const DEFAULT_NAME$1 = "Windows Shell";
class WindowsTerminal extends Terminal {
constructor(file, args, opt) {
super(opt);
this._checkType("args", args, "string", true);
args = args || [];
file = file || DEFAULT_FILE$1;
opt = opt || {};
opt.env = opt.env || process.env;
if (opt.encoding) {
console.warn("Setting encoding on Windows is not supported");
}
const env = assign({}, opt.env);
this._cols = opt.cols || DEFAULT_COLS;
this._rows = opt.rows || DEFAULT_ROWS;
const cwd = opt.cwd || process.cwd();
const name = opt.name || env.TERM || DEFAULT_NAME$1;
const parsedEnv = this._parseEnv(env);
this._isReady = false;
this._deferreds = [];
this._agent = new WindowsPtyAgent(file, args, parsedEnv, cwd, this._cols, this._rows, false, opt.useConpty, opt.useConptyDll, opt.conptyInheritCursor);
this._socket = this._agent.outSocket;
this._pid = this._agent.innerPid;
this._fd = this._agent.fd;
this._pty = this._agent.pty;
this._socket.on("ready_datapipe", () => {
["connect", "data", "end", "timeout", "drain"].forEach((event) => {
this._socket.on(event, () => {
if (!this._isReady && event === "data") {
this._isReady = true;
this._deferreds.forEach((fn) => {
fn.run();
});
this._deferreds = [];
}
});
});
this._socket.on("error", (err) => {
this._close();
if (err.code) {
if (~err.code.indexOf("errno 5") || ~err.code.indexOf("EIO")) return;
}
if (this.listeners("error").length < 2) {
throw err;
}
});
this._socket.on("close", () => {
this.emit("exit", this._agent.exitCode);
this._close();
});
});
this._file = file;
this._name = name;
this._readable = true;
this._writable = true;
this._forwardEvents();
}
_write(data) {
this._defer(this._doWrite, data);
}
_doWrite(data) {
this._agent.inSocket.write(data);
}
/**
* openpty
*/
static open(options) {
throw new Error("open() not supported on windows, use Fork() instead.");
}
/**
* TTY
*/
resize(cols, rows) {
if (cols <= 0 || rows <= 0 || isNaN(cols) || isNaN(rows) || cols === Infinity || rows === Infinity) {
throw new Error("resizing must be done using positive cols and rows");
}
this._deferNoArgs(() => {
this._agent.resize(cols, rows);
this._cols = cols;
this._rows = rows;
});
}
clear() {
this._deferNoArgs(() => {
this._agent.clear();
});
}
destroy() {
this._deferNoArgs(() => {
this.kill();
});
}
kill(signal) {
this._deferNoArgs(() => {
if (signal) {
throw new Error("Signals not supported on windows.");
}
this._close();
this._agent.kill();
});
}
_deferNoArgs(deferredFn) {
if (this._isReady) {
deferredFn.call(this);
return;
}
this._deferreds.push({
run: () => deferredFn.call(this)
});
}
_defer(deferredFn, arg) {
if (this._isReady) {
deferredFn.call(this, arg);
return;
}
this._deferreds.push({
run: () => deferredFn.call(this, arg)
});
}
get process() {
return this._name;
}
get master() {
throw new Error("master is not supported on Windows");
}
get slave() {
throw new Error("slave is not supported on Windows");
}
}
const windowsTerminal = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
__proto__: null,
WindowsTerminal
}, Symbol.toStringTag, { value: "Module" }));
const require$$0 = /* @__PURE__ */ getAugmentedNamespace(windowsTerminal);
function prebuildName() {
const tags = [];
tags.push(process.versions.hasOwnProperty("electron") ? "electron" : "node");
tags.push("abi" + process.versions.modules);
if (os.platform() === "linux" && fs.existsSync("/etc/alpine-release")) {
tags.push("musl");
}
return tags.join(".") + ".node";
}
const pathToBuild = path.resolve(__dirname, `../prebuilds/${os.platform()}-${os.arch()}/${prebuildName()}`);
const ptyPath = fs.existsSync(pathToBuild) ? pathToBuild : null;
let pty;
try {
pty = commonjsRequire(ptyPath || "../build/Release/pty.node");
} catch (outerError) {
try {
pty = commonjsRequire(ptyPath ? "../build/Release/pty.node" : "../build/Debug/pty.node");
} catch (innerError) {
console.error("innerError", innerError);
throw outerError;
}
}
const pty$1 = pty;
let helperPath;
helperPath = "../build/Release/spawn-helper";
helperPath = path.resolve(__dirname, helperPath);
helperPath = helperPath.replace("app.asar", "app.asar.unpacked");
helperPath = helperPath.replace("node_modules.asar", "node_modules.asar.unpacked");
const DEFAULT_FILE = "sh";
const DEFAULT_NAME = "xterm";
const DESTROY_SOCKET_TIMEOUT_MS = 200;
class UnixTerminal extends Terminal {
constructor(file, args, opt) {
super(opt);
this._boundClose = false;
this._emittedClose = false;
if (typeof args === "string") {
throw new Error("args as a string is not supported on unix.");
}
args = args || [];
file = file || DEFAULT_FILE;
opt = opt || {};
opt.env = opt.env || process.env;
this._cols = opt.cols || DEFAULT_COLS;
this._rows = opt.rows || DEFAULT_ROWS;
const uid = opt.uid ?? -1;
const gid = opt.gid ?? -1;
const env = assign({}, opt.env);
if (opt.env === process.env) {
this._sanitizeEnv(env);
}
const cwd = opt.cwd || process.cwd();
env.PWD = cwd;
const name = opt.name || env.TERM || DEFAULT_NAME;
env.TERM = name;
const parsedEnv = this._parseEnv(env);
const encoding = opt.encoding === void 0 ? "utf8" : opt.encoding;
const onexit = (code, signal) => {
if (!this._emittedClose) {
if (this._boundClose) {
return;
}
this._boundClose = true;
let timeout = setTimeout(() => {
timeout = null;
this._socket.destroy();
}, DESTROY_SOCKET_TIMEOUT_MS);
this.once("close", () => {
if (timeout !== null) {
clearTimeout(timeout);
}
this.emit("exit", code, signal);
});
return;
}
this.emit("exit", code, signal);
};
const term = pty$1.fork(file, args, parsedEnv, cwd, this._cols, this._rows, uid, gid, encoding === "utf8", helperPath, onexit);
this._socket = new tty.ReadStream(term.fd);
if (encoding !== null) {
this._socket.setEncoding(encoding);
}
this._socket.on("error", (err) => {
if (err.code) {
if (~err.code.indexOf("EAGAIN")) {
return;
}
}
this._close();
if (!this._emittedClose) {
this._emittedClose = true;
this.emit("close");
}
if (err.code) {
if (~err.code.indexOf("errno 5") || ~err.code.indexOf("EIO")) {
return;
}
}
if (this.listeners("error").length < 2) {
throw err;
}
});
this._pid = term.pid;
this._fd = term.fd;
this._pty = term.pty;
this._file = file;
this._name = name;
this._readable = true;
this._writable = true;
this._socket.on("close", () => {
if (this._emittedClose) {
return;
}
this._emittedClose = true;
this._close();
this.emit("close");
});
this._forwardEvents();
}
get master() {
return this._master;
}
get slave() {
return this._slave;
}
_write(data) {
this._socket.write(data);
}
/* Accessors */
get fd() {
return this._fd;
}
get ptsName() {
return this._pty;
}
/**
* openpty
*/
static open(opt) {
const self = Object.create(UnixTerminal.prototype);
opt = opt || {};
if (arguments.length > 1) {
opt = {
cols: arguments[1],
rows: arguments[2]
};
}
const cols = opt.cols || DEFAULT_COLS;
const rows = opt.rows || DEFAULT_ROWS;
const encoding = opt.encoding === void 0 ? "utf8" : opt.encoding;
const term = pty$1.open(cols, rows);
self._master = new tty.ReadStream(term.master);
if (encoding !== null) {
self._master.setEncoding(encoding);
}
self._master.resume();
self._slave = new tty.ReadStream(term.slave);
if (encoding !== null) {
self._slave.setEncoding(encoding);
}
self._slave.resume();
self._socket = self._master;
self._pid = -1;
self._fd = term.master;
self._pty = term.pty;
self._file = process.argv[0] || "node";
self._name = process.env.TERM || "";
self._readable = true;
self._writable = true;
self._socket.on("error", (err) => {
self._close();
if (self.listeners("error").length < 2) {
throw err;
}
});
self._socket.on("close", () => {
self._close();
});
return self;
}
destroy() {
this._close();
this._socket.once("close", () => {
this.kill("SIGHUP");
});
this._socket.destroy();
}
kill(signal) {
try {
process.kill(this.pid, signal || "SIGHUP");
} catch (e) {
}
}
/**
* Gets the name of the process.
*/
get process() {
if (process.platform === "darwin") {
const title = pty$1.process(this._fd);
return title !== "kernel_task" ? title : this._file;
}
return pty$1.process(this._fd, this._pty) || this._file;
}
/**
* TTY
*/
resize(cols, rows) {
if (cols <= 0 || rows <= 0 || isNaN(cols) || isNaN(rows) || cols === Infinity || rows === Infinity) {
throw new Error("resizing must be done using positive cols and rows");
}
pty$1.resize(this._fd, cols, rows);
this._cols = cols;
this._rows = rows;
}
clear() {
}
_sanitizeEnv(env) {
delete env["TMUX"];
delete env["TMUX_PANE"];
delete env["STY"];
delete env["WINDOW"];
delete env["WINDOWID"];
delete env["TERMCAP"];
delete env["COLUMNS"];
delete env["LINES"];
}
}
const unixTerminal = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
__proto__: null,
UnixTerminal
}, Symbol.toStringTag, { value: "Module" }));
const require$$1 = /* @__PURE__ */ getAugmentedNamespace(unixTerminal);
let terminalCtor;
if (process.platform === "win32") {
terminalCtor = require$$0.WindowsTerminal;
} else {
terminalCtor = require$$1.UnixTerminal;
}
function spawn(file, args, opt) {
return new terminalCtor(file, args, opt);
}
function fork(file, args, opt) {
return new terminalCtor(file, args, opt);
}
function createTerminal(file, args, opt) {
return new terminalCtor(file, args, opt);
}
function open(options) {
return terminalCtor.open(options);
}
const native = process.platform !== "win32" ? commonjsRequire(ptyPath || "../build/Release/pty.node") : null;
export {
createTerminal,
fork,
native,
open,
spawn
};