visual-exec 1.0.0 → 1.0.2
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/visual-exec.d.ts +21 -0
- package/dist/visual-exec.js +73 -2
- package/package.json +3 -3
package/dist/visual-exec.d.ts
CHANGED
|
@@ -126,6 +126,27 @@ export declare const parsers: {
|
|
|
126
126
|
jsonLines: typeof jsonLinesParser;
|
|
127
127
|
keyValue: typeof keyValueParser;
|
|
128
128
|
};
|
|
129
|
+
export interface SanitizeOptions {
|
|
130
|
+
/** "space" collapses tabs (single-line contexts), "keep" preserves them (dumps) */
|
|
131
|
+
tabs?: "space" | "keep";
|
|
132
|
+
/** what to do with a lone \r - "drop", or "newline" to keep progress redraws readable */
|
|
133
|
+
cr?: "drop" | "newline";
|
|
134
|
+
/** append an SGR reset when the text emitted colour, so it cannot bleed into later output */
|
|
135
|
+
resetAtEnd?: boolean;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Strip terminal control sequences from child output, keeping colour. Never use this for
|
|
139
|
+
* output written to a file or handed to a callback - those should get the bytes untouched.
|
|
140
|
+
*/
|
|
141
|
+
export declare function sanitizeText(text: string, options?: SanitizeOptions): string;
|
|
142
|
+
/** Profile for text squeezed into a single progress line. */
|
|
143
|
+
export declare function sanitizeForDisplay(text: string): string;
|
|
144
|
+
/**
|
|
145
|
+
* Profile for a multi-line transcript written into scrollback: tabs are real formatting,
|
|
146
|
+
* carriage returns become line breaks so progress redraws stay readable, and a trailing
|
|
147
|
+
* reset stops an unterminated child colour bleeding into later output.
|
|
148
|
+
*/
|
|
149
|
+
export declare function sanitizeForOutput(text: string): string;
|
|
129
150
|
export declare class VisualExec {
|
|
130
151
|
private _title;
|
|
131
152
|
private _logLabel;
|
package/dist/visual-exec.js
CHANGED
|
@@ -70,6 +70,70 @@ export const parsers = {
|
|
|
70
70
|
jsonLines: jsonLinesParser,
|
|
71
71
|
keyValue: keyValueParser
|
|
72
72
|
};
|
|
73
|
+
//
|
|
74
|
+
// Child output is captured from a pipe and rendered back into this process's terminal. It
|
|
75
|
+
// can carry the child's own control sequences - a nested fyn renders its own visual logger
|
|
76
|
+
// and emits eraseLine/cursorUp runs. Those are zero-width to string-width, so line
|
|
77
|
+
// accounting stays correct while the terminal still *executes* them, walking the cursor up
|
|
78
|
+
// and erasing committed output.
|
|
79
|
+
//
|
|
80
|
+
// Keep SGR (colour); drop anything that can move the cursor, erase, scroll, switch charset,
|
|
81
|
+
// set the window title, or write the clipboard.
|
|
82
|
+
//
|
|
83
|
+
// The escape grammar, in the order the alternation must test it:
|
|
84
|
+
// CSI ESC [ params intermediates final
|
|
85
|
+
// OSC/DCS/PM/APC/SOS ESC ] P ^ _ X ... terminated by BEL or ST (or truncated at end)
|
|
86
|
+
// nF ESC intermediates(0x20-0x2F)+ final e.g. ESC(0, ESC#8, ESC%G
|
|
87
|
+
// Fp/Fe/Fs ESC single(0x30-0x7E) e.g. ESC7, ESCc, ESC M
|
|
88
|
+
//
|
|
89
|
+
const ESCAPE_SEQUENCE = new RegExp("\u001B(?:" +
|
|
90
|
+
"\\[[0-?]*[ -/]*[@-~]" +
|
|
91
|
+
"|[\\]P^_X][\\s\\S]*?(?:\\u0007|\u001B\\\\|$)" +
|
|
92
|
+
"|[ -/]+[0-~]" +
|
|
93
|
+
"|[0-~]" +
|
|
94
|
+
")", "g");
|
|
95
|
+
const SGR_SEQUENCE = new RegExp("^\u001B\\[[0-?]*[ -/]*m$");
|
|
96
|
+
const HAS_SGR = new RegExp("\u001B\\[[0-?]*[ -/]*m");
|
|
97
|
+
const SGR_RESET = "\u001B[0m";
|
|
98
|
+
// 8-bit C1 controls: escapes without the ESC byte. 0x9B is an 8-bit CSI and 0x9D/0x90 are
|
|
99
|
+
// 8-bit OSC/DCS, so consume their payload too - dropping only the introducer would leave the
|
|
100
|
+
// parameters behind as inert but visible junk. Anything left is stripped bare.
|
|
101
|
+
const C1_SEQUENCE = /[\u009B][0-?]*[ -/]*[@-~]|[\u009D\u0090\u009E\u009F][\s\S]*?(?:\u0007|\u009C|$)/g;
|
|
102
|
+
const C1_CONTROL = /[\u0080-\u009F]/g;
|
|
103
|
+
// C0 controls that move the cursor or clear the screen. \n and \t are left to the profile,
|
|
104
|
+
// and \u001B is left alone - the only ones remaining are inside kept SGR sequences.
|
|
105
|
+
// eslint-disable-next-line no-control-regex
|
|
106
|
+
const UNSAFE_CONTROL = /[\u0000-\u0008\u000B-\u001A\u001C-\u001F\u007F]/g;
|
|
107
|
+
/**
|
|
108
|
+
* Strip terminal control sequences from child output, keeping colour. Never use this for
|
|
109
|
+
* output written to a file or handed to a callback - those should get the bytes untouched.
|
|
110
|
+
*/
|
|
111
|
+
export function sanitizeText(text, options = {}) {
|
|
112
|
+
const { tabs = "space", cr = "drop", resetAtEnd = false } = options;
|
|
113
|
+
let out = text.replace(ESCAPE_SEQUENCE, seq => (SGR_SEQUENCE.test(seq) ? seq : ""));
|
|
114
|
+
// must run before UNSAFE_CONTROL, which would otherwise swallow the \r
|
|
115
|
+
if (cr === "newline") {
|
|
116
|
+
out = out.replace(/\r\n?/g, "\n");
|
|
117
|
+
}
|
|
118
|
+
out = out
|
|
119
|
+
.replace(/\t/g, tabs === "keep" ? "\t" : " ")
|
|
120
|
+
.replace(C1_SEQUENCE, "")
|
|
121
|
+
.replace(C1_CONTROL, "")
|
|
122
|
+
.replace(UNSAFE_CONTROL, "");
|
|
123
|
+
return resetAtEnd && HAS_SGR.test(out) ? out + SGR_RESET : out;
|
|
124
|
+
}
|
|
125
|
+
/** Profile for text squeezed into a single progress line. */
|
|
126
|
+
export function sanitizeForDisplay(text) {
|
|
127
|
+
return sanitizeText(text, { tabs: "space", cr: "drop" });
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Profile for a multi-line transcript written into scrollback: tabs are real formatting,
|
|
131
|
+
* carriage returns become line breaks so progress redraws stay readable, and a trailing
|
|
132
|
+
* reset stops an unterminated child colour bleeding into later output.
|
|
133
|
+
*/
|
|
134
|
+
export function sanitizeForOutput(text) {
|
|
135
|
+
return sanitizeText(text, { tabs: "keep", cr: "newline", resetAtEnd: true });
|
|
136
|
+
}
|
|
73
137
|
export class VisualExec {
|
|
74
138
|
constructor(options) {
|
|
75
139
|
this._aborted = false;
|
|
@@ -157,7 +221,9 @@ export class VisualExec {
|
|
|
157
221
|
this._onProgress(progress);
|
|
158
222
|
}
|
|
159
223
|
_updateDigest(item, buf) {
|
|
160
|
-
|
|
224
|
+
// sanitize at the boundary so every downstream use - lines, item.buf, the item message -
|
|
225
|
+
// is already inert. Raw output still reaches onOutput/outputFile via _createDataHandler.
|
|
226
|
+
const newBuf = item.buf + sanitizeForDisplay(buf);
|
|
161
227
|
const lines = newBuf
|
|
162
228
|
.split("\n")
|
|
163
229
|
.map(x => x && x.trim())
|
|
@@ -393,7 +459,12 @@ export class VisualExec {
|
|
|
393
459
|
this._logger[level](`${chalk.green("No output")} from ${this._outputLabel}`);
|
|
394
460
|
return;
|
|
395
461
|
}
|
|
396
|
-
|
|
462
|
+
//
|
|
463
|
+
// The child's bytes end up on this terminal and in the saved log data, so scrub the
|
|
464
|
+
// control sequences out first - an error report that erases the lines above it is worse
|
|
465
|
+
// than useless. sanitizeForOutput runs before colorize, which only adds SGR.
|
|
466
|
+
//
|
|
467
|
+
const colorize = (t) => sanitizeForOutput(t).replace(/ERR!/g, chalk.red("ERR!"));
|
|
397
468
|
const logs = [chalk.green(">>>"), `Start of output from ${this._outputLabel} ===`];
|
|
398
469
|
if (output.stdout) {
|
|
399
470
|
logs.push(`\n${colorize(output.stdout)}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "visual-exec",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"homepage": "https://github.com/jchip/fynjs/tree/main/packages/visual-exec",
|
|
5
5
|
"description": "Visual shell exec with spinners and progress display",
|
|
6
6
|
"type": "module",
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"ci-info": "^4.4.0",
|
|
50
50
|
"has-ansi": "^6.0.2",
|
|
51
51
|
"strip-ansi": "^7.2.0",
|
|
52
|
-
"visual-logger": "^2.0.
|
|
53
|
-
"xsh": "^1.0.
|
|
52
|
+
"visual-logger": "^2.0.1",
|
|
53
|
+
"xsh": "^1.0.1"
|
|
54
54
|
},
|
|
55
55
|
"sideEffects": false
|
|
56
56
|
}
|