spawn-term 3.4.2 → 3.5.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/dist/cjs/lib/TerminalBuffer.js +50 -207
- package/dist/cjs/lib/TerminalBuffer.js.map +1 -1
- package/dist/cjs/session.js +3 -3
- package/dist/cjs/session.js.map +1 -1
- package/dist/cjs/src/lib/TerminalBuffer.d.ts +15 -2
- package/dist/esm/lib/TerminalBuffer.js +37 -144
- package/dist/esm/lib/TerminalBuffer.js.map +1 -1
- package/dist/esm/session.js +3 -3
- package/dist/esm/session.js.map +1 -1
- package/dist/esm/src/lib/TerminalBuffer.d.ts +15 -2
- package/package.json +3 -3
|
@@ -8,7 +8,15 @@ Object.defineProperty(exports, "TerminalBuffer", {
|
|
|
8
8
|
return TerminalBuffer;
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
|
-
var
|
|
11
|
+
var _terminalmodel = require("terminal-model");
|
|
12
|
+
function _array_like_to_array(arr, len) {
|
|
13
|
+
if (len == null || len > arr.length) len = arr.length;
|
|
14
|
+
for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
|
|
15
|
+
return arr2;
|
|
16
|
+
}
|
|
17
|
+
function _array_without_holes(arr) {
|
|
18
|
+
if (Array.isArray(arr)) return _array_like_to_array(arr);
|
|
19
|
+
}
|
|
12
20
|
function _class_call_check(instance, Constructor) {
|
|
13
21
|
if (!(instance instanceof Constructor)) {
|
|
14
22
|
throw new TypeError("Cannot call a class as a function");
|
|
@@ -28,152 +36,38 @@ function _create_class(Constructor, protoProps, staticProps) {
|
|
|
28
36
|
if (staticProps) _defineProperties(Constructor, staticProps);
|
|
29
37
|
return Constructor;
|
|
30
38
|
}
|
|
31
|
-
function
|
|
32
|
-
if (
|
|
33
|
-
Object.defineProperty(obj, key, {
|
|
34
|
-
value: value,
|
|
35
|
-
enumerable: true,
|
|
36
|
-
configurable: true,
|
|
37
|
-
writable: true
|
|
38
|
-
});
|
|
39
|
-
} else {
|
|
40
|
-
obj[key] = value;
|
|
41
|
-
}
|
|
42
|
-
return obj;
|
|
39
|
+
function _iterable_to_array(iter) {
|
|
40
|
+
if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
|
|
43
41
|
}
|
|
44
|
-
function
|
|
45
|
-
|
|
46
|
-
var cacheBabelInterop = new WeakMap();
|
|
47
|
-
var cacheNodeInterop = new WeakMap();
|
|
48
|
-
return (_getRequireWildcardCache = function(nodeInterop) {
|
|
49
|
-
return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
|
|
50
|
-
})(nodeInterop);
|
|
42
|
+
function _non_iterable_spread() {
|
|
43
|
+
throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
51
44
|
}
|
|
52
|
-
function
|
|
53
|
-
|
|
54
|
-
return obj;
|
|
55
|
-
}
|
|
56
|
-
if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
|
|
57
|
-
return {
|
|
58
|
-
default: obj
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
var cache = _getRequireWildcardCache(nodeInterop);
|
|
62
|
-
if (cache && cache.has(obj)) {
|
|
63
|
-
return cache.get(obj);
|
|
64
|
-
}
|
|
65
|
-
var newObj = {
|
|
66
|
-
__proto__: null
|
|
67
|
-
};
|
|
68
|
-
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
|
|
69
|
-
for(var key in obj){
|
|
70
|
-
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
71
|
-
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
|
|
72
|
-
if (desc && (desc.get || desc.set)) {
|
|
73
|
-
Object.defineProperty(newObj, key, desc);
|
|
74
|
-
} else {
|
|
75
|
-
newObj[key] = obj[key];
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
newObj.default = obj;
|
|
80
|
-
if (cache) {
|
|
81
|
-
cache.set(obj, newObj);
|
|
82
|
-
}
|
|
83
|
-
return newObj;
|
|
45
|
+
function _to_consumable_array(arr) {
|
|
46
|
+
return _array_without_holes(arr) || _iterable_to_array(arr) || _unsupported_iterable_to_array(arr) || _non_iterable_spread();
|
|
84
47
|
}
|
|
85
|
-
function
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}));
|
|
93
|
-
}
|
|
94
|
-
ownKeys.forEach(function(key) {
|
|
95
|
-
_define_property(target, key, source[key]);
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
return target;
|
|
99
|
-
}
|
|
100
|
-
var _xterm_default;
|
|
101
|
-
// Handle both ESM and CJS module formats
|
|
102
|
-
var Terminal = _headless.Terminal || ((_xterm_default = _headless.default) === null || _xterm_default === void 0 ? void 0 : _xterm_default.Terminal);
|
|
103
|
-
// ANSI color mode constants from xterm.js
|
|
104
|
-
var COLOR_MODE_DEFAULT = 0;
|
|
105
|
-
var COLOR_MODE_16 = 16777216; // 0x1000000 - 16 color palette (0-15)
|
|
106
|
-
var COLOR_MODE_256 = 33554432; // 0x2000000 - 256 color palette
|
|
107
|
-
var COLOR_MODE_RGB = 50331648; // 0x3000000 - 24-bit RGB
|
|
108
|
-
var DEFAULT_STYLE = {
|
|
109
|
-
fg: -1,
|
|
110
|
-
fgMode: COLOR_MODE_DEFAULT,
|
|
111
|
-
bg: -1,
|
|
112
|
-
bgMode: COLOR_MODE_DEFAULT,
|
|
113
|
-
bold: false,
|
|
114
|
-
dim: false,
|
|
115
|
-
italic: false,
|
|
116
|
-
underline: false,
|
|
117
|
-
inverse: false,
|
|
118
|
-
strikethrough: false
|
|
119
|
-
};
|
|
120
|
-
function styleEquals(a, b) {
|
|
121
|
-
return a.fg === b.fg && a.fgMode === b.fgMode && a.bg === b.bg && a.bgMode === b.bgMode && a.bold === b.bold && a.dim === b.dim && a.italic === b.italic && a.underline === b.underline && a.inverse === b.inverse && a.strikethrough === b.strikethrough;
|
|
122
|
-
}
|
|
123
|
-
function buildAnsiCode(style) {
|
|
124
|
-
var codes = [];
|
|
125
|
-
// Attributes
|
|
126
|
-
if (style.bold) codes.push(1);
|
|
127
|
-
if (style.dim) codes.push(2);
|
|
128
|
-
if (style.italic) codes.push(3);
|
|
129
|
-
if (style.underline) codes.push(4);
|
|
130
|
-
if (style.inverse) codes.push(7);
|
|
131
|
-
if (style.strikethrough) codes.push(9);
|
|
132
|
-
// Foreground color
|
|
133
|
-
if (style.fgMode === COLOR_MODE_16) {
|
|
134
|
-
// 16-color palette: 0-7 are 30-37, 8-15 are 90-97
|
|
135
|
-
if (style.fg < 8) {
|
|
136
|
-
codes.push(30 + style.fg);
|
|
137
|
-
} else {
|
|
138
|
-
codes.push(90 + (style.fg - 8));
|
|
139
|
-
}
|
|
140
|
-
} else if (style.fgMode === COLOR_MODE_256) {
|
|
141
|
-
codes.push(38, 5, style.fg);
|
|
142
|
-
} else if (style.fgMode === COLOR_MODE_RGB) {
|
|
143
|
-
// RGB is encoded in the color value
|
|
144
|
-
var r = style.fg >> 16 & 0xff;
|
|
145
|
-
var g = style.fg >> 8 & 0xff;
|
|
146
|
-
var b = style.fg & 0xff;
|
|
147
|
-
codes.push(38, 2, r, g, b);
|
|
148
|
-
}
|
|
149
|
-
// Background color
|
|
150
|
-
if (style.bgMode === COLOR_MODE_16) {
|
|
151
|
-
if (style.bg < 8) {
|
|
152
|
-
codes.push(40 + style.bg);
|
|
153
|
-
} else {
|
|
154
|
-
codes.push(100 + (style.bg - 8));
|
|
155
|
-
}
|
|
156
|
-
} else if (style.bgMode === COLOR_MODE_256) {
|
|
157
|
-
codes.push(48, 5, style.bg);
|
|
158
|
-
} else if (style.bgMode === COLOR_MODE_RGB) {
|
|
159
|
-
var r1 = style.bg >> 16 & 0xff;
|
|
160
|
-
var g1 = style.bg >> 8 & 0xff;
|
|
161
|
-
var b1 = style.bg & 0xff;
|
|
162
|
-
codes.push(48, 2, r1, g1, b1);
|
|
163
|
-
}
|
|
164
|
-
if (codes.length === 0) return '';
|
|
165
|
-
return "\x1b[".concat(codes.join(';'), "m");
|
|
48
|
+
function _unsupported_iterable_to_array(o, minLen) {
|
|
49
|
+
if (!o) return;
|
|
50
|
+
if (typeof o === "string") return _array_like_to_array(o, minLen);
|
|
51
|
+
var n = Object.prototype.toString.call(o).slice(8, -1);
|
|
52
|
+
if (n === "Object" && o.constructor) n = o.constructor.name;
|
|
53
|
+
if (n === "Map" || n === "Set") return Array.from(n);
|
|
54
|
+
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
|
|
166
55
|
}
|
|
167
56
|
var TerminalBuffer = /*#__PURE__*/ function() {
|
|
168
57
|
"use strict";
|
|
169
|
-
function TerminalBuffer(
|
|
170
|
-
var
|
|
58
|
+
function TerminalBuffer(_cols) {
|
|
59
|
+
var _this = this;
|
|
60
|
+
var _scrollback = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : 10000;
|
|
171
61
|
_class_call_check(this, TerminalBuffer);
|
|
172
|
-
this.
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
62
|
+
this.allLines = [];
|
|
63
|
+
// terminal-model doesn't enforce column width during parsing
|
|
64
|
+
// It preserves all content as-is
|
|
65
|
+
this.terminal = new _terminalmodel.StreamingTerminal();
|
|
66
|
+
// Listen for completed lines (when \n is encountered)
|
|
67
|
+
this.terminal.setLineReadyCallback(function() {
|
|
68
|
+
var line = _this.terminal.renderLine();
|
|
69
|
+
_this.terminal.reset();
|
|
70
|
+
_this.allLines.push(line);
|
|
177
71
|
});
|
|
178
72
|
}
|
|
179
73
|
var _proto = TerminalBuffer.prototype;
|
|
@@ -181,88 +75,37 @@ var TerminalBuffer = /*#__PURE__*/ function() {
|
|
|
181
75
|
* Write raw data to the terminal buffer.
|
|
182
76
|
* The terminal interprets all ANSI sequences automatically.
|
|
183
77
|
*/ _proto.write = function write(data) {
|
|
184
|
-
|
|
185
|
-
this.terminal.write(str);
|
|
78
|
+
this.terminal.write(data);
|
|
186
79
|
};
|
|
187
80
|
/**
|
|
188
81
|
* Resize the terminal width.
|
|
189
|
-
|
|
190
|
-
|
|
82
|
+
* terminal-model doesn't use column constraints, so this is a no-op for compatibility.
|
|
83
|
+
*/ _proto.resize = function resize(_cols) {
|
|
84
|
+
// No-op - terminal-model doesn't enforce column width
|
|
191
85
|
};
|
|
192
86
|
/**
|
|
193
87
|
* Extract the rendered lines from the terminal buffer.
|
|
194
88
|
* This returns the actual visible content after all ANSI sequences
|
|
195
89
|
* have been processed, with color codes preserved.
|
|
90
|
+
*
|
|
91
|
+
* CRITICAL: Unlike the xterm implementation, we do NOT call trimStart(),
|
|
92
|
+
* which preserves legitimate indentation and blank lines.
|
|
196
93
|
*/ _proto.getLines = function getLines() {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
var result = '';
|
|
203
|
-
var currentStyle = _object_spread({}, DEFAULT_STYLE);
|
|
204
|
-
var _hasContent = false;
|
|
205
|
-
// First pass: find the last non-empty cell to know where content ends
|
|
206
|
-
var lastContentIndex = -1;
|
|
207
|
-
for(var j = bufferLine.length - 1; j >= 0; j--){
|
|
208
|
-
var cell = bufferLine.getCell(j);
|
|
209
|
-
if (cell && cell.getChars()) {
|
|
210
|
-
lastContentIndex = j;
|
|
211
|
-
break;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
// Second pass: build the line with ANSI codes
|
|
215
|
-
for(var j1 = 0; j1 <= lastContentIndex; j1++){
|
|
216
|
-
var cell1 = bufferLine.getCell(j1);
|
|
217
|
-
if (!cell1) continue;
|
|
218
|
-
var char = cell1.getChars();
|
|
219
|
-
var cellStyle = {
|
|
220
|
-
fg: cell1.getFgColor(),
|
|
221
|
-
fgMode: cell1.getFgColorMode(),
|
|
222
|
-
bg: cell1.getBgColor(),
|
|
223
|
-
bgMode: cell1.getBgColorMode(),
|
|
224
|
-
bold: cell1.isBold() !== 0,
|
|
225
|
-
dim: cell1.isDim() !== 0,
|
|
226
|
-
italic: cell1.isItalic() !== 0,
|
|
227
|
-
underline: cell1.isUnderline() !== 0,
|
|
228
|
-
inverse: cell1.isInverse() !== 0,
|
|
229
|
-
strikethrough: cell1.isStrikethrough() !== 0
|
|
230
|
-
};
|
|
231
|
-
// Check if style changed
|
|
232
|
-
if (!styleEquals(cellStyle, currentStyle)) {
|
|
233
|
-
// Reset if going back to default, otherwise emit new style
|
|
234
|
-
if (styleEquals(cellStyle, DEFAULT_STYLE)) {
|
|
235
|
-
result += '\x1b[0m';
|
|
236
|
-
} else {
|
|
237
|
-
// If we had styling before, reset first for clean transition
|
|
238
|
-
if (!styleEquals(currentStyle, DEFAULT_STYLE)) {
|
|
239
|
-
result += '\x1b[0m';
|
|
240
|
-
}
|
|
241
|
-
result += buildAnsiCode(cellStyle);
|
|
242
|
-
}
|
|
243
|
-
currentStyle = cellStyle;
|
|
244
|
-
}
|
|
245
|
-
result += char || ' ';
|
|
246
|
-
if (char) _hasContent = true;
|
|
247
|
-
}
|
|
248
|
-
// Reset at end of line if we had styling
|
|
249
|
-
if (!styleEquals(currentStyle, DEFAULT_STYLE)) {
|
|
250
|
-
result += '\x1b[0m';
|
|
251
|
-
}
|
|
252
|
-
// Trim leading whitespace - tools like ncu/npm use cursor positioning
|
|
253
|
-
// which creates lines with leading spaces when interpreted by xterm
|
|
254
|
-
lines.push(result.trimStart());
|
|
255
|
-
}
|
|
256
|
-
// Trim trailing empty lines
|
|
257
|
-
while(lines.length > 0 && lines[lines.length - 1] === ''){
|
|
258
|
-
lines.pop();
|
|
94
|
+
// Flush any pending content (incomplete line without \n)
|
|
95
|
+
if (this.terminal.hasContent()) {
|
|
96
|
+
var line = this.terminal.renderLine();
|
|
97
|
+
this.terminal.reset();
|
|
98
|
+
this.allLines.push(line);
|
|
259
99
|
}
|
|
260
|
-
|
|
100
|
+
// Return copy of all lines WITHOUT trimStart() or trimming blank lines
|
|
101
|
+
// Preserves all whitespace, indentation, and blank lines
|
|
102
|
+
return _to_consumable_array(this.allLines);
|
|
261
103
|
};
|
|
262
104
|
/**
|
|
263
105
|
* Clean up terminal resources.
|
|
264
106
|
*/ _proto.dispose = function dispose() {
|
|
265
107
|
this.terminal.dispose();
|
|
108
|
+
this.allLines = [];
|
|
266
109
|
};
|
|
267
110
|
_create_class(TerminalBuffer, [
|
|
268
111
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/lib/TerminalBuffer.ts"],"sourcesContent":["import * as xterm from '@xterm/headless';\n\n// Handle both ESM and CJS module formats\nconst Terminal = (xterm as { Terminal: typeof xterm.Terminal; default?: { Terminal: typeof xterm.Terminal } }).Terminal || (xterm as { default?: { Terminal: typeof xterm.Terminal } }).default?.Terminal;\n\n// ANSI color mode constants from xterm.js\nconst COLOR_MODE_DEFAULT = 0;\nconst COLOR_MODE_16 = 16777216; // 0x1000000 - 16 color palette (0-15)\nconst COLOR_MODE_256 = 33554432; // 0x2000000 - 256 color palette\nconst COLOR_MODE_RGB = 50331648; // 0x3000000 - 24-bit RGB\n\n/**\n * Wrapper around @xterm/headless Terminal that provides a virtual terminal buffer.\n * Interprets ANSI escape sequences (cursor movement, line clearing, etc.) to produce\n * the actual rendered output rather than raw intermediate states.\n */\n// Cell attribute state for tracking changes\ninterface CellStyle {\n fg: number;\n fgMode: number;\n bg: number;\n bgMode: number;\n bold: boolean;\n dim: boolean;\n italic: boolean;\n underline: boolean;\n inverse: boolean;\n strikethrough: boolean;\n}\n\nconst DEFAULT_STYLE: CellStyle = {\n fg: -1,\n fgMode: COLOR_MODE_DEFAULT,\n bg: -1,\n bgMode: COLOR_MODE_DEFAULT,\n bold: false,\n dim: false,\n italic: false,\n underline: false,\n inverse: false,\n strikethrough: false,\n};\n\nfunction styleEquals(a: CellStyle, b: CellStyle): boolean {\n return a.fg === b.fg && a.fgMode === b.fgMode && a.bg === b.bg && a.bgMode === b.bgMode && a.bold === b.bold && a.dim === b.dim && a.italic === b.italic && a.underline === b.underline && a.inverse === b.inverse && a.strikethrough === b.strikethrough;\n}\n\nfunction buildAnsiCode(style: CellStyle): string {\n const codes: number[] = [];\n\n // Attributes\n if (style.bold) codes.push(1);\n if (style.dim) codes.push(2);\n if (style.italic) codes.push(3);\n if (style.underline) codes.push(4);\n if (style.inverse) codes.push(7);\n if (style.strikethrough) codes.push(9);\n\n // Foreground color\n if (style.fgMode === COLOR_MODE_16) {\n // 16-color palette: 0-7 are 30-37, 8-15 are 90-97\n if (style.fg < 8) {\n codes.push(30 + style.fg);\n } else {\n codes.push(90 + (style.fg - 8));\n }\n } else if (style.fgMode === COLOR_MODE_256) {\n codes.push(38, 5, style.fg);\n } else if (style.fgMode === COLOR_MODE_RGB) {\n // RGB is encoded in the color value\n const r = (style.fg >> 16) & 0xff;\n const g = (style.fg >> 8) & 0xff;\n const b = style.fg & 0xff;\n codes.push(38, 2, r, g, b);\n }\n\n // Background color\n if (style.bgMode === COLOR_MODE_16) {\n if (style.bg < 8) {\n codes.push(40 + style.bg);\n } else {\n codes.push(100 + (style.bg - 8));\n }\n } else if (style.bgMode === COLOR_MODE_256) {\n codes.push(48, 5, style.bg);\n } else if (style.bgMode === COLOR_MODE_RGB) {\n const r = (style.bg >> 16) & 0xff;\n const g = (style.bg >> 8) & 0xff;\n const b = style.bg & 0xff;\n codes.push(48, 2, r, g, b);\n }\n\n if (codes.length === 0) return '';\n return `\\x1b[${codes.join(';')}m`;\n}\n\nexport class TerminalBuffer {\n private terminal: InstanceType<typeof Terminal>;\n\n constructor(cols: number, scrollback = 10000) {\n this.terminal = new Terminal({\n cols,\n rows: 50, // Visible rows (doesn't matter much for headless)\n scrollback,\n allowProposedApi: true,\n });\n }\n\n /**\n * Write raw data to the terminal buffer.\n * The terminal interprets all ANSI sequences automatically.\n */\n write(data: string | Buffer): void {\n const str = typeof data === 'string' ? data : data.toString('utf8');\n this.terminal.write(str);\n }\n\n /**\n * Resize the terminal width.\n */\n resize(cols: number): void {\n this.terminal.resize(cols, this.terminal.rows);\n }\n\n /**\n * Extract the rendered lines from the terminal buffer.\n * This returns the actual visible content after all ANSI sequences\n * have been processed, with color codes preserved.\n */\n getLines(): string[] {\n const buffer = this.terminal.buffer.active;\n const lines: string[] = [];\n\n for (let i = 0; i < buffer.length; i++) {\n const bufferLine = buffer.getLine(i);\n if (!bufferLine) continue;\n\n let result = '';\n let currentStyle: CellStyle = { ...DEFAULT_STYLE };\n let _hasContent = false;\n\n // First pass: find the last non-empty cell to know where content ends\n let lastContentIndex = -1;\n for (let j = bufferLine.length - 1; j >= 0; j--) {\n const cell = bufferLine.getCell(j);\n if (cell && cell.getChars()) {\n lastContentIndex = j;\n break;\n }\n }\n\n // Second pass: build the line with ANSI codes\n for (let j = 0; j <= lastContentIndex; j++) {\n const cell = bufferLine.getCell(j);\n if (!cell) continue;\n\n const char = cell.getChars();\n const cellStyle: CellStyle = {\n fg: cell.getFgColor(),\n fgMode: cell.getFgColorMode(),\n bg: cell.getBgColor(),\n bgMode: cell.getBgColorMode(),\n bold: cell.isBold() !== 0,\n dim: cell.isDim() !== 0,\n italic: cell.isItalic() !== 0,\n underline: cell.isUnderline() !== 0,\n inverse: cell.isInverse() !== 0,\n strikethrough: cell.isStrikethrough() !== 0,\n };\n\n // Check if style changed\n if (!styleEquals(cellStyle, currentStyle)) {\n // Reset if going back to default, otherwise emit new style\n if (styleEquals(cellStyle, DEFAULT_STYLE)) {\n result += '\\x1b[0m';\n } else {\n // If we had styling before, reset first for clean transition\n if (!styleEquals(currentStyle, DEFAULT_STYLE)) {\n result += '\\x1b[0m';\n }\n result += buildAnsiCode(cellStyle);\n }\n currentStyle = cellStyle;\n }\n\n result += char || ' ';\n if (char) _hasContent = true;\n }\n\n // Reset at end of line if we had styling\n if (!styleEquals(currentStyle, DEFAULT_STYLE)) {\n result += '\\x1b[0m';\n }\n\n // Trim leading whitespace - tools like ncu/npm use cursor positioning\n // which creates lines with leading spaces when interpreted by xterm\n lines.push(result.trimStart());\n }\n\n // Trim trailing empty lines\n while (lines.length > 0 && lines[lines.length - 1] === '') {\n lines.pop();\n }\n\n return lines;\n }\n\n /**\n * Get the number of rendered lines.\n */\n get lineCount(): number {\n return this.getLines().length;\n }\n\n /**\n * Clean up terminal resources.\n */\n dispose(): void {\n this.terminal.dispose();\n }\n}\n"],"names":["TerminalBuffer","Terminal","xterm","default","COLOR_MODE_DEFAULT","COLOR_MODE_16","COLOR_MODE_256","COLOR_MODE_RGB","DEFAULT_STYLE","fg","fgMode","bg","bgMode","bold","dim","italic","underline","inverse","strikethrough","styleEquals","a","b","buildAnsiCode","style","codes","push","r","g","length","join","cols","scrollback","terminal","rows","allowProposedApi","write","data","str","toString","resize","getLines","buffer","active","lines","i","bufferLine","getLine","result","currentStyle","_hasContent","lastContentIndex","j","cell","getCell","getChars","char","cellStyle","getFgColor","getFgColorMode","getBgColor","getBgColorMode","isBold","isDim","isItalic","isUnderline","isInverse","isStrikethrough","trimStart","pop","dispose","lineCount"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/lib/TerminalBuffer.ts"],"sourcesContent":["import { StreamingTerminal } from 'terminal-model';\n\n/**\n * Wrapper around terminal-model's StreamingTerminal that provides a virtual terminal buffer.\n * Interprets ANSI escape sequences (cursor movement, line clearing, etc.) to produce\n * the actual rendered output rather than raw intermediate states.\n *\n * This implementation preserves whitespace and blank lines by NOT calling trimStart(),\n * which was the bug in the previous xterm-based implementation.\n */\nexport class TerminalBuffer {\n private terminal: StreamingTerminal;\n private allLines: string[] = [];\n\n constructor(_cols: number, _scrollback = 10000) {\n // terminal-model doesn't enforce column width during parsing\n // It preserves all content as-is\n this.terminal = new StreamingTerminal();\n\n // Listen for completed lines (when \\n is encountered)\n this.terminal.setLineReadyCallback(() => {\n const line = this.terminal.renderLine();\n this.terminal.reset();\n this.allLines.push(line);\n });\n }\n\n /**\n * Write raw data to the terminal buffer.\n * The terminal interprets all ANSI sequences automatically.\n */\n write(data: string | Buffer): void {\n this.terminal.write(data);\n }\n\n /**\n * Resize the terminal width.\n * terminal-model doesn't use column constraints, so this is a no-op for compatibility.\n */\n resize(_cols: number): void {\n // No-op - terminal-model doesn't enforce column width\n }\n\n /**\n * Extract the rendered lines from the terminal buffer.\n * This returns the actual visible content after all ANSI sequences\n * have been processed, with color codes preserved.\n *\n * CRITICAL: Unlike the xterm implementation, we do NOT call trimStart(),\n * which preserves legitimate indentation and blank lines.\n */\n getLines(): string[] {\n // Flush any pending content (incomplete line without \\n)\n if (this.terminal.hasContent()) {\n const line = this.terminal.renderLine();\n this.terminal.reset();\n this.allLines.push(line);\n }\n\n // Return copy of all lines WITHOUT trimStart() or trimming blank lines\n // Preserves all whitespace, indentation, and blank lines\n return [...this.allLines];\n }\n\n /**\n * Get the number of rendered lines.\n */\n get lineCount(): number {\n return this.getLines().length;\n }\n\n /**\n * Clean up terminal resources.\n */\n dispose(): void {\n this.terminal.dispose();\n this.allLines = [];\n }\n}\n"],"names":["TerminalBuffer","_cols","_scrollback","allLines","terminal","StreamingTerminal","setLineReadyCallback","line","renderLine","reset","push","write","data","resize","getLines","hasContent","dispose","lineCount","length"],"mappings":";;;;+BAUaA;;;eAAAA;;;6BAVqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAU3B,IAAA,AAAMA,+BAAN;;aAAMA,eAICC,KAAa;;YAAEC,cAAAA,iEAAc;gCAJ9BF;aAEHG,WAAqB,EAAE;QAG7B,6DAA6D;QAC7D,iCAAiC;QACjC,IAAI,CAACC,QAAQ,GAAG,IAAIC,gCAAiB;QAErC,sDAAsD;QACtD,IAAI,CAACD,QAAQ,CAACE,oBAAoB,CAAC;YACjC,IAAMC,OAAO,MAAKH,QAAQ,CAACI,UAAU;YACrC,MAAKJ,QAAQ,CAACK,KAAK;YACnB,MAAKN,QAAQ,CAACO,IAAI,CAACH;QACrB;;iBAdSP;IAiBX;;;GAGC,GACDW,OAAAA,KAEC,GAFDA,SAAAA,MAAMC,IAAqB;QACzB,IAAI,CAACR,QAAQ,CAACO,KAAK,CAACC;IACtB;IAEA;;;GAGC,GACDC,OAAAA,MAEC,GAFDA,SAAAA,OAAOZ,KAAa;IAClB,sDAAsD;IACxD;IAEA;;;;;;;GAOC,GACDa,OAAAA,QAWC,GAXDA,SAAAA;QACE,yDAAyD;QACzD,IAAI,IAAI,CAACV,QAAQ,CAACW,UAAU,IAAI;YAC9B,IAAMR,OAAO,IAAI,CAACH,QAAQ,CAACI,UAAU;YACrC,IAAI,CAACJ,QAAQ,CAACK,KAAK;YACnB,IAAI,CAACN,QAAQ,CAACO,IAAI,CAACH;QACrB;QAEA,uEAAuE;QACvE,yDAAyD;QACzD,OAAQ,qBAAG,IAAI,CAACJ,QAAQ;IAC1B;IASA;;GAEC,GACDa,OAAAA,OAGC,GAHDA,SAAAA;QACE,IAAI,CAACZ,QAAQ,CAACY,OAAO;QACrB,IAAI,CAACb,QAAQ,GAAG,EAAE;IACpB;kBAnEWH;;YAyDPiB,KAAAA;iBAAJ,AAHA;;GAEC,GACD;gBACE,OAAO,IAAI,CAACH,QAAQ,GAAGI,MAAM;YAC/B;;;WA3DWlB"}
|
package/dist/cjs/session.js
CHANGED
|
@@ -180,9 +180,9 @@ var SessionImpl = /*#__PURE__*/ function() {
|
|
|
180
180
|
}), {
|
|
181
181
|
maxFps: _constantsts.DEFAULT_MAX_FPS
|
|
182
182
|
});
|
|
183
|
-
// Interactive mode requires a TTY
|
|
184
|
-
//
|
|
185
|
-
this.isInteractive =
|
|
183
|
+
// Interactive mode requires a real TTY for user input (e.g., press 'q' to quit)
|
|
184
|
+
// Without a TTY, there's no way to receive keyboard input, so auto-exit when complete
|
|
185
|
+
this.isInteractive = process.stdout.isTTY ? (_options_interactive = options.interactive) !== null && _options_interactive !== void 0 ? _options_interactive : false : false;
|
|
186
186
|
}
|
|
187
187
|
var _proto = SessionImpl.prototype;
|
|
188
188
|
_proto.spawn = function spawn(command, args, spawnOptions, options, callback) {
|
package/dist/cjs/session.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/session.tsx"],"sourcesContent":["import spawn, { crossSpawn, type SpawnResult } from 'cross-spawn-cb';\nimport crypto from 'crypto';\nimport { render } from 'ink';\nimport oo from 'on-one';\nimport Queue from 'queue-cb';\n\nimport App from './components/App.ts';\nimport { DEFAULT_MAX_FPS } from './constants.ts';\nimport concatWritable from './lib/concatWritable.ts';\nimport formatArguments from './lib/formatArguments.ts';\nimport { TerminalBuffer } from './lib/TerminalBuffer.ts';\nimport { ProcessStore } from './state/processStore.ts';\nimport type { ProcessOptions, SessionOptions, SpawnError, SpawnOptions, TerminalCallback } from './types.ts';\n\nexport interface Session {\n spawn(command: string, args: string[], spawnOptions: SpawnOptions, options: ProcessOptions, callback: TerminalCallback): void;\n close(): void;\n waitAndClose(callback?: () => void): void;\n}\n\nclass SessionImpl implements Session {\n private store: ProcessStore;\n private inkApp: ReturnType<typeof render> | null = null;\n private runningCount = 0;\n private closed = false;\n private waitCallbacks: (() => void)[] = [];\n private isInteractive: boolean;\n private terminalWidth: number;\n\n constructor(options: SessionOptions = {}) {\n this.store = new ProcessStore(options);\n // Use a very wide buffer to prevent line wrapping in xterm\n // Actual display truncation is handled by Ink components\n this.terminalWidth = 10000;\n\n // Only render Ink when stdout is a real terminal\n // When piped (e.g., nested spawn-term), skip Ink to avoid cursor positioning artifacts\n this.inkApp = render(<App store={this.store} />, { maxFps: DEFAULT_MAX_FPS });\n\n // Interactive mode requires a TTY to capture user input (e.g., press 'q' to quit)\n // Force non-interactive when no UI is rendered, otherwise waitAndClose would hang\n this.isInteractive = this.inkApp ? (options.interactive ?? false) : false;\n }\n\n spawn(command: string, args: string[], spawnOptions: SpawnOptions, options: ProcessOptions, callback: TerminalCallback): void {\n if (this.closed) {\n throw new Error('Session is closed');\n }\n\n const { encoding, stdio, ...csOptions } = spawnOptions;\n\n if (stdio === 'inherit') {\n // When Ink is not rendering (stdout not a TTY), pass output directly to stdout\n if (!this.inkApp) {\n const cp = crossSpawn(command, args, { ...csOptions, stdio: 'inherit' });\n spawn.worker(cp, csOptions, (err?: SpawnError) => {\n const res = (err ? err : {}) as SpawnResult;\n res.stdout = null;\n res.stderr = null;\n res.output = [null, null, null];\n err ? callback(err) : callback(null, res);\n });\n return;\n }\n\n this.runningCount++;\n const id = crypto.randomUUID();\n\n // Create terminal buffer for ANSI sequence interpretation\n const terminalBuffer = new TerminalBuffer(this.terminalWidth);\n\n this.store.addProcess({\n id,\n title: [command].concat(formatArguments(args)).join(' '),\n state: 'running',\n lines: [],\n terminalBuffer,\n group: options.group,\n expanded: options.expanded,\n });\n\n const cp = crossSpawn(command, args, csOptions);\n\n // Pipe stdout and stderr directly to terminal buffer\n // Both streams go to the same buffer to maintain correct ordering\n if (cp.stdout) {\n cp.stdout.on('data', (chunk: Buffer) => {\n terminalBuffer.write(chunk);\n this.store.notify();\n });\n }\n if (cp.stderr) {\n cp.stderr.on('data', (chunk: Buffer) => {\n terminalBuffer.write(chunk);\n this.store.notify();\n });\n }\n\n // Wait for process to complete\n const queue = new Queue();\n if (cp.stdout) {\n queue.defer(oo.bind(null, cp.stdout, ['error', 'end', 'close']));\n }\n if (cp.stderr) {\n queue.defer(oo.bind(null, cp.stderr, ['error', 'end', 'close']));\n }\n queue.defer(spawn.worker.bind(null, cp, csOptions));\n queue.await((err?: SpawnError) => {\n const res = (err ? err : {}) as SpawnResult;\n res.stdout = null; // Not collecting raw output in inherit mode\n res.stderr = null;\n res.output = [null, null, null];\n this.store.updateProcess(id, { state: err ? 'error' : 'success' });\n\n this.onProcessComplete();\n err ? callback(err) : callback(null, res);\n });\n } else {\n // Non-inherit mode: collect output but don't display in UI\n const cp = crossSpawn(command, args, csOptions);\n const outputs = { stdout: null as ReturnType<typeof concatWritable> | null, stderr: null as ReturnType<typeof concatWritable> | null };\n\n const queue = new Queue();\n if (cp.stdout) {\n outputs.stdout = concatWritable((output) => {\n (outputs.stdout as unknown as { output: string }).output = output.toString(encoding || 'utf8');\n });\n queue.defer(oo.bind(null, cp.stdout.pipe(outputs.stdout), ['error', 'end', 'close', 'finish']));\n }\n if (cp.stderr) {\n outputs.stderr = concatWritable((output) => {\n (outputs.stderr as unknown as { output: string }).output = output.toString(encoding || 'utf8');\n });\n queue.defer(oo.bind(null, cp.stderr.pipe(outputs.stderr), ['error', 'end', 'close', 'finish']));\n }\n queue.defer(spawn.worker.bind(null, cp, csOptions));\n queue.await((err?: SpawnError) => {\n const res = (err ? err : {}) as SpawnResult;\n res.stdout = outputs.stdout ? (outputs.stdout as unknown as { output: string }).output : null;\n res.stderr = outputs.stderr ? (outputs.stderr as unknown as { output: string }).output : null;\n res.output = [res.stdout, res.stderr, null];\n err ? callback(err) : callback(null, res);\n });\n }\n }\n\n close(): void {\n if (this.closed) return;\n this.closed = true;\n this.cleanup();\n }\n\n waitAndClose(callback?: () => void): void {\n if (this.closed) {\n callback?.();\n return;\n }\n\n if (callback) this.waitCallbacks.push(callback);\n\n if (this.runningCount === 0) {\n if (this.isInteractive) {\n // In interactive mode, wait for user to quit (press 'q')\n const unsubscribe = this.store.subscribe(() => {\n if (this.store.getShouldExit()) {\n unsubscribe();\n this.closeAndCallWaitCallbacks();\n }\n });\n } else {\n this.closeAndCallWaitCallbacks();\n }\n }\n // If runningCount > 0, will close when it hits 0 in onProcessComplete\n }\n\n private onProcessComplete(): void {\n this.runningCount--;\n if (this.runningCount === 0 && this.waitCallbacks.length > 0) {\n if (this.isInteractive) {\n // In interactive mode, wait for user to quit (press 'q')\n const unsubscribe = this.store.subscribe(() => {\n if (this.store.getShouldExit()) {\n unsubscribe();\n this.closeAndCallWaitCallbacks();\n }\n });\n } else {\n this.closeAndCallWaitCallbacks();\n }\n }\n }\n\n private closeAndCallWaitCallbacks(): void {\n if (this.closed) return;\n this.closed = true;\n this.cleanup(() => {\n for (const cb of this.waitCallbacks) cb();\n this.waitCallbacks = [];\n });\n }\n\n private cleanup(onComplete?: () => void): void {\n // Signal exit to React component\n this.store.signalExit(() => {\n this.store.reset();\n process.stdout.write('\\x1b[?25h'); // show cursor\n });\n\n // Wait for Ink to finish\n if (this.inkApp) {\n this.inkApp\n .waitUntilExit()\n .then(() => {\n const cb = this.store.getExitCallback();\n cb?.();\n onComplete?.();\n })\n .catch(() => {\n const cb = this.store.getExitCallback();\n cb?.();\n onComplete?.();\n });\n this.inkApp = null;\n } else {\n onComplete?.();\n }\n }\n}\n\nexport function createSession(options: SessionOptions = {}): Session {\n return new SessionImpl(options);\n}\n"],"names":["createSession","SessionImpl","options","inkApp","runningCount","closed","waitCallbacks","store","ProcessStore","terminalWidth","render","App","maxFps","DEFAULT_MAX_FPS","isInteractive","interactive","spawn","command","args","spawnOptions","callback","Error","encoding","stdio","csOptions","cp","crossSpawn","worker","err","res","stdout","stderr","output","id","crypto","randomUUID","terminalBuffer","TerminalBuffer","addProcess","title","concat","formatArguments","join","state","lines","group","expanded","on","chunk","write","notify","queue","Queue","defer","oo","bind","await","updateProcess","onProcessComplete","outputs","concatWritable","toString","pipe","close","cleanup","waitAndClose","push","unsubscribe","subscribe","getShouldExit","closeAndCallWaitCallbacks","length","cb","onComplete","signalExit","reset","process","waitUntilExit","then","getExitCallback","catch"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/session.tsx"],"sourcesContent":["import spawn, { crossSpawn, type SpawnResult } from 'cross-spawn-cb';\nimport crypto from 'crypto';\nimport { render } from 'ink';\nimport oo from 'on-one';\nimport Queue from 'queue-cb';\n\nimport App from './components/App.ts';\nimport { DEFAULT_MAX_FPS } from './constants.ts';\nimport concatWritable from './lib/concatWritable.ts';\nimport formatArguments from './lib/formatArguments.ts';\nimport { TerminalBuffer } from './lib/TerminalBuffer.ts';\nimport { ProcessStore } from './state/processStore.ts';\nimport type { ProcessOptions, SessionOptions, SpawnError, SpawnOptions, TerminalCallback } from './types.ts';\n\nexport interface Session {\n spawn(command: string, args: string[], spawnOptions: SpawnOptions, options: ProcessOptions, callback: TerminalCallback): void;\n close(): void;\n waitAndClose(callback?: () => void): void;\n}\n\nclass SessionImpl implements Session {\n private store: ProcessStore;\n private inkApp: ReturnType<typeof render> | null = null;\n private runningCount = 0;\n private closed = false;\n private waitCallbacks: (() => void)[] = [];\n private isInteractive: boolean;\n private terminalWidth: number;\n\n constructor(options: SessionOptions = {}) {\n this.store = new ProcessStore(options);\n // Use a very wide buffer to prevent line wrapping in xterm\n // Actual display truncation is handled by Ink components\n this.terminalWidth = 10000;\n\n // Only render Ink when stdout is a real terminal\n // When piped (e.g., nested spawn-term), skip Ink to avoid cursor positioning artifacts\n this.inkApp = render(<App store={this.store} />, { maxFps: DEFAULT_MAX_FPS });\n\n // Interactive mode requires a real TTY for user input (e.g., press 'q' to quit)\n // Without a TTY, there's no way to receive keyboard input, so auto-exit when complete\n this.isInteractive = process.stdout.isTTY ? (options.interactive ?? false) : false;\n }\n\n spawn(command: string, args: string[], spawnOptions: SpawnOptions, options: ProcessOptions, callback: TerminalCallback): void {\n if (this.closed) {\n throw new Error('Session is closed');\n }\n\n const { encoding, stdio, ...csOptions } = spawnOptions;\n\n if (stdio === 'inherit') {\n // When Ink is not rendering (stdout not a TTY), pass output directly to stdout\n if (!this.inkApp) {\n const cp = crossSpawn(command, args, { ...csOptions, stdio: 'inherit' });\n spawn.worker(cp, csOptions, (err?: SpawnError) => {\n const res = (err ? err : {}) as SpawnResult;\n res.stdout = null;\n res.stderr = null;\n res.output = [null, null, null];\n err ? callback(err) : callback(null, res);\n });\n return;\n }\n\n this.runningCount++;\n const id = crypto.randomUUID();\n\n // Create terminal buffer for ANSI sequence interpretation\n const terminalBuffer = new TerminalBuffer(this.terminalWidth);\n\n this.store.addProcess({\n id,\n title: [command].concat(formatArguments(args)).join(' '),\n state: 'running',\n lines: [],\n terminalBuffer,\n group: options.group,\n expanded: options.expanded,\n });\n\n const cp = crossSpawn(command, args, csOptions);\n\n // Pipe stdout and stderr directly to terminal buffer\n // Both streams go to the same buffer to maintain correct ordering\n if (cp.stdout) {\n cp.stdout.on('data', (chunk: Buffer) => {\n terminalBuffer.write(chunk);\n this.store.notify();\n });\n }\n if (cp.stderr) {\n cp.stderr.on('data', (chunk: Buffer) => {\n terminalBuffer.write(chunk);\n this.store.notify();\n });\n }\n\n // Wait for process to complete\n const queue = new Queue();\n if (cp.stdout) {\n queue.defer(oo.bind(null, cp.stdout, ['error', 'end', 'close']));\n }\n if (cp.stderr) {\n queue.defer(oo.bind(null, cp.stderr, ['error', 'end', 'close']));\n }\n queue.defer(spawn.worker.bind(null, cp, csOptions));\n queue.await((err?: SpawnError) => {\n const res = (err ? err : {}) as SpawnResult;\n res.stdout = null; // Not collecting raw output in inherit mode\n res.stderr = null;\n res.output = [null, null, null];\n this.store.updateProcess(id, { state: err ? 'error' : 'success' });\n\n this.onProcessComplete();\n err ? callback(err) : callback(null, res);\n });\n } else {\n // Non-inherit mode: collect output but don't display in UI\n const cp = crossSpawn(command, args, csOptions);\n const outputs = { stdout: null as ReturnType<typeof concatWritable> | null, stderr: null as ReturnType<typeof concatWritable> | null };\n\n const queue = new Queue();\n if (cp.stdout) {\n outputs.stdout = concatWritable((output) => {\n (outputs.stdout as unknown as { output: string }).output = output.toString(encoding || 'utf8');\n });\n queue.defer(oo.bind(null, cp.stdout.pipe(outputs.stdout), ['error', 'end', 'close', 'finish']));\n }\n if (cp.stderr) {\n outputs.stderr = concatWritable((output) => {\n (outputs.stderr as unknown as { output: string }).output = output.toString(encoding || 'utf8');\n });\n queue.defer(oo.bind(null, cp.stderr.pipe(outputs.stderr), ['error', 'end', 'close', 'finish']));\n }\n queue.defer(spawn.worker.bind(null, cp, csOptions));\n queue.await((err?: SpawnError) => {\n const res = (err ? err : {}) as SpawnResult;\n res.stdout = outputs.stdout ? (outputs.stdout as unknown as { output: string }).output : null;\n res.stderr = outputs.stderr ? (outputs.stderr as unknown as { output: string }).output : null;\n res.output = [res.stdout, res.stderr, null];\n err ? callback(err) : callback(null, res);\n });\n }\n }\n\n close(): void {\n if (this.closed) return;\n this.closed = true;\n this.cleanup();\n }\n\n waitAndClose(callback?: () => void): void {\n if (this.closed) {\n callback?.();\n return;\n }\n\n if (callback) this.waitCallbacks.push(callback);\n\n if (this.runningCount === 0) {\n if (this.isInteractive) {\n // In interactive mode, wait for user to quit (press 'q')\n const unsubscribe = this.store.subscribe(() => {\n if (this.store.getShouldExit()) {\n unsubscribe();\n this.closeAndCallWaitCallbacks();\n }\n });\n } else {\n this.closeAndCallWaitCallbacks();\n }\n }\n // If runningCount > 0, will close when it hits 0 in onProcessComplete\n }\n\n private onProcessComplete(): void {\n this.runningCount--;\n if (this.runningCount === 0 && this.waitCallbacks.length > 0) {\n if (this.isInteractive) {\n // In interactive mode, wait for user to quit (press 'q')\n const unsubscribe = this.store.subscribe(() => {\n if (this.store.getShouldExit()) {\n unsubscribe();\n this.closeAndCallWaitCallbacks();\n }\n });\n } else {\n this.closeAndCallWaitCallbacks();\n }\n }\n }\n\n private closeAndCallWaitCallbacks(): void {\n if (this.closed) return;\n this.closed = true;\n this.cleanup(() => {\n for (const cb of this.waitCallbacks) cb();\n this.waitCallbacks = [];\n });\n }\n\n private cleanup(onComplete?: () => void): void {\n // Signal exit to React component\n this.store.signalExit(() => {\n this.store.reset();\n process.stdout.write('\\x1b[?25h'); // show cursor\n });\n\n // Wait for Ink to finish\n if (this.inkApp) {\n this.inkApp\n .waitUntilExit()\n .then(() => {\n const cb = this.store.getExitCallback();\n cb?.();\n onComplete?.();\n })\n .catch(() => {\n const cb = this.store.getExitCallback();\n cb?.();\n onComplete?.();\n });\n this.inkApp = null;\n } else {\n onComplete?.();\n }\n }\n}\n\nexport function createSession(options: SessionOptions = {}): Session {\n return new SessionImpl(options);\n}\n"],"names":["createSession","SessionImpl","options","inkApp","runningCount","closed","waitCallbacks","store","ProcessStore","terminalWidth","render","App","maxFps","DEFAULT_MAX_FPS","isInteractive","process","stdout","isTTY","interactive","spawn","command","args","spawnOptions","callback","Error","encoding","stdio","csOptions","cp","crossSpawn","worker","err","res","stderr","output","id","crypto","randomUUID","terminalBuffer","TerminalBuffer","addProcess","title","concat","formatArguments","join","state","lines","group","expanded","on","chunk","write","notify","queue","Queue","defer","oo","bind","await","updateProcess","onProcessComplete","outputs","concatWritable","toString","pipe","close","cleanup","waitAndClose","push","unsubscribe","subscribe","getShouldExit","closeAndCallWaitCallbacks","length","cb","onComplete","signalExit","reset","waitUntilExit","then","getExitCallback","catch"],"mappings":";;;;+BAsOgBA;;;eAAAA;;;;oEAtOoC;6DACjC;mBACI;4DACR;8DACG;4DAEF;2BACgB;uEACL;wEACC;gCACG;8BACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAS7B,IAAA,AAAMC,4BAAN;;aAAMA;YASQC,UAAAA,iEAA0B,CAAC;gCATnCD;YAqB2CC;aAnBvCC,SAA2C;aAC3CC,eAAe;aACfC,SAAS;aACTC,gBAAgC,EAAE;QAKxC,IAAI,CAACC,KAAK,GAAG,IAAIC,4BAAY,CAACN;QAC9B,2DAA2D;QAC3D,yDAAyD;QACzD,IAAI,CAACO,aAAa,GAAG;QAErB,iDAAiD;QACjD,uFAAuF;QACvF,IAAI,CAACN,MAAM,GAAGO,IAAAA,WAAM,gBAAC,qBAACC,cAAG;YAACJ,OAAO,IAAI,CAACA,KAAK;YAAM;YAAEK,QAAQC,4BAAe;QAAC;QAE3E,gFAAgF;QAChF,sFAAsF;QACtF,IAAI,CAACC,aAAa,GAAGC,QAAQC,MAAM,CAACC,KAAK,IAAIf,uBAAAA,QAAQgB,WAAW,cAAnBhB,kCAAAA,uBAAuB,QAAS;;iBArB3ED;IAwBJkB,OAAAA,KAoGC,GApGDA,SAAAA,MAAMC,OAAe,EAAEC,IAAc,EAAEC,YAA0B,EAAEpB,OAAuB,EAAEqB,QAA0B;;QACpH,IAAI,IAAI,CAAClB,MAAM,EAAE;YACf,MAAM,IAAImB,MAAM;QAClB;QAEA,IAAQC,WAAkCH,aAAlCG,UAAUC,QAAwBJ,aAAxBI,OAAUC,uCAAcL;;;;QAE1C,IAAII,UAAU,WAAW;YACvB,+EAA+E;YAC/E,IAAI,CAAC,IAAI,CAACvB,MAAM,EAAE;gBAChB,IAAMyB,KAAKC,IAAAA,wBAAU,EAACT,SAASC,MAAM,wCAAKM;oBAAWD,OAAO;;gBAC5DP,qBAAK,CAACW,MAAM,CAACF,IAAID,WAAW,SAACI;oBAC3B,IAAMC,MAAOD,MAAMA,MAAM,CAAC;oBAC1BC,IAAIhB,MAAM,GAAG;oBACbgB,IAAIC,MAAM,GAAG;oBACbD,IAAIE,MAAM,GAAG;wBAAC;wBAAM;wBAAM;qBAAK;oBAC/BH,MAAMR,SAASQ,OAAOR,SAAS,MAAMS;gBACvC;gBACA;YACF;YAEA,IAAI,CAAC5B,YAAY;YACjB,IAAM+B,KAAKC,eAAM,CAACC,UAAU;YAE5B,0DAA0D;YAC1D,IAAMC,iBAAiB,IAAIC,gCAAc,CAAC,IAAI,CAAC9B,aAAa;YAE5D,IAAI,CAACF,KAAK,CAACiC,UAAU,CAAC;gBACpBL,IAAAA;gBACAM,OAAO;oBAACrB;iBAAQ,CAACsB,MAAM,CAACC,IAAAA,0BAAe,EAACtB,OAAOuB,IAAI,CAAC;gBACpDC,OAAO;gBACPC,OAAO,EAAE;gBACTR,gBAAAA;gBACAS,OAAO7C,QAAQ6C,KAAK;gBACpBC,UAAU9C,QAAQ8C,QAAQ;YAC5B;YAEA,IAAMpB,MAAKC,IAAAA,wBAAU,EAACT,SAASC,MAAMM;YAErC,qDAAqD;YACrD,kEAAkE;YAClE,IAAIC,IAAGZ,MAAM,EAAE;gBACbY,IAAGZ,MAAM,CAACiC,EAAE,CAAC,QAAQ,SAACC;oBACpBZ,eAAea,KAAK,CAACD;oBACrB,MAAK3C,KAAK,CAAC6C,MAAM;gBACnB;YACF;YACA,IAAIxB,IAAGK,MAAM,EAAE;gBACbL,IAAGK,MAAM,CAACgB,EAAE,CAAC,QAAQ,SAACC;oBACpBZ,eAAea,KAAK,CAACD;oBACrB,MAAK3C,KAAK,CAAC6C,MAAM;gBACnB;YACF;YAEA,+BAA+B;YAC/B,IAAMC,QAAQ,IAAIC,gBAAK;YACvB,IAAI1B,IAAGZ,MAAM,EAAE;gBACbqC,MAAME,KAAK,CAACC,cAAE,CAACC,IAAI,CAAC,MAAM7B,IAAGZ,MAAM,EAAE;oBAAC;oBAAS;oBAAO;iBAAQ;YAChE;YACA,IAAIY,IAAGK,MAAM,EAAE;gBACboB,MAAME,KAAK,CAACC,cAAE,CAACC,IAAI,CAAC,MAAM7B,IAAGK,MAAM,EAAE;oBAAC;oBAAS;oBAAO;iBAAQ;YAChE;YACAoB,MAAME,KAAK,CAACpC,qBAAK,CAACW,MAAM,CAAC2B,IAAI,CAAC,MAAM7B,KAAID;YACxC0B,MAAMK,KAAK,CAAC,SAAC3B;gBACX,IAAMC,MAAOD,MAAMA,MAAM,CAAC;gBAC1BC,IAAIhB,MAAM,GAAG,MAAM,4CAA4C;gBAC/DgB,IAAIC,MAAM,GAAG;gBACbD,IAAIE,MAAM,GAAG;oBAAC;oBAAM;oBAAM;iBAAK;gBAC/B,MAAK3B,KAAK,CAACoD,aAAa,CAACxB,IAAI;oBAAEU,OAAOd,MAAM,UAAU;gBAAU;gBAEhE,MAAK6B,iBAAiB;gBACtB7B,MAAMR,SAASQ,OAAOR,SAAS,MAAMS;YACvC;QACF,OAAO;YACL,2DAA2D;YAC3D,IAAMJ,MAAKC,IAAAA,wBAAU,EAACT,SAASC,MAAMM;YACrC,IAAMkC,UAAU;gBAAE7C,QAAQ;gBAAkDiB,QAAQ;YAAiD;YAErI,IAAMoB,SAAQ,IAAIC,gBAAK;YACvB,IAAI1B,IAAGZ,MAAM,EAAE;gBACb6C,QAAQ7C,MAAM,GAAG8C,IAAAA,yBAAc,EAAC,SAAC5B;oBAC9B2B,QAAQ7C,MAAM,CAAmCkB,MAAM,GAAGA,OAAO6B,QAAQ,CAACtC,YAAY;gBACzF;gBACA4B,OAAME,KAAK,CAACC,cAAE,CAACC,IAAI,CAAC,MAAM7B,IAAGZ,MAAM,CAACgD,IAAI,CAACH,QAAQ7C,MAAM,GAAG;oBAAC;oBAAS;oBAAO;oBAAS;iBAAS;YAC/F;YACA,IAAIY,IAAGK,MAAM,EAAE;gBACb4B,QAAQ5B,MAAM,GAAG6B,IAAAA,yBAAc,EAAC,SAAC5B;oBAC9B2B,QAAQ5B,MAAM,CAAmCC,MAAM,GAAGA,OAAO6B,QAAQ,CAACtC,YAAY;gBACzF;gBACA4B,OAAME,KAAK,CAACC,cAAE,CAACC,IAAI,CAAC,MAAM7B,IAAGK,MAAM,CAAC+B,IAAI,CAACH,QAAQ5B,MAAM,GAAG;oBAAC;oBAAS;oBAAO;oBAAS;iBAAS;YAC/F;YACAoB,OAAME,KAAK,CAACpC,qBAAK,CAACW,MAAM,CAAC2B,IAAI,CAAC,MAAM7B,KAAID;YACxC0B,OAAMK,KAAK,CAAC,SAAC3B;gBACX,IAAMC,MAAOD,MAAMA,MAAM,CAAC;gBAC1BC,IAAIhB,MAAM,GAAG6C,QAAQ7C,MAAM,GAAG,AAAC6C,QAAQ7C,MAAM,CAAmCkB,MAAM,GAAG;gBACzFF,IAAIC,MAAM,GAAG4B,QAAQ5B,MAAM,GAAG,AAAC4B,QAAQ5B,MAAM,CAAmCC,MAAM,GAAG;gBACzFF,IAAIE,MAAM,GAAG;oBAACF,IAAIhB,MAAM;oBAAEgB,IAAIC,MAAM;oBAAE;iBAAK;gBAC3CF,MAAMR,SAASQ,OAAOR,SAAS,MAAMS;YACvC;QACF;IACF;IAEAiC,OAAAA,KAIC,GAJDA,SAAAA;QACE,IAAI,IAAI,CAAC5D,MAAM,EAAE;QACjB,IAAI,CAACA,MAAM,GAAG;QACd,IAAI,CAAC6D,OAAO;IACd;IAEAC,OAAAA,YAsBC,GAtBDA,SAAAA,aAAa5C,QAAqB;;QAChC,IAAI,IAAI,CAAClB,MAAM,EAAE;YACfkB,qBAAAA,+BAAAA;YACA;QACF;QAEA,IAAIA,UAAU,IAAI,CAACjB,aAAa,CAAC8D,IAAI,CAAC7C;QAEtC,IAAI,IAAI,CAACnB,YAAY,KAAK,GAAG;YAC3B,IAAI,IAAI,CAACU,aAAa,EAAE;gBACtB,yDAAyD;gBACzD,IAAMuD,cAAc,IAAI,CAAC9D,KAAK,CAAC+D,SAAS,CAAC;oBACvC,IAAI,MAAK/D,KAAK,CAACgE,aAAa,IAAI;wBAC9BF;wBACA,MAAKG,yBAAyB;oBAChC;gBACF;YACF,OAAO;gBACL,IAAI,CAACA,yBAAyB;YAChC;QACF;IACA,sEAAsE;IACxE;IAEA,OAAQZ,iBAeP,GAfD,SAAQA;;QACN,IAAI,CAACxD,YAAY;QACjB,IAAI,IAAI,CAACA,YAAY,KAAK,KAAK,IAAI,CAACE,aAAa,CAACmE,MAAM,GAAG,GAAG;YAC5D,IAAI,IAAI,CAAC3D,aAAa,EAAE;gBACtB,yDAAyD;gBACzD,IAAMuD,cAAc,IAAI,CAAC9D,KAAK,CAAC+D,SAAS,CAAC;oBACvC,IAAI,MAAK/D,KAAK,CAACgE,aAAa,IAAI;wBAC9BF;wBACA,MAAKG,yBAAyB;oBAChC;gBACF;YACF,OAAO;gBACL,IAAI,CAACA,yBAAyB;YAChC;QACF;IACF;IAEA,OAAQA,yBAOP,GAPD,SAAQA;;QACN,IAAI,IAAI,CAACnE,MAAM,EAAE;QACjB,IAAI,CAACA,MAAM,GAAG;QACd,IAAI,CAAC6D,OAAO,CAAC;gBACN,kCAAA,2BAAA;;gBAAL,QAAK,YAAY,MAAK5D,aAAa,qBAA9B,SAAA,6BAAA,QAAA,yBAAA;oBAAA,IAAMoE,KAAN;oBAAgCA;;;gBAAhC;gBAAA;;;yBAAA,6BAAA;wBAAA;;;wBAAA;8BAAA;;;;YACL,MAAKpE,aAAa,GAAG,EAAE;QACzB;IACF;IAEA,OAAQ4D,OAyBP,GAzBD,SAAQA,QAAQS,UAAuB;;QACrC,iCAAiC;QACjC,IAAI,CAACpE,KAAK,CAACqE,UAAU,CAAC;YACpB,MAAKrE,KAAK,CAACsE,KAAK;YAChB9D,QAAQC,MAAM,CAACmC,KAAK,CAAC,cAAc,cAAc;QACnD;QAEA,yBAAyB;QACzB,IAAI,IAAI,CAAChD,MAAM,EAAE;YACf,IAAI,CAACA,MAAM,CACR2E,aAAa,GACbC,IAAI,CAAC;gBACJ,IAAML,KAAK,MAAKnE,KAAK,CAACyE,eAAe;gBACrCN,eAAAA,yBAAAA;gBACAC,uBAAAA,iCAAAA;YACF,GACCM,KAAK,CAAC;gBACL,IAAMP,KAAK,MAAKnE,KAAK,CAACyE,eAAe;gBACrCN,eAAAA,yBAAAA;gBACAC,uBAAAA,iCAAAA;YACF;YACF,IAAI,CAACxE,MAAM,GAAG;QAChB,OAAO;YACLwE,uBAAAA,iCAAAA;QACF;IACF;WA/MI1E;;AAkNC,SAASD;QAAcE,UAAAA,iEAA0B,CAAC;IACvD,OAAO,IAAID,YAAYC;AACzB"}
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrapper around terminal-model's StreamingTerminal that provides a virtual terminal buffer.
|
|
3
|
+
* Interprets ANSI escape sequences (cursor movement, line clearing, etc.) to produce
|
|
4
|
+
* the actual rendered output rather than raw intermediate states.
|
|
5
|
+
*
|
|
6
|
+
* This implementation preserves whitespace and blank lines by NOT calling trimStart(),
|
|
7
|
+
* which was the bug in the previous xterm-based implementation.
|
|
8
|
+
*/
|
|
1
9
|
export declare class TerminalBuffer {
|
|
2
10
|
private terminal;
|
|
3
|
-
|
|
11
|
+
private allLines;
|
|
12
|
+
constructor(_cols: number, _scrollback?: number);
|
|
4
13
|
/**
|
|
5
14
|
* Write raw data to the terminal buffer.
|
|
6
15
|
* The terminal interprets all ANSI sequences automatically.
|
|
@@ -8,12 +17,16 @@ export declare class TerminalBuffer {
|
|
|
8
17
|
write(data: string | Buffer): void;
|
|
9
18
|
/**
|
|
10
19
|
* Resize the terminal width.
|
|
20
|
+
* terminal-model doesn't use column constraints, so this is a no-op for compatibility.
|
|
11
21
|
*/
|
|
12
|
-
resize(
|
|
22
|
+
resize(_cols: number): void;
|
|
13
23
|
/**
|
|
14
24
|
* Extract the rendered lines from the terminal buffer.
|
|
15
25
|
* This returns the actual visible content after all ANSI sequences
|
|
16
26
|
* have been processed, with color codes preserved.
|
|
27
|
+
*
|
|
28
|
+
* CRITICAL: Unlike the xterm implementation, we do NOT call trimStart(),
|
|
29
|
+
* which preserves legitimate indentation and blank lines.
|
|
17
30
|
*/
|
|
18
31
|
getLines(): string[];
|
|
19
32
|
/**
|
|
@@ -1,155 +1,43 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const DEFAULT_STYLE = {
|
|
11
|
-
fg: -1,
|
|
12
|
-
fgMode: COLOR_MODE_DEFAULT,
|
|
13
|
-
bg: -1,
|
|
14
|
-
bgMode: COLOR_MODE_DEFAULT,
|
|
15
|
-
bold: false,
|
|
16
|
-
dim: false,
|
|
17
|
-
italic: false,
|
|
18
|
-
underline: false,
|
|
19
|
-
inverse: false,
|
|
20
|
-
strikethrough: false
|
|
21
|
-
};
|
|
22
|
-
function styleEquals(a, b) {
|
|
23
|
-
return a.fg === b.fg && a.fgMode === b.fgMode && a.bg === b.bg && a.bgMode === b.bgMode && a.bold === b.bold && a.dim === b.dim && a.italic === b.italic && a.underline === b.underline && a.inverse === b.inverse && a.strikethrough === b.strikethrough;
|
|
24
|
-
}
|
|
25
|
-
function buildAnsiCode(style) {
|
|
26
|
-
const codes = [];
|
|
27
|
-
// Attributes
|
|
28
|
-
if (style.bold) codes.push(1);
|
|
29
|
-
if (style.dim) codes.push(2);
|
|
30
|
-
if (style.italic) codes.push(3);
|
|
31
|
-
if (style.underline) codes.push(4);
|
|
32
|
-
if (style.inverse) codes.push(7);
|
|
33
|
-
if (style.strikethrough) codes.push(9);
|
|
34
|
-
// Foreground color
|
|
35
|
-
if (style.fgMode === COLOR_MODE_16) {
|
|
36
|
-
// 16-color palette: 0-7 are 30-37, 8-15 are 90-97
|
|
37
|
-
if (style.fg < 8) {
|
|
38
|
-
codes.push(30 + style.fg);
|
|
39
|
-
} else {
|
|
40
|
-
codes.push(90 + (style.fg - 8));
|
|
41
|
-
}
|
|
42
|
-
} else if (style.fgMode === COLOR_MODE_256) {
|
|
43
|
-
codes.push(38, 5, style.fg);
|
|
44
|
-
} else if (style.fgMode === COLOR_MODE_RGB) {
|
|
45
|
-
// RGB is encoded in the color value
|
|
46
|
-
const r = style.fg >> 16 & 0xff;
|
|
47
|
-
const g = style.fg >> 8 & 0xff;
|
|
48
|
-
const b = style.fg & 0xff;
|
|
49
|
-
codes.push(38, 2, r, g, b);
|
|
50
|
-
}
|
|
51
|
-
// Background color
|
|
52
|
-
if (style.bgMode === COLOR_MODE_16) {
|
|
53
|
-
if (style.bg < 8) {
|
|
54
|
-
codes.push(40 + style.bg);
|
|
55
|
-
} else {
|
|
56
|
-
codes.push(100 + (style.bg - 8));
|
|
57
|
-
}
|
|
58
|
-
} else if (style.bgMode === COLOR_MODE_256) {
|
|
59
|
-
codes.push(48, 5, style.bg);
|
|
60
|
-
} else if (style.bgMode === COLOR_MODE_RGB) {
|
|
61
|
-
const r = style.bg >> 16 & 0xff;
|
|
62
|
-
const g = style.bg >> 8 & 0xff;
|
|
63
|
-
const b = style.bg & 0xff;
|
|
64
|
-
codes.push(48, 2, r, g, b);
|
|
65
|
-
}
|
|
66
|
-
if (codes.length === 0) return '';
|
|
67
|
-
return `\x1b[${codes.join(';')}m`;
|
|
68
|
-
}
|
|
69
|
-
export class TerminalBuffer {
|
|
1
|
+
import { StreamingTerminal } from 'terminal-model';
|
|
2
|
+
/**
|
|
3
|
+
* Wrapper around terminal-model's StreamingTerminal that provides a virtual terminal buffer.
|
|
4
|
+
* Interprets ANSI escape sequences (cursor movement, line clearing, etc.) to produce
|
|
5
|
+
* the actual rendered output rather than raw intermediate states.
|
|
6
|
+
*
|
|
7
|
+
* This implementation preserves whitespace and blank lines by NOT calling trimStart(),
|
|
8
|
+
* which was the bug in the previous xterm-based implementation.
|
|
9
|
+
*/ export class TerminalBuffer {
|
|
70
10
|
/**
|
|
71
11
|
* Write raw data to the terminal buffer.
|
|
72
12
|
* The terminal interprets all ANSI sequences automatically.
|
|
73
13
|
*/ write(data) {
|
|
74
|
-
|
|
75
|
-
this.terminal.write(str);
|
|
14
|
+
this.terminal.write(data);
|
|
76
15
|
}
|
|
77
16
|
/**
|
|
78
17
|
* Resize the terminal width.
|
|
79
|
-
|
|
80
|
-
|
|
18
|
+
* terminal-model doesn't use column constraints, so this is a no-op for compatibility.
|
|
19
|
+
*/ resize(_cols) {
|
|
20
|
+
// No-op - terminal-model doesn't enforce column width
|
|
81
21
|
}
|
|
82
22
|
/**
|
|
83
23
|
* Extract the rendered lines from the terminal buffer.
|
|
84
24
|
* This returns the actual visible content after all ANSI sequences
|
|
85
25
|
* have been processed, with color codes preserved.
|
|
26
|
+
*
|
|
27
|
+
* CRITICAL: Unlike the xterm implementation, we do NOT call trimStart(),
|
|
28
|
+
* which preserves legitimate indentation and blank lines.
|
|
86
29
|
*/ getLines() {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
let result = '';
|
|
93
|
-
let currentStyle = {
|
|
94
|
-
...DEFAULT_STYLE
|
|
95
|
-
};
|
|
96
|
-
let _hasContent = false;
|
|
97
|
-
// First pass: find the last non-empty cell to know where content ends
|
|
98
|
-
let lastContentIndex = -1;
|
|
99
|
-
for(let j = bufferLine.length - 1; j >= 0; j--){
|
|
100
|
-
const cell = bufferLine.getCell(j);
|
|
101
|
-
if (cell && cell.getChars()) {
|
|
102
|
-
lastContentIndex = j;
|
|
103
|
-
break;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
// Second pass: build the line with ANSI codes
|
|
107
|
-
for(let j = 0; j <= lastContentIndex; j++){
|
|
108
|
-
const cell = bufferLine.getCell(j);
|
|
109
|
-
if (!cell) continue;
|
|
110
|
-
const char = cell.getChars();
|
|
111
|
-
const cellStyle = {
|
|
112
|
-
fg: cell.getFgColor(),
|
|
113
|
-
fgMode: cell.getFgColorMode(),
|
|
114
|
-
bg: cell.getBgColor(),
|
|
115
|
-
bgMode: cell.getBgColorMode(),
|
|
116
|
-
bold: cell.isBold() !== 0,
|
|
117
|
-
dim: cell.isDim() !== 0,
|
|
118
|
-
italic: cell.isItalic() !== 0,
|
|
119
|
-
underline: cell.isUnderline() !== 0,
|
|
120
|
-
inverse: cell.isInverse() !== 0,
|
|
121
|
-
strikethrough: cell.isStrikethrough() !== 0
|
|
122
|
-
};
|
|
123
|
-
// Check if style changed
|
|
124
|
-
if (!styleEquals(cellStyle, currentStyle)) {
|
|
125
|
-
// Reset if going back to default, otherwise emit new style
|
|
126
|
-
if (styleEquals(cellStyle, DEFAULT_STYLE)) {
|
|
127
|
-
result += '\x1b[0m';
|
|
128
|
-
} else {
|
|
129
|
-
// If we had styling before, reset first for clean transition
|
|
130
|
-
if (!styleEquals(currentStyle, DEFAULT_STYLE)) {
|
|
131
|
-
result += '\x1b[0m';
|
|
132
|
-
}
|
|
133
|
-
result += buildAnsiCode(cellStyle);
|
|
134
|
-
}
|
|
135
|
-
currentStyle = cellStyle;
|
|
136
|
-
}
|
|
137
|
-
result += char || ' ';
|
|
138
|
-
if (char) _hasContent = true;
|
|
139
|
-
}
|
|
140
|
-
// Reset at end of line if we had styling
|
|
141
|
-
if (!styleEquals(currentStyle, DEFAULT_STYLE)) {
|
|
142
|
-
result += '\x1b[0m';
|
|
143
|
-
}
|
|
144
|
-
// Trim leading whitespace - tools like ncu/npm use cursor positioning
|
|
145
|
-
// which creates lines with leading spaces when interpreted by xterm
|
|
146
|
-
lines.push(result.trimStart());
|
|
147
|
-
}
|
|
148
|
-
// Trim trailing empty lines
|
|
149
|
-
while(lines.length > 0 && lines[lines.length - 1] === ''){
|
|
150
|
-
lines.pop();
|
|
30
|
+
// Flush any pending content (incomplete line without \n)
|
|
31
|
+
if (this.terminal.hasContent()) {
|
|
32
|
+
const line = this.terminal.renderLine();
|
|
33
|
+
this.terminal.reset();
|
|
34
|
+
this.allLines.push(line);
|
|
151
35
|
}
|
|
152
|
-
|
|
36
|
+
// Return copy of all lines WITHOUT trimStart() or trimming blank lines
|
|
37
|
+
// Preserves all whitespace, indentation, and blank lines
|
|
38
|
+
return [
|
|
39
|
+
...this.allLines
|
|
40
|
+
];
|
|
153
41
|
}
|
|
154
42
|
/**
|
|
155
43
|
* Get the number of rendered lines.
|
|
@@ -160,13 +48,18 @@ export class TerminalBuffer {
|
|
|
160
48
|
* Clean up terminal resources.
|
|
161
49
|
*/ dispose() {
|
|
162
50
|
this.terminal.dispose();
|
|
51
|
+
this.allLines = [];
|
|
163
52
|
}
|
|
164
|
-
constructor(
|
|
165
|
-
this.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
53
|
+
constructor(_cols, _scrollback = 10000){
|
|
54
|
+
this.allLines = [];
|
|
55
|
+
// terminal-model doesn't enforce column width during parsing
|
|
56
|
+
// It preserves all content as-is
|
|
57
|
+
this.terminal = new StreamingTerminal();
|
|
58
|
+
// Listen for completed lines (when \n is encountered)
|
|
59
|
+
this.terminal.setLineReadyCallback(()=>{
|
|
60
|
+
const line = this.terminal.renderLine();
|
|
61
|
+
this.terminal.reset();
|
|
62
|
+
this.allLines.push(line);
|
|
170
63
|
});
|
|
171
64
|
}
|
|
172
65
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/lib/TerminalBuffer.ts"],"sourcesContent":["import * as xterm from '@xterm/headless';\n\n// Handle both ESM and CJS module formats\nconst Terminal = (xterm as { Terminal: typeof xterm.Terminal; default?: { Terminal: typeof xterm.Terminal } }).Terminal || (xterm as { default?: { Terminal: typeof xterm.Terminal } }).default?.Terminal;\n\n// ANSI color mode constants from xterm.js\nconst COLOR_MODE_DEFAULT = 0;\nconst COLOR_MODE_16 = 16777216; // 0x1000000 - 16 color palette (0-15)\nconst COLOR_MODE_256 = 33554432; // 0x2000000 - 256 color palette\nconst COLOR_MODE_RGB = 50331648; // 0x3000000 - 24-bit RGB\n\n/**\n * Wrapper around @xterm/headless Terminal that provides a virtual terminal buffer.\n * Interprets ANSI escape sequences (cursor movement, line clearing, etc.) to produce\n * the actual rendered output rather than raw intermediate states.\n */\n// Cell attribute state for tracking changes\ninterface CellStyle {\n fg: number;\n fgMode: number;\n bg: number;\n bgMode: number;\n bold: boolean;\n dim: boolean;\n italic: boolean;\n underline: boolean;\n inverse: boolean;\n strikethrough: boolean;\n}\n\nconst DEFAULT_STYLE: CellStyle = {\n fg: -1,\n fgMode: COLOR_MODE_DEFAULT,\n bg: -1,\n bgMode: COLOR_MODE_DEFAULT,\n bold: false,\n dim: false,\n italic: false,\n underline: false,\n inverse: false,\n strikethrough: false,\n};\n\nfunction styleEquals(a: CellStyle, b: CellStyle): boolean {\n return a.fg === b.fg && a.fgMode === b.fgMode && a.bg === b.bg && a.bgMode === b.bgMode && a.bold === b.bold && a.dim === b.dim && a.italic === b.italic && a.underline === b.underline && a.inverse === b.inverse && a.strikethrough === b.strikethrough;\n}\n\nfunction buildAnsiCode(style: CellStyle): string {\n const codes: number[] = [];\n\n // Attributes\n if (style.bold) codes.push(1);\n if (style.dim) codes.push(2);\n if (style.italic) codes.push(3);\n if (style.underline) codes.push(4);\n if (style.inverse) codes.push(7);\n if (style.strikethrough) codes.push(9);\n\n // Foreground color\n if (style.fgMode === COLOR_MODE_16) {\n // 16-color palette: 0-7 are 30-37, 8-15 are 90-97\n if (style.fg < 8) {\n codes.push(30 + style.fg);\n } else {\n codes.push(90 + (style.fg - 8));\n }\n } else if (style.fgMode === COLOR_MODE_256) {\n codes.push(38, 5, style.fg);\n } else if (style.fgMode === COLOR_MODE_RGB) {\n // RGB is encoded in the color value\n const r = (style.fg >> 16) & 0xff;\n const g = (style.fg >> 8) & 0xff;\n const b = style.fg & 0xff;\n codes.push(38, 2, r, g, b);\n }\n\n // Background color\n if (style.bgMode === COLOR_MODE_16) {\n if (style.bg < 8) {\n codes.push(40 + style.bg);\n } else {\n codes.push(100 + (style.bg - 8));\n }\n } else if (style.bgMode === COLOR_MODE_256) {\n codes.push(48, 5, style.bg);\n } else if (style.bgMode === COLOR_MODE_RGB) {\n const r = (style.bg >> 16) & 0xff;\n const g = (style.bg >> 8) & 0xff;\n const b = style.bg & 0xff;\n codes.push(48, 2, r, g, b);\n }\n\n if (codes.length === 0) return '';\n return `\\x1b[${codes.join(';')}m`;\n}\n\nexport class TerminalBuffer {\n private terminal: InstanceType<typeof Terminal>;\n\n constructor(cols: number, scrollback = 10000) {\n this.terminal = new Terminal({\n cols,\n rows: 50, // Visible rows (doesn't matter much for headless)\n scrollback,\n allowProposedApi: true,\n });\n }\n\n /**\n * Write raw data to the terminal buffer.\n * The terminal interprets all ANSI sequences automatically.\n */\n write(data: string | Buffer): void {\n const str = typeof data === 'string' ? data : data.toString('utf8');\n this.terminal.write(str);\n }\n\n /**\n * Resize the terminal width.\n */\n resize(cols: number): void {\n this.terminal.resize(cols, this.terminal.rows);\n }\n\n /**\n * Extract the rendered lines from the terminal buffer.\n * This returns the actual visible content after all ANSI sequences\n * have been processed, with color codes preserved.\n */\n getLines(): string[] {\n const buffer = this.terminal.buffer.active;\n const lines: string[] = [];\n\n for (let i = 0; i < buffer.length; i++) {\n const bufferLine = buffer.getLine(i);\n if (!bufferLine) continue;\n\n let result = '';\n let currentStyle: CellStyle = { ...DEFAULT_STYLE };\n let _hasContent = false;\n\n // First pass: find the last non-empty cell to know where content ends\n let lastContentIndex = -1;\n for (let j = bufferLine.length - 1; j >= 0; j--) {\n const cell = bufferLine.getCell(j);\n if (cell && cell.getChars()) {\n lastContentIndex = j;\n break;\n }\n }\n\n // Second pass: build the line with ANSI codes\n for (let j = 0; j <= lastContentIndex; j++) {\n const cell = bufferLine.getCell(j);\n if (!cell) continue;\n\n const char = cell.getChars();\n const cellStyle: CellStyle = {\n fg: cell.getFgColor(),\n fgMode: cell.getFgColorMode(),\n bg: cell.getBgColor(),\n bgMode: cell.getBgColorMode(),\n bold: cell.isBold() !== 0,\n dim: cell.isDim() !== 0,\n italic: cell.isItalic() !== 0,\n underline: cell.isUnderline() !== 0,\n inverse: cell.isInverse() !== 0,\n strikethrough: cell.isStrikethrough() !== 0,\n };\n\n // Check if style changed\n if (!styleEquals(cellStyle, currentStyle)) {\n // Reset if going back to default, otherwise emit new style\n if (styleEquals(cellStyle, DEFAULT_STYLE)) {\n result += '\\x1b[0m';\n } else {\n // If we had styling before, reset first for clean transition\n if (!styleEquals(currentStyle, DEFAULT_STYLE)) {\n result += '\\x1b[0m';\n }\n result += buildAnsiCode(cellStyle);\n }\n currentStyle = cellStyle;\n }\n\n result += char || ' ';\n if (char) _hasContent = true;\n }\n\n // Reset at end of line if we had styling\n if (!styleEquals(currentStyle, DEFAULT_STYLE)) {\n result += '\\x1b[0m';\n }\n\n // Trim leading whitespace - tools like ncu/npm use cursor positioning\n // which creates lines with leading spaces when interpreted by xterm\n lines.push(result.trimStart());\n }\n\n // Trim trailing empty lines\n while (lines.length > 0 && lines[lines.length - 1] === '') {\n lines.pop();\n }\n\n return lines;\n }\n\n /**\n * Get the number of rendered lines.\n */\n get lineCount(): number {\n return this.getLines().length;\n }\n\n /**\n * Clean up terminal resources.\n */\n dispose(): void {\n this.terminal.dispose();\n }\n}\n"],"names":["xterm","Terminal","default","COLOR_MODE_DEFAULT","COLOR_MODE_16","COLOR_MODE_256","COLOR_MODE_RGB","DEFAULT_STYLE","fg","fgMode","bg","bgMode","bold","dim","italic","underline","inverse","strikethrough","styleEquals","a","b","buildAnsiCode","style","codes","push","r","g","length","join","TerminalBuffer","write","data","str","toString","terminal","resize","cols","rows","getLines","buffer","active","lines","i","bufferLine","getLine","result","currentStyle","_hasContent","lastContentIndex","j","cell","getCell","getChars","char","cellStyle","getFgColor","getFgColorMode","getBgColor","getBgColorMode","isBold","isDim","isItalic","isUnderline","isInverse","isStrikethrough","trimStart","pop","lineCount","dispose","scrollback","allowProposedApi"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/lib/TerminalBuffer.ts"],"sourcesContent":["import { StreamingTerminal } from 'terminal-model';\n\n/**\n * Wrapper around terminal-model's StreamingTerminal that provides a virtual terminal buffer.\n * Interprets ANSI escape sequences (cursor movement, line clearing, etc.) to produce\n * the actual rendered output rather than raw intermediate states.\n *\n * This implementation preserves whitespace and blank lines by NOT calling trimStart(),\n * which was the bug in the previous xterm-based implementation.\n */\nexport class TerminalBuffer {\n private terminal: StreamingTerminal;\n private allLines: string[] = [];\n\n constructor(_cols: number, _scrollback = 10000) {\n // terminal-model doesn't enforce column width during parsing\n // It preserves all content as-is\n this.terminal = new StreamingTerminal();\n\n // Listen for completed lines (when \\n is encountered)\n this.terminal.setLineReadyCallback(() => {\n const line = this.terminal.renderLine();\n this.terminal.reset();\n this.allLines.push(line);\n });\n }\n\n /**\n * Write raw data to the terminal buffer.\n * The terminal interprets all ANSI sequences automatically.\n */\n write(data: string | Buffer): void {\n this.terminal.write(data);\n }\n\n /**\n * Resize the terminal width.\n * terminal-model doesn't use column constraints, so this is a no-op for compatibility.\n */\n resize(_cols: number): void {\n // No-op - terminal-model doesn't enforce column width\n }\n\n /**\n * Extract the rendered lines from the terminal buffer.\n * This returns the actual visible content after all ANSI sequences\n * have been processed, with color codes preserved.\n *\n * CRITICAL: Unlike the xterm implementation, we do NOT call trimStart(),\n * which preserves legitimate indentation and blank lines.\n */\n getLines(): string[] {\n // Flush any pending content (incomplete line without \\n)\n if (this.terminal.hasContent()) {\n const line = this.terminal.renderLine();\n this.terminal.reset();\n this.allLines.push(line);\n }\n\n // Return copy of all lines WITHOUT trimStart() or trimming blank lines\n // Preserves all whitespace, indentation, and blank lines\n return [...this.allLines];\n }\n\n /**\n * Get the number of rendered lines.\n */\n get lineCount(): number {\n return this.getLines().length;\n }\n\n /**\n * Clean up terminal resources.\n */\n dispose(): void {\n this.terminal.dispose();\n this.allLines = [];\n }\n}\n"],"names":["StreamingTerminal","TerminalBuffer","write","data","terminal","resize","_cols","getLines","hasContent","line","renderLine","reset","allLines","push","lineCount","length","dispose","_scrollback","setLineReadyCallback"],"mappings":"AAAA,SAASA,iBAAiB,QAAQ,iBAAiB;AAEnD;;;;;;;CAOC,GACD,OAAO,MAAMC;IAiBX;;;GAGC,GACDC,MAAMC,IAAqB,EAAQ;QACjC,IAAI,CAACC,QAAQ,CAACF,KAAK,CAACC;IACtB;IAEA;;;GAGC,GACDE,OAAOC,KAAa,EAAQ;IAC1B,sDAAsD;IACxD;IAEA;;;;;;;GAOC,GACDC,WAAqB;QACnB,yDAAyD;QACzD,IAAI,IAAI,CAACH,QAAQ,CAACI,UAAU,IAAI;YAC9B,MAAMC,OAAO,IAAI,CAACL,QAAQ,CAACM,UAAU;YACrC,IAAI,CAACN,QAAQ,CAACO,KAAK;YACnB,IAAI,CAACC,QAAQ,CAACC,IAAI,CAACJ;QACrB;QAEA,uEAAuE;QACvE,yDAAyD;QACzD,OAAO;eAAI,IAAI,CAACG,QAAQ;SAAC;IAC3B;IAEA;;GAEC,GACD,IAAIE,YAAoB;QACtB,OAAO,IAAI,CAACP,QAAQ,GAAGQ,MAAM;IAC/B;IAEA;;GAEC,GACDC,UAAgB;QACd,IAAI,CAACZ,QAAQ,CAACY,OAAO;QACrB,IAAI,CAACJ,QAAQ,GAAG,EAAE;IACpB;IA/DA,YAAYN,KAAa,EAAEW,cAAc,KAAK,CAAE;aAFxCL,WAAqB,EAAE;QAG7B,6DAA6D;QAC7D,iCAAiC;QACjC,IAAI,CAACR,QAAQ,GAAG,IAAIJ;QAEpB,sDAAsD;QACtD,IAAI,CAACI,QAAQ,CAACc,oBAAoB,CAAC;YACjC,MAAMT,OAAO,IAAI,CAACL,QAAQ,CAACM,UAAU;YACrC,IAAI,CAACN,QAAQ,CAACO,KAAK;YACnB,IAAI,CAACC,QAAQ,CAACC,IAAI,CAACJ;QACrB;IACF;AAqDF"}
|
package/dist/esm/session.js
CHANGED
|
@@ -231,9 +231,9 @@ let SessionImpl = class SessionImpl {
|
|
|
231
231
|
}), {
|
|
232
232
|
maxFps: DEFAULT_MAX_FPS
|
|
233
233
|
});
|
|
234
|
-
// Interactive mode requires a TTY
|
|
235
|
-
//
|
|
236
|
-
this.isInteractive =
|
|
234
|
+
// Interactive mode requires a real TTY for user input (e.g., press 'q' to quit)
|
|
235
|
+
// Without a TTY, there's no way to receive keyboard input, so auto-exit when complete
|
|
236
|
+
this.isInteractive = process.stdout.isTTY ? (_options_interactive = options.interactive) !== null && _options_interactive !== void 0 ? _options_interactive : false : false;
|
|
237
237
|
}
|
|
238
238
|
};
|
|
239
239
|
export function createSession(options = {}) {
|
package/dist/esm/session.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/session.tsx"],"sourcesContent":["import spawn, { crossSpawn, type SpawnResult } from 'cross-spawn-cb';\nimport crypto from 'crypto';\nimport { render } from 'ink';\nimport oo from 'on-one';\nimport Queue from 'queue-cb';\n\nimport App from './components/App.ts';\nimport { DEFAULT_MAX_FPS } from './constants.ts';\nimport concatWritable from './lib/concatWritable.ts';\nimport formatArguments from './lib/formatArguments.ts';\nimport { TerminalBuffer } from './lib/TerminalBuffer.ts';\nimport { ProcessStore } from './state/processStore.ts';\nimport type { ProcessOptions, SessionOptions, SpawnError, SpawnOptions, TerminalCallback } from './types.ts';\n\nexport interface Session {\n spawn(command: string, args: string[], spawnOptions: SpawnOptions, options: ProcessOptions, callback: TerminalCallback): void;\n close(): void;\n waitAndClose(callback?: () => void): void;\n}\n\nclass SessionImpl implements Session {\n private store: ProcessStore;\n private inkApp: ReturnType<typeof render> | null = null;\n private runningCount = 0;\n private closed = false;\n private waitCallbacks: (() => void)[] = [];\n private isInteractive: boolean;\n private terminalWidth: number;\n\n constructor(options: SessionOptions = {}) {\n this.store = new ProcessStore(options);\n // Use a very wide buffer to prevent line wrapping in xterm\n // Actual display truncation is handled by Ink components\n this.terminalWidth = 10000;\n\n // Only render Ink when stdout is a real terminal\n // When piped (e.g., nested spawn-term), skip Ink to avoid cursor positioning artifacts\n this.inkApp = render(<App store={this.store} />, { maxFps: DEFAULT_MAX_FPS });\n\n // Interactive mode requires a TTY to capture user input (e.g., press 'q' to quit)\n // Force non-interactive when no UI is rendered, otherwise waitAndClose would hang\n this.isInteractive = this.inkApp ? (options.interactive ?? false) : false;\n }\n\n spawn(command: string, args: string[], spawnOptions: SpawnOptions, options: ProcessOptions, callback: TerminalCallback): void {\n if (this.closed) {\n throw new Error('Session is closed');\n }\n\n const { encoding, stdio, ...csOptions } = spawnOptions;\n\n if (stdio === 'inherit') {\n // When Ink is not rendering (stdout not a TTY), pass output directly to stdout\n if (!this.inkApp) {\n const cp = crossSpawn(command, args, { ...csOptions, stdio: 'inherit' });\n spawn.worker(cp, csOptions, (err?: SpawnError) => {\n const res = (err ? err : {}) as SpawnResult;\n res.stdout = null;\n res.stderr = null;\n res.output = [null, null, null];\n err ? callback(err) : callback(null, res);\n });\n return;\n }\n\n this.runningCount++;\n const id = crypto.randomUUID();\n\n // Create terminal buffer for ANSI sequence interpretation\n const terminalBuffer = new TerminalBuffer(this.terminalWidth);\n\n this.store.addProcess({\n id,\n title: [command].concat(formatArguments(args)).join(' '),\n state: 'running',\n lines: [],\n terminalBuffer,\n group: options.group,\n expanded: options.expanded,\n });\n\n const cp = crossSpawn(command, args, csOptions);\n\n // Pipe stdout and stderr directly to terminal buffer\n // Both streams go to the same buffer to maintain correct ordering\n if (cp.stdout) {\n cp.stdout.on('data', (chunk: Buffer) => {\n terminalBuffer.write(chunk);\n this.store.notify();\n });\n }\n if (cp.stderr) {\n cp.stderr.on('data', (chunk: Buffer) => {\n terminalBuffer.write(chunk);\n this.store.notify();\n });\n }\n\n // Wait for process to complete\n const queue = new Queue();\n if (cp.stdout) {\n queue.defer(oo.bind(null, cp.stdout, ['error', 'end', 'close']));\n }\n if (cp.stderr) {\n queue.defer(oo.bind(null, cp.stderr, ['error', 'end', 'close']));\n }\n queue.defer(spawn.worker.bind(null, cp, csOptions));\n queue.await((err?: SpawnError) => {\n const res = (err ? err : {}) as SpawnResult;\n res.stdout = null; // Not collecting raw output in inherit mode\n res.stderr = null;\n res.output = [null, null, null];\n this.store.updateProcess(id, { state: err ? 'error' : 'success' });\n\n this.onProcessComplete();\n err ? callback(err) : callback(null, res);\n });\n } else {\n // Non-inherit mode: collect output but don't display in UI\n const cp = crossSpawn(command, args, csOptions);\n const outputs = { stdout: null as ReturnType<typeof concatWritable> | null, stderr: null as ReturnType<typeof concatWritable> | null };\n\n const queue = new Queue();\n if (cp.stdout) {\n outputs.stdout = concatWritable((output) => {\n (outputs.stdout as unknown as { output: string }).output = output.toString(encoding || 'utf8');\n });\n queue.defer(oo.bind(null, cp.stdout.pipe(outputs.stdout), ['error', 'end', 'close', 'finish']));\n }\n if (cp.stderr) {\n outputs.stderr = concatWritable((output) => {\n (outputs.stderr as unknown as { output: string }).output = output.toString(encoding || 'utf8');\n });\n queue.defer(oo.bind(null, cp.stderr.pipe(outputs.stderr), ['error', 'end', 'close', 'finish']));\n }\n queue.defer(spawn.worker.bind(null, cp, csOptions));\n queue.await((err?: SpawnError) => {\n const res = (err ? err : {}) as SpawnResult;\n res.stdout = outputs.stdout ? (outputs.stdout as unknown as { output: string }).output : null;\n res.stderr = outputs.stderr ? (outputs.stderr as unknown as { output: string }).output : null;\n res.output = [res.stdout, res.stderr, null];\n err ? callback(err) : callback(null, res);\n });\n }\n }\n\n close(): void {\n if (this.closed) return;\n this.closed = true;\n this.cleanup();\n }\n\n waitAndClose(callback?: () => void): void {\n if (this.closed) {\n callback?.();\n return;\n }\n\n if (callback) this.waitCallbacks.push(callback);\n\n if (this.runningCount === 0) {\n if (this.isInteractive) {\n // In interactive mode, wait for user to quit (press 'q')\n const unsubscribe = this.store.subscribe(() => {\n if (this.store.getShouldExit()) {\n unsubscribe();\n this.closeAndCallWaitCallbacks();\n }\n });\n } else {\n this.closeAndCallWaitCallbacks();\n }\n }\n // If runningCount > 0, will close when it hits 0 in onProcessComplete\n }\n\n private onProcessComplete(): void {\n this.runningCount--;\n if (this.runningCount === 0 && this.waitCallbacks.length > 0) {\n if (this.isInteractive) {\n // In interactive mode, wait for user to quit (press 'q')\n const unsubscribe = this.store.subscribe(() => {\n if (this.store.getShouldExit()) {\n unsubscribe();\n this.closeAndCallWaitCallbacks();\n }\n });\n } else {\n this.closeAndCallWaitCallbacks();\n }\n }\n }\n\n private closeAndCallWaitCallbacks(): void {\n if (this.closed) return;\n this.closed = true;\n this.cleanup(() => {\n for (const cb of this.waitCallbacks) cb();\n this.waitCallbacks = [];\n });\n }\n\n private cleanup(onComplete?: () => void): void {\n // Signal exit to React component\n this.store.signalExit(() => {\n this.store.reset();\n process.stdout.write('\\x1b[?25h'); // show cursor\n });\n\n // Wait for Ink to finish\n if (this.inkApp) {\n this.inkApp\n .waitUntilExit()\n .then(() => {\n const cb = this.store.getExitCallback();\n cb?.();\n onComplete?.();\n })\n .catch(() => {\n const cb = this.store.getExitCallback();\n cb?.();\n onComplete?.();\n });\n this.inkApp = null;\n } else {\n onComplete?.();\n }\n }\n}\n\nexport function createSession(options: SessionOptions = {}): Session {\n return new SessionImpl(options);\n}\n"],"names":["spawn","crossSpawn","crypto","render","oo","Queue","App","DEFAULT_MAX_FPS","concatWritable","formatArguments","TerminalBuffer","ProcessStore","SessionImpl","command","args","spawnOptions","options","callback","closed","Error","encoding","stdio","csOptions","inkApp","cp","worker","err","res","stdout","stderr","output","runningCount","id","randomUUID","terminalBuffer","terminalWidth","store","addProcess","title","concat","join","state","lines","group","expanded","on","chunk","write","notify","queue","defer","bind","await","updateProcess","onProcessComplete","outputs","toString","pipe","close","cleanup","waitAndClose","waitCallbacks","push","isInteractive","unsubscribe","subscribe","getShouldExit","closeAndCallWaitCallbacks","length","cb","onComplete","signalExit","reset","process","waitUntilExit","then","getExitCallback","catch","maxFps","interactive","createSession"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/session.tsx"],"sourcesContent":["import spawn, { crossSpawn, type SpawnResult } from 'cross-spawn-cb';\nimport crypto from 'crypto';\nimport { render } from 'ink';\nimport oo from 'on-one';\nimport Queue from 'queue-cb';\n\nimport App from './components/App.ts';\nimport { DEFAULT_MAX_FPS } from './constants.ts';\nimport concatWritable from './lib/concatWritable.ts';\nimport formatArguments from './lib/formatArguments.ts';\nimport { TerminalBuffer } from './lib/TerminalBuffer.ts';\nimport { ProcessStore } from './state/processStore.ts';\nimport type { ProcessOptions, SessionOptions, SpawnError, SpawnOptions, TerminalCallback } from './types.ts';\n\nexport interface Session {\n spawn(command: string, args: string[], spawnOptions: SpawnOptions, options: ProcessOptions, callback: TerminalCallback): void;\n close(): void;\n waitAndClose(callback?: () => void): void;\n}\n\nclass SessionImpl implements Session {\n private store: ProcessStore;\n private inkApp: ReturnType<typeof render> | null = null;\n private runningCount = 0;\n private closed = false;\n private waitCallbacks: (() => void)[] = [];\n private isInteractive: boolean;\n private terminalWidth: number;\n\n constructor(options: SessionOptions = {}) {\n this.store = new ProcessStore(options);\n // Use a very wide buffer to prevent line wrapping in xterm\n // Actual display truncation is handled by Ink components\n this.terminalWidth = 10000;\n\n // Only render Ink when stdout is a real terminal\n // When piped (e.g., nested spawn-term), skip Ink to avoid cursor positioning artifacts\n this.inkApp = render(<App store={this.store} />, { maxFps: DEFAULT_MAX_FPS });\n\n // Interactive mode requires a real TTY for user input (e.g., press 'q' to quit)\n // Without a TTY, there's no way to receive keyboard input, so auto-exit when complete\n this.isInteractive = process.stdout.isTTY ? (options.interactive ?? false) : false;\n }\n\n spawn(command: string, args: string[], spawnOptions: SpawnOptions, options: ProcessOptions, callback: TerminalCallback): void {\n if (this.closed) {\n throw new Error('Session is closed');\n }\n\n const { encoding, stdio, ...csOptions } = spawnOptions;\n\n if (stdio === 'inherit') {\n // When Ink is not rendering (stdout not a TTY), pass output directly to stdout\n if (!this.inkApp) {\n const cp = crossSpawn(command, args, { ...csOptions, stdio: 'inherit' });\n spawn.worker(cp, csOptions, (err?: SpawnError) => {\n const res = (err ? err : {}) as SpawnResult;\n res.stdout = null;\n res.stderr = null;\n res.output = [null, null, null];\n err ? callback(err) : callback(null, res);\n });\n return;\n }\n\n this.runningCount++;\n const id = crypto.randomUUID();\n\n // Create terminal buffer for ANSI sequence interpretation\n const terminalBuffer = new TerminalBuffer(this.terminalWidth);\n\n this.store.addProcess({\n id,\n title: [command].concat(formatArguments(args)).join(' '),\n state: 'running',\n lines: [],\n terminalBuffer,\n group: options.group,\n expanded: options.expanded,\n });\n\n const cp = crossSpawn(command, args, csOptions);\n\n // Pipe stdout and stderr directly to terminal buffer\n // Both streams go to the same buffer to maintain correct ordering\n if (cp.stdout) {\n cp.stdout.on('data', (chunk: Buffer) => {\n terminalBuffer.write(chunk);\n this.store.notify();\n });\n }\n if (cp.stderr) {\n cp.stderr.on('data', (chunk: Buffer) => {\n terminalBuffer.write(chunk);\n this.store.notify();\n });\n }\n\n // Wait for process to complete\n const queue = new Queue();\n if (cp.stdout) {\n queue.defer(oo.bind(null, cp.stdout, ['error', 'end', 'close']));\n }\n if (cp.stderr) {\n queue.defer(oo.bind(null, cp.stderr, ['error', 'end', 'close']));\n }\n queue.defer(spawn.worker.bind(null, cp, csOptions));\n queue.await((err?: SpawnError) => {\n const res = (err ? err : {}) as SpawnResult;\n res.stdout = null; // Not collecting raw output in inherit mode\n res.stderr = null;\n res.output = [null, null, null];\n this.store.updateProcess(id, { state: err ? 'error' : 'success' });\n\n this.onProcessComplete();\n err ? callback(err) : callback(null, res);\n });\n } else {\n // Non-inherit mode: collect output but don't display in UI\n const cp = crossSpawn(command, args, csOptions);\n const outputs = { stdout: null as ReturnType<typeof concatWritable> | null, stderr: null as ReturnType<typeof concatWritable> | null };\n\n const queue = new Queue();\n if (cp.stdout) {\n outputs.stdout = concatWritable((output) => {\n (outputs.stdout as unknown as { output: string }).output = output.toString(encoding || 'utf8');\n });\n queue.defer(oo.bind(null, cp.stdout.pipe(outputs.stdout), ['error', 'end', 'close', 'finish']));\n }\n if (cp.stderr) {\n outputs.stderr = concatWritable((output) => {\n (outputs.stderr as unknown as { output: string }).output = output.toString(encoding || 'utf8');\n });\n queue.defer(oo.bind(null, cp.stderr.pipe(outputs.stderr), ['error', 'end', 'close', 'finish']));\n }\n queue.defer(spawn.worker.bind(null, cp, csOptions));\n queue.await((err?: SpawnError) => {\n const res = (err ? err : {}) as SpawnResult;\n res.stdout = outputs.stdout ? (outputs.stdout as unknown as { output: string }).output : null;\n res.stderr = outputs.stderr ? (outputs.stderr as unknown as { output: string }).output : null;\n res.output = [res.stdout, res.stderr, null];\n err ? callback(err) : callback(null, res);\n });\n }\n }\n\n close(): void {\n if (this.closed) return;\n this.closed = true;\n this.cleanup();\n }\n\n waitAndClose(callback?: () => void): void {\n if (this.closed) {\n callback?.();\n return;\n }\n\n if (callback) this.waitCallbacks.push(callback);\n\n if (this.runningCount === 0) {\n if (this.isInteractive) {\n // In interactive mode, wait for user to quit (press 'q')\n const unsubscribe = this.store.subscribe(() => {\n if (this.store.getShouldExit()) {\n unsubscribe();\n this.closeAndCallWaitCallbacks();\n }\n });\n } else {\n this.closeAndCallWaitCallbacks();\n }\n }\n // If runningCount > 0, will close when it hits 0 in onProcessComplete\n }\n\n private onProcessComplete(): void {\n this.runningCount--;\n if (this.runningCount === 0 && this.waitCallbacks.length > 0) {\n if (this.isInteractive) {\n // In interactive mode, wait for user to quit (press 'q')\n const unsubscribe = this.store.subscribe(() => {\n if (this.store.getShouldExit()) {\n unsubscribe();\n this.closeAndCallWaitCallbacks();\n }\n });\n } else {\n this.closeAndCallWaitCallbacks();\n }\n }\n }\n\n private closeAndCallWaitCallbacks(): void {\n if (this.closed) return;\n this.closed = true;\n this.cleanup(() => {\n for (const cb of this.waitCallbacks) cb();\n this.waitCallbacks = [];\n });\n }\n\n private cleanup(onComplete?: () => void): void {\n // Signal exit to React component\n this.store.signalExit(() => {\n this.store.reset();\n process.stdout.write('\\x1b[?25h'); // show cursor\n });\n\n // Wait for Ink to finish\n if (this.inkApp) {\n this.inkApp\n .waitUntilExit()\n .then(() => {\n const cb = this.store.getExitCallback();\n cb?.();\n onComplete?.();\n })\n .catch(() => {\n const cb = this.store.getExitCallback();\n cb?.();\n onComplete?.();\n });\n this.inkApp = null;\n } else {\n onComplete?.();\n }\n }\n}\n\nexport function createSession(options: SessionOptions = {}): Session {\n return new SessionImpl(options);\n}\n"],"names":["spawn","crossSpawn","crypto","render","oo","Queue","App","DEFAULT_MAX_FPS","concatWritable","formatArguments","TerminalBuffer","ProcessStore","SessionImpl","command","args","spawnOptions","options","callback","closed","Error","encoding","stdio","csOptions","inkApp","cp","worker","err","res","stdout","stderr","output","runningCount","id","randomUUID","terminalBuffer","terminalWidth","store","addProcess","title","concat","join","state","lines","group","expanded","on","chunk","write","notify","queue","defer","bind","await","updateProcess","onProcessComplete","outputs","toString","pipe","close","cleanup","waitAndClose","waitCallbacks","push","isInteractive","unsubscribe","subscribe","getShouldExit","closeAndCallWaitCallbacks","length","cb","onComplete","signalExit","reset","process","waitUntilExit","then","getExitCallback","catch","maxFps","isTTY","interactive","createSession"],"mappings":";AAAA,OAAOA,SAASC,UAAU,QAA0B,iBAAiB;AACrE,OAAOC,YAAY,SAAS;AAC5B,SAASC,MAAM,QAAQ,MAAM;AAC7B,OAAOC,QAAQ,SAAS;AACxB,OAAOC,WAAW,WAAW;AAE7B,OAAOC,SAAS,sBAAsB;AACtC,SAASC,eAAe,QAAQ,iBAAiB;AACjD,OAAOC,oBAAoB,0BAA0B;AACrD,OAAOC,qBAAqB,2BAA2B;AACvD,SAASC,cAAc,QAAQ,0BAA0B;AACzD,SAASC,YAAY,QAAQ,0BAA0B;AASvD,IAAA,AAAMC,cAAN,MAAMA;IAwBJZ,MAAMa,OAAe,EAAEC,IAAc,EAAEC,YAA0B,EAAEC,OAAuB,EAAEC,QAA0B,EAAQ;QAC5H,IAAI,IAAI,CAACC,MAAM,EAAE;YACf,MAAM,IAAIC,MAAM;QAClB;QAEA,MAAM,EAAEC,QAAQ,EAAEC,KAAK,EAAE,GAAGC,WAAW,GAAGP;QAE1C,IAAIM,UAAU,WAAW;YACvB,+EAA+E;YAC/E,IAAI,CAAC,IAAI,CAACE,MAAM,EAAE;gBAChB,MAAMC,KAAKvB,WAAWY,SAASC,MAAM;oBAAE,GAAGQ,SAAS;oBAAED,OAAO;gBAAU;gBACtErB,MAAMyB,MAAM,CAACD,IAAIF,WAAW,CAACI;oBAC3B,MAAMC,MAAOD,MAAMA,MAAM,CAAC;oBAC1BC,IAAIC,MAAM,GAAG;oBACbD,IAAIE,MAAM,GAAG;oBACbF,IAAIG,MAAM,GAAG;wBAAC;wBAAM;wBAAM;qBAAK;oBAC/BJ,MAAMT,SAASS,OAAOT,SAAS,MAAMU;gBACvC;gBACA;YACF;YAEA,IAAI,CAACI,YAAY;YACjB,MAAMC,KAAK9B,OAAO+B,UAAU;YAE5B,0DAA0D;YAC1D,MAAMC,iBAAiB,IAAIxB,eAAe,IAAI,CAACyB,aAAa;YAE5D,IAAI,CAACC,KAAK,CAACC,UAAU,CAAC;gBACpBL;gBACAM,OAAO;oBAACzB;iBAAQ,CAAC0B,MAAM,CAAC9B,gBAAgBK,OAAO0B,IAAI,CAAC;gBACpDC,OAAO;gBACPC,OAAO,EAAE;gBACTR;gBACAS,OAAO3B,QAAQ2B,KAAK;gBACpBC,UAAU5B,QAAQ4B,QAAQ;YAC5B;YAEA,MAAMpB,KAAKvB,WAAWY,SAASC,MAAMQ;YAErC,qDAAqD;YACrD,kEAAkE;YAClE,IAAIE,GAAGI,MAAM,EAAE;gBACbJ,GAAGI,MAAM,CAACiB,EAAE,CAAC,QAAQ,CAACC;oBACpBZ,eAAea,KAAK,CAACD;oBACrB,IAAI,CAACV,KAAK,CAACY,MAAM;gBACnB;YACF;YACA,IAAIxB,GAAGK,MAAM,EAAE;gBACbL,GAAGK,MAAM,CAACgB,EAAE,CAAC,QAAQ,CAACC;oBACpBZ,eAAea,KAAK,CAACD;oBACrB,IAAI,CAACV,KAAK,CAACY,MAAM;gBACnB;YACF;YAEA,+BAA+B;YAC/B,MAAMC,QAAQ,IAAI5C;YAClB,IAAImB,GAAGI,MAAM,EAAE;gBACbqB,MAAMC,KAAK,CAAC9C,GAAG+C,IAAI,CAAC,MAAM3B,GAAGI,MAAM,EAAE;oBAAC;oBAAS;oBAAO;iBAAQ;YAChE;YACA,IAAIJ,GAAGK,MAAM,EAAE;gBACboB,MAAMC,KAAK,CAAC9C,GAAG+C,IAAI,CAAC,MAAM3B,GAAGK,MAAM,EAAE;oBAAC;oBAAS;oBAAO;iBAAQ;YAChE;YACAoB,MAAMC,KAAK,CAAClD,MAAMyB,MAAM,CAAC0B,IAAI,CAAC,MAAM3B,IAAIF;YACxC2B,MAAMG,KAAK,CAAC,CAAC1B;gBACX,MAAMC,MAAOD,MAAMA,MAAM,CAAC;gBAC1BC,IAAIC,MAAM,GAAG,MAAM,4CAA4C;gBAC/DD,IAAIE,MAAM,GAAG;gBACbF,IAAIG,MAAM,GAAG;oBAAC;oBAAM;oBAAM;iBAAK;gBAC/B,IAAI,CAACM,KAAK,CAACiB,aAAa,CAACrB,IAAI;oBAAES,OAAOf,MAAM,UAAU;gBAAU;gBAEhE,IAAI,CAAC4B,iBAAiB;gBACtB5B,MAAMT,SAASS,OAAOT,SAAS,MAAMU;YACvC;QACF,OAAO;YACL,2DAA2D;YAC3D,MAAMH,KAAKvB,WAAWY,SAASC,MAAMQ;YACrC,MAAMiC,UAAU;gBAAE3B,QAAQ;gBAAkDC,QAAQ;YAAiD;YAErI,MAAMoB,QAAQ,IAAI5C;YAClB,IAAImB,GAAGI,MAAM,EAAE;gBACb2B,QAAQ3B,MAAM,GAAGpB,eAAe,CAACsB;oBAC9ByB,QAAQ3B,MAAM,CAAmCE,MAAM,GAAGA,OAAO0B,QAAQ,CAACpC,YAAY;gBACzF;gBACA6B,MAAMC,KAAK,CAAC9C,GAAG+C,IAAI,CAAC,MAAM3B,GAAGI,MAAM,CAAC6B,IAAI,CAACF,QAAQ3B,MAAM,GAAG;oBAAC;oBAAS;oBAAO;oBAAS;iBAAS;YAC/F;YACA,IAAIJ,GAAGK,MAAM,EAAE;gBACb0B,QAAQ1B,MAAM,GAAGrB,eAAe,CAACsB;oBAC9ByB,QAAQ1B,MAAM,CAAmCC,MAAM,GAAGA,OAAO0B,QAAQ,CAACpC,YAAY;gBACzF;gBACA6B,MAAMC,KAAK,CAAC9C,GAAG+C,IAAI,CAAC,MAAM3B,GAAGK,MAAM,CAAC4B,IAAI,CAACF,QAAQ1B,MAAM,GAAG;oBAAC;oBAAS;oBAAO;oBAAS;iBAAS;YAC/F;YACAoB,MAAMC,KAAK,CAAClD,MAAMyB,MAAM,CAAC0B,IAAI,CAAC,MAAM3B,IAAIF;YACxC2B,MAAMG,KAAK,CAAC,CAAC1B;gBACX,MAAMC,MAAOD,MAAMA,MAAM,CAAC;gBAC1BC,IAAIC,MAAM,GAAG2B,QAAQ3B,MAAM,GAAG,AAAC2B,QAAQ3B,MAAM,CAAmCE,MAAM,GAAG;gBACzFH,IAAIE,MAAM,GAAG0B,QAAQ1B,MAAM,GAAG,AAAC0B,QAAQ1B,MAAM,CAAmCC,MAAM,GAAG;gBACzFH,IAAIG,MAAM,GAAG;oBAACH,IAAIC,MAAM;oBAAED,IAAIE,MAAM;oBAAE;iBAAK;gBAC3CH,MAAMT,SAASS,OAAOT,SAAS,MAAMU;YACvC;QACF;IACF;IAEA+B,QAAc;QACZ,IAAI,IAAI,CAACxC,MAAM,EAAE;QACjB,IAAI,CAACA,MAAM,GAAG;QACd,IAAI,CAACyC,OAAO;IACd;IAEAC,aAAa3C,QAAqB,EAAQ;QACxC,IAAI,IAAI,CAACC,MAAM,EAAE;YACfD,qBAAAA,+BAAAA;YACA;QACF;QAEA,IAAIA,UAAU,IAAI,CAAC4C,aAAa,CAACC,IAAI,CAAC7C;QAEtC,IAAI,IAAI,CAACc,YAAY,KAAK,GAAG;YAC3B,IAAI,IAAI,CAACgC,aAAa,EAAE;gBACtB,yDAAyD;gBACzD,MAAMC,cAAc,IAAI,CAAC5B,KAAK,CAAC6B,SAAS,CAAC;oBACvC,IAAI,IAAI,CAAC7B,KAAK,CAAC8B,aAAa,IAAI;wBAC9BF;wBACA,IAAI,CAACG,yBAAyB;oBAChC;gBACF;YACF,OAAO;gBACL,IAAI,CAACA,yBAAyB;YAChC;QACF;IACA,sEAAsE;IACxE;IAEQb,oBAA0B;QAChC,IAAI,CAACvB,YAAY;QACjB,IAAI,IAAI,CAACA,YAAY,KAAK,KAAK,IAAI,CAAC8B,aAAa,CAACO,MAAM,GAAG,GAAG;YAC5D,IAAI,IAAI,CAACL,aAAa,EAAE;gBACtB,yDAAyD;gBACzD,MAAMC,cAAc,IAAI,CAAC5B,KAAK,CAAC6B,SAAS,CAAC;oBACvC,IAAI,IAAI,CAAC7B,KAAK,CAAC8B,aAAa,IAAI;wBAC9BF;wBACA,IAAI,CAACG,yBAAyB;oBAChC;gBACF;YACF,OAAO;gBACL,IAAI,CAACA,yBAAyB;YAChC;QACF;IACF;IAEQA,4BAAkC;QACxC,IAAI,IAAI,CAACjD,MAAM,EAAE;QACjB,IAAI,CAACA,MAAM,GAAG;QACd,IAAI,CAACyC,OAAO,CAAC;YACX,KAAK,MAAMU,MAAM,IAAI,CAACR,aAAa,CAAEQ;YACrC,IAAI,CAACR,aAAa,GAAG,EAAE;QACzB;IACF;IAEQF,QAAQW,UAAuB,EAAQ;QAC7C,iCAAiC;QACjC,IAAI,CAAClC,KAAK,CAACmC,UAAU,CAAC;YACpB,IAAI,CAACnC,KAAK,CAACoC,KAAK;YAChBC,QAAQ7C,MAAM,CAACmB,KAAK,CAAC,cAAc,cAAc;QACnD;QAEA,yBAAyB;QACzB,IAAI,IAAI,CAACxB,MAAM,EAAE;YACf,IAAI,CAACA,MAAM,CACRmD,aAAa,GACbC,IAAI,CAAC;gBACJ,MAAMN,KAAK,IAAI,CAACjC,KAAK,CAACwC,eAAe;gBACrCP,eAAAA,yBAAAA;gBACAC,uBAAAA,iCAAAA;YACF,GACCO,KAAK,CAAC;gBACL,MAAMR,KAAK,IAAI,CAACjC,KAAK,CAACwC,eAAe;gBACrCP,eAAAA,yBAAAA;gBACAC,uBAAAA,iCAAAA;YACF;YACF,IAAI,CAAC/C,MAAM,GAAG;QAChB,OAAO;YACL+C,uBAAAA,iCAAAA;QACF;IACF;IAtMA,YAAYtD,UAA0B,CAAC,CAAC,CAAE;YAYKA;aAnBvCO,SAA2C;aAC3CQ,eAAe;aACfb,SAAS;aACT2C,gBAAgC,EAAE;QAKxC,IAAI,CAACzB,KAAK,GAAG,IAAIzB,aAAaK;QAC9B,2DAA2D;QAC3D,yDAAyD;QACzD,IAAI,CAACmB,aAAa,GAAG;QAErB,iDAAiD;QACjD,uFAAuF;QACvF,IAAI,CAACZ,MAAM,GAAGpB,qBAAO,KAACG;YAAI8B,OAAO,IAAI,CAACA,KAAK;YAAM;YAAE0C,QAAQvE;QAAgB;QAE3E,gFAAgF;QAChF,sFAAsF;QACtF,IAAI,CAACwD,aAAa,GAAGU,QAAQ7C,MAAM,CAACmD,KAAK,IAAI/D,uBAAAA,QAAQgE,WAAW,cAAnBhE,kCAAAA,uBAAuB,QAAS;IAC/E;AA0LF;AAEA,OAAO,SAASiE,cAAcjE,UAA0B,CAAC,CAAC;IACxD,OAAO,IAAIJ,YAAYI;AACzB"}
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrapper around terminal-model's StreamingTerminal that provides a virtual terminal buffer.
|
|
3
|
+
* Interprets ANSI escape sequences (cursor movement, line clearing, etc.) to produce
|
|
4
|
+
* the actual rendered output rather than raw intermediate states.
|
|
5
|
+
*
|
|
6
|
+
* This implementation preserves whitespace and blank lines by NOT calling trimStart(),
|
|
7
|
+
* which was the bug in the previous xterm-based implementation.
|
|
8
|
+
*/
|
|
1
9
|
export declare class TerminalBuffer {
|
|
2
10
|
private terminal;
|
|
3
|
-
|
|
11
|
+
private allLines;
|
|
12
|
+
constructor(_cols: number, _scrollback?: number);
|
|
4
13
|
/**
|
|
5
14
|
* Write raw data to the terminal buffer.
|
|
6
15
|
* The terminal interprets all ANSI sequences automatically.
|
|
@@ -8,12 +17,16 @@ export declare class TerminalBuffer {
|
|
|
8
17
|
write(data: string | Buffer): void;
|
|
9
18
|
/**
|
|
10
19
|
* Resize the terminal width.
|
|
20
|
+
* terminal-model doesn't use column constraints, so this is a no-op for compatibility.
|
|
11
21
|
*/
|
|
12
|
-
resize(
|
|
22
|
+
resize(_cols: number): void;
|
|
13
23
|
/**
|
|
14
24
|
* Extract the rendered lines from the terminal buffer.
|
|
15
25
|
* This returns the actual visible content after all ANSI sequences
|
|
16
26
|
* have been processed, with color codes preserved.
|
|
27
|
+
*
|
|
28
|
+
* CRITICAL: Unlike the xterm implementation, we do NOT call trimStart(),
|
|
29
|
+
* which preserves legitimate indentation and blank lines.
|
|
17
30
|
*/
|
|
18
31
|
getLines(): string[];
|
|
19
32
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spawn-term",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.1",
|
|
4
4
|
"description": "Formats spawn with for terminal grouping",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"spawn",
|
|
@@ -42,11 +42,11 @@
|
|
|
42
42
|
"version": "tsds version"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@xterm/headless": "^6.0.0",
|
|
46
45
|
"cross-spawn-cb": "^3.0.0",
|
|
47
46
|
"install-module-linked": "^1.3.16",
|
|
48
47
|
"on-one": "^1.0.0",
|
|
49
|
-
"queue-cb": "^1.0.0"
|
|
48
|
+
"queue-cb": "^1.0.0",
|
|
49
|
+
"terminal-model": "^0.1.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@types/mocha": "*",
|