typescript-virtual-container 1.5.6 → 1.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -20
- package/dist/.tsbuildinfo +1 -1
- package/dist/SSHMimic/index.d.ts +5 -1
- package/dist/SSHMimic/index.js +27 -3
- package/dist/SSHMimic/prompt.d.ts +2 -1
- package/dist/SSHMimic/prompt.js +27 -5
- package/dist/SSHMimic/scp.d.ts +34 -0
- package/dist/SSHMimic/scp.js +285 -0
- package/dist/SSHMimic/sftp.d.ts +53 -3
- package/dist/SSHMimic/sftp.js +9 -3
- package/dist/VirtualFileSystem/binaryPack.d.ts +7 -0
- package/dist/VirtualFileSystem/binaryPack.js +37 -1
- package/dist/VirtualFileSystem/index.d.ts +7 -0
- package/dist/VirtualFileSystem/index.js +67 -27
- package/dist/VirtualFileSystem/internalTypes.d.ts +2 -0
- package/dist/VirtualFileSystem/path.d.ts +5 -0
- package/dist/VirtualFileSystem/path.js +24 -11
- package/dist/VirtualPackageManager/index.d.ts +4 -2
- package/dist/VirtualPackageManager/index.js +24 -4
- package/dist/VirtualShell/index.d.ts +6 -3
- package/dist/VirtualShell/index.js +3 -10
- package/dist/VirtualShell/shell.js +114 -140
- package/dist/VirtualShell/shellParser.js +1 -22
- package/dist/commands/exit.js +1 -1
- package/dist/commands/find.js +1 -4
- package/dist/commands/helpers.d.ts +0 -20
- package/dist/commands/helpers.js +0 -97
- package/dist/commands/id.js +8 -1
- package/dist/commands/index.d.ts +1 -1
- package/dist/commands/index.js +1 -1
- package/dist/commands/manuals-bundle.js +10 -1
- package/dist/commands/perl.js +1 -1
- package/dist/commands/python.js +5 -2
- package/dist/commands/registry.js +6 -1
- package/dist/commands/rm.d.ts +1 -1
- package/dist/commands/rm.js +48 -11
- package/dist/commands/runtime.d.ts +5 -0
- package/dist/commands/runtime.js +90 -88
- package/dist/commands/strace.js +1 -1
- package/dist/commands/tar.js +2 -2
- package/dist/commands/test.js +2 -2
- package/dist/modules/linuxRootfs.js +7 -6
- package/dist/modules/nanoEditor.d.ts +92 -0
- package/dist/modules/nanoEditor.js +956 -0
- package/dist/modules/neofetch.js +2 -2
- package/dist/modules/webTermRenderer.d.ts +42 -0
- package/dist/modules/webTermRenderer.js +291 -0
- package/dist/types/commands.d.ts +4 -0
- package/dist/utils/argv.d.ts +6 -0
- package/dist/utils/argv.js +32 -0
- package/dist/utils/expand.d.ts +5 -2
- package/dist/utils/expand.js +70 -67
- package/dist/utils/glob.d.ts +6 -0
- package/dist/utils/glob.js +34 -0
- package/dist/utils/shellSession.d.ts +10 -0
- package/dist/utils/shellSession.js +56 -0
- package/dist/utils/tokenize.js +13 -13
- package/package.json +7 -6
package/dist/modules/neofetch.js
CHANGED
|
@@ -208,10 +208,10 @@ function resolveDefaults(info) {
|
|
|
208
208
|
os: info.osName ?? `${readOsPrettyName() ?? os.type()} ${os.arch()}`,
|
|
209
209
|
arch: os.arch(),
|
|
210
210
|
},
|
|
211
|
-
resolution: info.resolution ?? "n/a (ssh)",
|
|
211
|
+
resolution: info.resolution ?? shellProps?.resolution ?? "n/a (ssh)",
|
|
212
212
|
terminal: info.terminal ?? "unknown",
|
|
213
213
|
cpu: info.cpu ?? resolveCpuLabel(),
|
|
214
|
-
gpu: info.gpu ?? "n/a",
|
|
214
|
+
gpu: info.gpu ?? shellProps?.gpu ?? "n/a",
|
|
215
215
|
memoryUsedMiB: info.memoryUsedMiB ?? toMiB(usedMem),
|
|
216
216
|
memoryTotalMiB: info.memoryTotalMiB ?? toMiB(totalMem),
|
|
217
217
|
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal VT100 screen buffer for browser-side rendering.
|
|
3
|
+
* Handles the subset of escape sequences emitted by NanoEditor:
|
|
4
|
+
* - CSI H / CSI row;colH (cursor position)
|
|
5
|
+
* - CSI K (erase to end of line)
|
|
6
|
+
* - CSI 2J (erase display)
|
|
7
|
+
* - CSI ?25l / ?25h (cursor hide/show)
|
|
8
|
+
* - CSI <n> m (SGR — bold, reverse, fg, bg, reset)
|
|
9
|
+
*/
|
|
10
|
+
export interface Cell {
|
|
11
|
+
ch: string;
|
|
12
|
+
bold: boolean;
|
|
13
|
+
reverse: boolean;
|
|
14
|
+
fg: string | null;
|
|
15
|
+
bg: string | null;
|
|
16
|
+
}
|
|
17
|
+
export declare class WebTermRenderer {
|
|
18
|
+
private rows;
|
|
19
|
+
private cols;
|
|
20
|
+
private screen;
|
|
21
|
+
private curRow;
|
|
22
|
+
private curCol;
|
|
23
|
+
private cursorVisible;
|
|
24
|
+
private bold;
|
|
25
|
+
private reverse;
|
|
26
|
+
private fg;
|
|
27
|
+
private bg;
|
|
28
|
+
private buf;
|
|
29
|
+
constructor(rows: number, cols: number);
|
|
30
|
+
resize(rows: number, cols: number): void;
|
|
31
|
+
write(data: string): void;
|
|
32
|
+
private flush;
|
|
33
|
+
private handleCsi;
|
|
34
|
+
private handleSgr;
|
|
35
|
+
private putChar;
|
|
36
|
+
private makeScreen;
|
|
37
|
+
/** Render current screen state to an HTML string for a <pre> element. */
|
|
38
|
+
renderHtml(): string;
|
|
39
|
+
get cursorRow(): number;
|
|
40
|
+
get cursorCol(): number;
|
|
41
|
+
get isCursorVisible(): boolean;
|
|
42
|
+
}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal VT100 screen buffer for browser-side rendering.
|
|
3
|
+
* Handles the subset of escape sequences emitted by NanoEditor:
|
|
4
|
+
* - CSI H / CSI row;colH (cursor position)
|
|
5
|
+
* - CSI K (erase to end of line)
|
|
6
|
+
* - CSI 2J (erase display)
|
|
7
|
+
* - CSI ?25l / ?25h (cursor hide/show)
|
|
8
|
+
* - CSI <n> m (SGR — bold, reverse, fg, bg, reset)
|
|
9
|
+
*/
|
|
10
|
+
const DEFAULT_CELL = { ch: " ", bold: false, reverse: false, fg: null, bg: null };
|
|
11
|
+
function makeCell(partial) {
|
|
12
|
+
return { ...DEFAULT_CELL, ...partial };
|
|
13
|
+
}
|
|
14
|
+
export class WebTermRenderer {
|
|
15
|
+
rows;
|
|
16
|
+
cols;
|
|
17
|
+
screen;
|
|
18
|
+
curRow = 0;
|
|
19
|
+
curCol = 0;
|
|
20
|
+
cursorVisible = true;
|
|
21
|
+
// Current SGR state
|
|
22
|
+
bold = false;
|
|
23
|
+
reverse = false;
|
|
24
|
+
fg = null;
|
|
25
|
+
bg = null;
|
|
26
|
+
buf = "";
|
|
27
|
+
constructor(rows, cols) {
|
|
28
|
+
this.rows = rows;
|
|
29
|
+
this.cols = cols;
|
|
30
|
+
this.screen = this.makeScreen();
|
|
31
|
+
}
|
|
32
|
+
resize(rows, cols) {
|
|
33
|
+
const newScreen = this.makeScreen(rows, cols);
|
|
34
|
+
for (let r = 0; r < Math.min(rows, this.rows); r++) {
|
|
35
|
+
for (let c = 0; c < Math.min(cols, this.cols); c++) {
|
|
36
|
+
newScreen[r][c] = this.screen[r]?.[c] ?? makeCell();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
this.rows = rows;
|
|
40
|
+
this.cols = cols;
|
|
41
|
+
this.screen = newScreen;
|
|
42
|
+
this.curRow = Math.min(this.curRow, rows - 1);
|
|
43
|
+
this.curCol = Math.min(this.curCol, cols - 1);
|
|
44
|
+
}
|
|
45
|
+
write(data) {
|
|
46
|
+
this.buf += data;
|
|
47
|
+
this.flush();
|
|
48
|
+
}
|
|
49
|
+
flush() {
|
|
50
|
+
let i = 0;
|
|
51
|
+
while (i < this.buf.length) {
|
|
52
|
+
const ch = this.buf[i];
|
|
53
|
+
if (ch === "\x1b") {
|
|
54
|
+
if (i + 1 >= this.buf.length)
|
|
55
|
+
break; // wait for more data
|
|
56
|
+
const next = this.buf[i + 1];
|
|
57
|
+
if (next === "[") {
|
|
58
|
+
// CSI — find terminator
|
|
59
|
+
let j = i + 2;
|
|
60
|
+
while (j < this.buf.length && (this.buf[j] < "@" || this.buf[j] > "~"))
|
|
61
|
+
j++;
|
|
62
|
+
if (j >= this.buf.length)
|
|
63
|
+
break; // incomplete
|
|
64
|
+
const seq = this.buf.slice(i + 2, j);
|
|
65
|
+
const cmd = this.buf[j];
|
|
66
|
+
this.handleCsi(seq, cmd);
|
|
67
|
+
i = j + 1;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
i += 2; // skip unknown ESC sequence
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else if (ch === "\r") {
|
|
74
|
+
this.curCol = 0;
|
|
75
|
+
i++;
|
|
76
|
+
}
|
|
77
|
+
else if (ch === "\n") {
|
|
78
|
+
this.curRow = Math.min(this.curRow + 1, this.rows - 1);
|
|
79
|
+
i++;
|
|
80
|
+
}
|
|
81
|
+
else if (ch.charCodeAt(0) >= 32) {
|
|
82
|
+
this.putChar(ch);
|
|
83
|
+
i++;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
i++; // skip other control chars
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
this.buf = this.buf.slice(i);
|
|
90
|
+
}
|
|
91
|
+
handleCsi(seq, cmd) {
|
|
92
|
+
if (cmd === "H" || cmd === "f") {
|
|
93
|
+
// Cursor position: row;col (1-based)
|
|
94
|
+
const parts = seq.split(";").map((n) => Number.parseInt(n || "1", 10));
|
|
95
|
+
this.curRow = Math.max(0, Math.min((parts[0] ?? 1) - 1, this.rows - 1));
|
|
96
|
+
this.curCol = Math.max(0, Math.min((parts[1] ?? 1) - 1, this.cols - 1));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (cmd === "K") {
|
|
100
|
+
// Erase line from cursor to end
|
|
101
|
+
const mode = seq === "" ? 0 : Number.parseInt(seq, 10);
|
|
102
|
+
if (mode === 0) {
|
|
103
|
+
for (let c = this.curCol; c < this.cols; c++) {
|
|
104
|
+
this.screen[this.curRow][c] = makeCell();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
else if (mode === 1) {
|
|
108
|
+
for (let c = 0; c <= this.curCol; c++) {
|
|
109
|
+
this.screen[this.curRow][c] = makeCell();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
else if (mode === 2) {
|
|
113
|
+
for (let c = 0; c < this.cols; c++) {
|
|
114
|
+
this.screen[this.curRow][c] = makeCell();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (cmd === "J") {
|
|
120
|
+
const mode = seq === "" ? 0 : Number.parseInt(seq, 10);
|
|
121
|
+
if (mode === 2) {
|
|
122
|
+
this.screen = this.makeScreen();
|
|
123
|
+
this.curRow = 0;
|
|
124
|
+
this.curCol = 0;
|
|
125
|
+
}
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (cmd === "m") {
|
|
129
|
+
this.handleSgr(seq);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (cmd === "l" && seq === "?25") {
|
|
133
|
+
this.cursorVisible = false;
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (cmd === "h" && seq === "?25") {
|
|
137
|
+
this.cursorVisible = true;
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
handleSgr(seq) {
|
|
142
|
+
const codes = seq === "" ? [0] : seq.split(";").map((n) => Number.parseInt(n || "0", 10));
|
|
143
|
+
let i = 0;
|
|
144
|
+
while (i < codes.length) {
|
|
145
|
+
const code = codes[i];
|
|
146
|
+
if (code === 0) {
|
|
147
|
+
this.bold = false;
|
|
148
|
+
this.reverse = false;
|
|
149
|
+
this.fg = null;
|
|
150
|
+
this.bg = null;
|
|
151
|
+
}
|
|
152
|
+
else if (code === 1) {
|
|
153
|
+
this.bold = true;
|
|
154
|
+
}
|
|
155
|
+
else if (code === 7) {
|
|
156
|
+
this.reverse = true;
|
|
157
|
+
}
|
|
158
|
+
else if (code === 22) {
|
|
159
|
+
this.bold = false;
|
|
160
|
+
}
|
|
161
|
+
else if (code === 27) {
|
|
162
|
+
this.reverse = false;
|
|
163
|
+
}
|
|
164
|
+
else if (code >= 30 && code <= 37) {
|
|
165
|
+
this.fg = ANSI_COLORS[code - 30];
|
|
166
|
+
}
|
|
167
|
+
else if (code === 38) {
|
|
168
|
+
if (codes[i + 1] === 5 && codes[i + 2] !== undefined) {
|
|
169
|
+
this.fg = xterm256(codes[i + 2]);
|
|
170
|
+
i += 2;
|
|
171
|
+
}
|
|
172
|
+
else if (codes[i + 1] === 2 && codes[i + 4] !== undefined) {
|
|
173
|
+
this.fg = `rgb(${codes[i + 2]},${codes[i + 3]},${codes[i + 4]})`;
|
|
174
|
+
i += 4;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
else if (code === 39) {
|
|
178
|
+
this.fg = null;
|
|
179
|
+
}
|
|
180
|
+
else if (code >= 40 && code <= 47) {
|
|
181
|
+
this.bg = ANSI_COLORS[code - 40];
|
|
182
|
+
}
|
|
183
|
+
else if (code === 48) {
|
|
184
|
+
if (codes[i + 1] === 5 && codes[i + 2] !== undefined) {
|
|
185
|
+
this.bg = xterm256(codes[i + 2]);
|
|
186
|
+
i += 2;
|
|
187
|
+
}
|
|
188
|
+
else if (codes[i + 1] === 2 && codes[i + 4] !== undefined) {
|
|
189
|
+
this.bg = `rgb(${codes[i + 2]},${codes[i + 3]},${codes[i + 4]})`;
|
|
190
|
+
i += 4;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
else if (code === 49) {
|
|
194
|
+
this.bg = null;
|
|
195
|
+
}
|
|
196
|
+
else if (code >= 90 && code <= 97) {
|
|
197
|
+
this.fg = ANSI_COLORS_BRIGHT[code - 90];
|
|
198
|
+
}
|
|
199
|
+
else if (code >= 100 && code <= 107) {
|
|
200
|
+
this.bg = ANSI_COLORS_BRIGHT[code - 100];
|
|
201
|
+
}
|
|
202
|
+
i++;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
putChar(ch) {
|
|
206
|
+
if (this.curRow >= this.rows || this.curCol >= this.cols)
|
|
207
|
+
return;
|
|
208
|
+
this.screen[this.curRow][this.curCol] = makeCell({
|
|
209
|
+
ch,
|
|
210
|
+
bold: this.bold,
|
|
211
|
+
reverse: this.reverse,
|
|
212
|
+
fg: this.fg,
|
|
213
|
+
bg: this.bg,
|
|
214
|
+
});
|
|
215
|
+
this.curCol++;
|
|
216
|
+
if (this.curCol >= this.cols) {
|
|
217
|
+
this.curCol = 0;
|
|
218
|
+
if (this.curRow < this.rows - 1)
|
|
219
|
+
this.curRow++;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
makeScreen(rows = this.rows, cols = this.cols) {
|
|
223
|
+
return Array.from({ length: rows }, () => Array.from({ length: cols }, () => makeCell()));
|
|
224
|
+
}
|
|
225
|
+
/** Render current screen state to an HTML string for a <pre> element. */
|
|
226
|
+
renderHtml() {
|
|
227
|
+
let html = "";
|
|
228
|
+
for (let r = 0; r < this.rows; r++) {
|
|
229
|
+
const row = this.screen[r];
|
|
230
|
+
let spanOpen = false;
|
|
231
|
+
let lastStyle = "";
|
|
232
|
+
for (let c = 0; c < this.cols; c++) {
|
|
233
|
+
const cell = row[c];
|
|
234
|
+
const isCursor = this.cursorVisible && r === this.curRow && c === this.curCol;
|
|
235
|
+
let fg = cell.fg ?? "#ccc";
|
|
236
|
+
let bg = cell.bg ?? "transparent";
|
|
237
|
+
if (cell.reverse) {
|
|
238
|
+
[fg, bg] = [bg === "transparent" ? "#000" : bg, fg];
|
|
239
|
+
}
|
|
240
|
+
if (cell.bold && !cell.fg)
|
|
241
|
+
fg = "#fff";
|
|
242
|
+
if (isCursor) {
|
|
243
|
+
[fg, bg] = [bg === "transparent" ? "#000" : bg, fg === "#ccc" ? "#ccc" : fg];
|
|
244
|
+
bg = "#ccc";
|
|
245
|
+
fg = "#000";
|
|
246
|
+
}
|
|
247
|
+
const style = `color:${fg};background:${bg};${cell.bold ? "font-weight:bold;" : ""}`;
|
|
248
|
+
if (style !== lastStyle) {
|
|
249
|
+
if (spanOpen)
|
|
250
|
+
html += "</span>";
|
|
251
|
+
html += `<span style="${style}">`;
|
|
252
|
+
spanOpen = true;
|
|
253
|
+
lastStyle = style;
|
|
254
|
+
}
|
|
255
|
+
html += escHtml(cell.ch);
|
|
256
|
+
}
|
|
257
|
+
if (spanOpen)
|
|
258
|
+
html += "</span>";
|
|
259
|
+
if (r < this.rows - 1)
|
|
260
|
+
html += "\n";
|
|
261
|
+
}
|
|
262
|
+
return html;
|
|
263
|
+
}
|
|
264
|
+
get cursorRow() { return this.curRow; }
|
|
265
|
+
get cursorCol() { return this.curCol; }
|
|
266
|
+
get isCursorVisible() { return this.cursorVisible; }
|
|
267
|
+
}
|
|
268
|
+
function escHtml(ch) {
|
|
269
|
+
if (ch === "&")
|
|
270
|
+
return "&";
|
|
271
|
+
if (ch === "<")
|
|
272
|
+
return "<";
|
|
273
|
+
if (ch === ">")
|
|
274
|
+
return ">";
|
|
275
|
+
return ch;
|
|
276
|
+
}
|
|
277
|
+
const ANSI_COLORS = ["#000", "#c00", "#0c0", "#cc0", "#00c", "#c0c", "#0cc", "#ccc"];
|
|
278
|
+
const ANSI_COLORS_BRIGHT = ["#555", "#f55", "#5f5", "#ff5", "#55f", "#f5f", "#5ff", "#fff"];
|
|
279
|
+
function xterm256(n) {
|
|
280
|
+
if (n < 16)
|
|
281
|
+
return (n < 8 ? ANSI_COLORS : ANSI_COLORS_BRIGHT)[n < 8 ? n : n - 8];
|
|
282
|
+
if (n < 232) {
|
|
283
|
+
const i = n - 16;
|
|
284
|
+
const r = Math.floor(i / 36) * 51;
|
|
285
|
+
const g = Math.floor((i % 36) / 6) * 51;
|
|
286
|
+
const b = (i % 6) * 51;
|
|
287
|
+
return `rgb(${r},${g},${b})`;
|
|
288
|
+
}
|
|
289
|
+
const v = (n - 232) * 10 + 8;
|
|
290
|
+
return `rgb(${v},${v},${v})`;
|
|
291
|
+
}
|
package/dist/types/commands.d.ts
CHANGED
|
@@ -94,6 +94,10 @@ export interface ShellEnv {
|
|
|
94
94
|
vars: Record<string, string>;
|
|
95
95
|
/** Exit status of the last executed command. */
|
|
96
96
|
lastExitCode: number;
|
|
97
|
+
/** @internal Cached split of vars.PATH — invalidated when _pathRaw !== vars.PATH. */
|
|
98
|
+
_pathRaw?: string;
|
|
99
|
+
/** @internal Pre-split PATH directories for resolveVfsBinary hot-path. */
|
|
100
|
+
_pathDirs?: string[];
|
|
97
101
|
}
|
|
98
102
|
/** Runtime context object passed to each command module. */
|
|
99
103
|
export interface CommandContext {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Returns true if `name` appears in `argv`. */
|
|
2
|
+
export declare function getFlag(argv: string[], name: string): boolean;
|
|
3
|
+
/** Returns the string value for `--name=VALUE` or `--name VALUE`, or `fallback`. */
|
|
4
|
+
export declare function getOptionString(argv: string[], name: string, fallback: string): string;
|
|
5
|
+
/** Returns the integer value for `--name=VALUE` or `--name VALUE`, or `fallback`. */
|
|
6
|
+
export declare function getOptionInt(argv: string[], name: string, fallback: number): number;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Returns true if `name` appears in `argv`. */
|
|
2
|
+
export function getFlag(argv, name) {
|
|
3
|
+
return argv.includes(name);
|
|
4
|
+
}
|
|
5
|
+
/** Returns the string value for `--name=VALUE` or `--name VALUE`, or `fallback`. */
|
|
6
|
+
export function getOptionString(argv, name, fallback) {
|
|
7
|
+
const prefix = `${name}=`;
|
|
8
|
+
for (let i = 0; i < argv.length; i++) {
|
|
9
|
+
const a = argv[i];
|
|
10
|
+
if (a.startsWith(prefix))
|
|
11
|
+
return a.slice(prefix.length);
|
|
12
|
+
if (a === name) {
|
|
13
|
+
const next = argv[i + 1];
|
|
14
|
+
return (next && !next.startsWith("--")) ? next : fallback;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return fallback;
|
|
18
|
+
}
|
|
19
|
+
/** Returns the integer value for `--name=VALUE` or `--name VALUE`, or `fallback`. */
|
|
20
|
+
export function getOptionInt(argv, name, fallback) {
|
|
21
|
+
const prefix = `${name}=`;
|
|
22
|
+
for (let i = 0; i < argv.length; i++) {
|
|
23
|
+
const a = argv[i];
|
|
24
|
+
if (a.startsWith(prefix))
|
|
25
|
+
return parseInt(a.slice(prefix.length), 10);
|
|
26
|
+
if (a === name) {
|
|
27
|
+
const next = argv[i + 1];
|
|
28
|
+
return next ? parseInt(next, 10) : fallback;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return fallback;
|
|
32
|
+
}
|
package/dist/utils/expand.d.ts
CHANGED
|
@@ -61,10 +61,13 @@ export declare function expandAsync(input: string, env: Record<string, string>,
|
|
|
61
61
|
* Supports * (any chars in segment) and ** (any path).
|
|
62
62
|
* Returns the original pattern if no matches found (bash behavior).
|
|
63
63
|
*/
|
|
64
|
-
|
|
64
|
+
type GlobVfs = {
|
|
65
65
|
list: (p: string) => string[];
|
|
66
66
|
exists: (p: string) => boolean;
|
|
67
67
|
stat: (p: string) => {
|
|
68
68
|
type: string;
|
|
69
69
|
};
|
|
70
|
-
|
|
70
|
+
statType?: (p: string) => string | null;
|
|
71
|
+
};
|
|
72
|
+
export declare function expandGlob(pattern: string, cwd: string, vfs: GlobVfs): string[];
|
|
73
|
+
export {};
|
package/dist/utils/expand.js
CHANGED
|
@@ -17,6 +17,24 @@
|
|
|
17
17
|
* $VAR simple reference
|
|
18
18
|
* $((expr)) arithmetic (integer)
|
|
19
19
|
*/
|
|
20
|
+
import { globToRegex } from "./glob";
|
|
21
|
+
// Memoized shell-pattern → RegExp for ${VAR//pat/rep} etc. forms.
|
|
22
|
+
// Key encodes anchor/greedy options to keep separate caches per form.
|
|
23
|
+
const _shellPatCache = new Map();
|
|
24
|
+
function shellPatToRegex(pat, anchor, greedy, global = false) {
|
|
25
|
+
const key = `${anchor}:${greedy ? "g" : "s"}:${global ? "G" : ""}:${pat}`;
|
|
26
|
+
let re = _shellPatCache.get(key);
|
|
27
|
+
if (re)
|
|
28
|
+
return re;
|
|
29
|
+
const esc = pat.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
30
|
+
const body = greedy
|
|
31
|
+
? esc.replace(/\*/g, ".*").replace(/\?/g, ".")
|
|
32
|
+
: esc.replace(/\*/g, "[^/]*").replace(/\?/g, ".");
|
|
33
|
+
const src = anchor === "prefix" ? `^${body}` : anchor === "suffix" ? `${body}$` : body;
|
|
34
|
+
re = new RegExp(src, global ? "g" : "");
|
|
35
|
+
_shellPatCache.set(key, re);
|
|
36
|
+
return re;
|
|
37
|
+
}
|
|
20
38
|
function tokenizeArith(expr, env) {
|
|
21
39
|
const tokens = [];
|
|
22
40
|
let i = 0;
|
|
@@ -193,25 +211,24 @@ export function evalArith(expr, env) {
|
|
|
193
211
|
* Single-quoted content is passed through verbatim (POSIX sh behaviour).
|
|
194
212
|
*/
|
|
195
213
|
function outsideSingleQuotes(input, replacer) {
|
|
214
|
+
// Fast path: no single quotes → apply replacer to whole string, no allocation
|
|
215
|
+
if (!input.includes("'"))
|
|
216
|
+
return replacer(input);
|
|
196
217
|
const parts = [];
|
|
197
218
|
let i = 0;
|
|
198
219
|
while (i < input.length) {
|
|
199
220
|
const sqIdx = input.indexOf("'", i);
|
|
200
221
|
if (sqIdx === -1) {
|
|
201
|
-
// No more single quotes — expand the rest
|
|
202
222
|
parts.push(replacer(input.slice(i)));
|
|
203
223
|
break;
|
|
204
224
|
}
|
|
205
|
-
// Expand the part before the single quote
|
|
206
225
|
parts.push(replacer(input.slice(i, sqIdx)));
|
|
207
|
-
// Find closing single quote — everything inside is literal
|
|
208
226
|
const closeIdx = input.indexOf("'", sqIdx + 1);
|
|
209
227
|
if (closeIdx === -1) {
|
|
210
|
-
// Unclosed quote — treat rest as literal
|
|
211
228
|
parts.push(input.slice(sqIdx));
|
|
212
229
|
break;
|
|
213
230
|
}
|
|
214
|
-
parts.push(input.slice(sqIdx, closeIdx + 1));
|
|
231
|
+
parts.push(input.slice(sqIdx, closeIdx + 1));
|
|
215
232
|
i = closeIdx + 1;
|
|
216
233
|
}
|
|
217
234
|
return parts.join("");
|
|
@@ -327,10 +344,14 @@ export function expandBraces(token) {
|
|
|
327
344
|
return expandBracesInternal(token, 0);
|
|
328
345
|
}
|
|
329
346
|
function expandArithmeticChunks(input, env) {
|
|
347
|
+
if (!input.includes("$(("))
|
|
348
|
+
return input;
|
|
330
349
|
let result = "";
|
|
331
350
|
let index = 0;
|
|
351
|
+
let flush = 0;
|
|
332
352
|
while (index < input.length) {
|
|
333
353
|
if (input[index] === "$" && input[index + 1] === "(" && input[index + 2] === "(") {
|
|
354
|
+
result += input.slice(flush, index);
|
|
334
355
|
let scan = index + 3;
|
|
335
356
|
let depth = 0;
|
|
336
357
|
while (scan < input.length) {
|
|
@@ -347,6 +368,7 @@ function expandArithmeticChunks(input, env) {
|
|
|
347
368
|
const value = evalArith(expr, env);
|
|
348
369
|
result += Number.isNaN(value) ? "0" : String(value);
|
|
349
370
|
index = scan + 2;
|
|
371
|
+
flush = index;
|
|
350
372
|
break;
|
|
351
373
|
}
|
|
352
374
|
}
|
|
@@ -354,16 +376,18 @@ function expandArithmeticChunks(input, env) {
|
|
|
354
376
|
}
|
|
355
377
|
if (scan >= input.length) {
|
|
356
378
|
result += input.slice(index);
|
|
357
|
-
|
|
379
|
+
return result;
|
|
358
380
|
}
|
|
359
381
|
continue;
|
|
360
382
|
}
|
|
361
|
-
result += input[index];
|
|
362
383
|
index++;
|
|
363
384
|
}
|
|
364
|
-
return result;
|
|
385
|
+
return result + input.slice(flush);
|
|
365
386
|
}
|
|
366
387
|
export function expandSync(input, env, lastExit = 0, home) {
|
|
388
|
+
// Fast path: nothing to expand (no $ and no ~ and no single quotes)
|
|
389
|
+
if (!input.includes("$") && !input.includes("~") && !input.includes("'"))
|
|
390
|
+
return input;
|
|
367
391
|
const homePath = home ?? env.HOME ?? "/home/user";
|
|
368
392
|
return outsideSingleQuotes(input, (chunk) => {
|
|
369
393
|
let s = chunk;
|
|
@@ -413,7 +437,7 @@ export function expandSync(input, env, lastExit = 0, home) {
|
|
|
413
437
|
s = s.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\/\/([^/}]*)\/([^}]*)\}/g, (_, name, pat, rep) => {
|
|
414
438
|
const val = env[name] ?? "";
|
|
415
439
|
try {
|
|
416
|
-
return val.replace(
|
|
440
|
+
return val.replace(shellPatToRegex(pat, "none", true, true), rep);
|
|
417
441
|
}
|
|
418
442
|
catch {
|
|
419
443
|
return val;
|
|
@@ -423,36 +447,20 @@ export function expandSync(input, env, lastExit = 0, home) {
|
|
|
423
447
|
s = s.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\/([^/}]*)\/([^}]*)\}/g, (_, name, pat, rep) => {
|
|
424
448
|
const val = env[name] ?? "";
|
|
425
449
|
try {
|
|
426
|
-
return val.replace(
|
|
450
|
+
return val.replace(shellPatToRegex(pat, "none", true, false), rep);
|
|
427
451
|
}
|
|
428
452
|
catch {
|
|
429
453
|
return val;
|
|
430
454
|
}
|
|
431
455
|
});
|
|
432
456
|
// ${VAR##pattern} — strip longest prefix
|
|
433
|
-
s = s.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)##([^}]+)\}/g, (_, name, pat) =>
|
|
434
|
-
const val = env[name] ?? "";
|
|
435
|
-
const re = new RegExp(`^${pat.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".")}`);
|
|
436
|
-
return val.replace(re, "");
|
|
437
|
-
});
|
|
457
|
+
s = s.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)##([^}]+)\}/g, (_, name, pat) => (env[name] ?? "").replace(shellPatToRegex(pat, "prefix", true), ""));
|
|
438
458
|
// ${VAR#pattern} — strip shortest prefix
|
|
439
|
-
s = s.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)#([^}]+)\}/g, (_, name, pat) =>
|
|
440
|
-
const val = env[name] ?? "";
|
|
441
|
-
const re = new RegExp(`^${pat.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]*").replace(/\?/g, ".")}`);
|
|
442
|
-
return val.replace(re, "");
|
|
443
|
-
});
|
|
459
|
+
s = s.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)#([^}]+)\}/g, (_, name, pat) => (env[name] ?? "").replace(shellPatToRegex(pat, "prefix", false), ""));
|
|
444
460
|
// ${VAR%%pattern} — strip longest suffix
|
|
445
|
-
s = s.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)%%([^}]+)\}/g, (_, name, pat) =>
|
|
446
|
-
const val = env[name] ?? "";
|
|
447
|
-
const re = new RegExp(`${pat.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".")}$`);
|
|
448
|
-
return val.replace(re, "");
|
|
449
|
-
});
|
|
461
|
+
s = s.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)%%([^}]+)\}/g, (_, name, pat) => (env[name] ?? "").replace(shellPatToRegex(pat, "suffix", true), ""));
|
|
450
462
|
// ${VAR%pattern} — strip shortest suffix
|
|
451
|
-
s = s.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)%([^}]+)\}/g, (_, name, pat) =>
|
|
452
|
-
const val = env[name] ?? "";
|
|
453
|
-
const re = new RegExp(`${pat.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]*").replace(/\?/g, ".")}$`);
|
|
454
|
-
return val.replace(re, "");
|
|
455
|
-
});
|
|
463
|
+
s = s.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)%([^}]+)\}/g, (_, name, pat) => (env[name] ?? "").replace(shellPatToRegex(pat, "suffix", false), ""));
|
|
456
464
|
// ${VAR}
|
|
457
465
|
s = s.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_, name) => env[name] ?? "");
|
|
458
466
|
// $VAR and positional params $1 $2 ...
|
|
@@ -540,12 +548,16 @@ export async function expandAsync(input, env, lastExit, runCmd) {
|
|
|
540
548
|
env[depthKey] = String(currentDepth);
|
|
541
549
|
}
|
|
542
550
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
551
|
+
function nodeType(vfs, p) {
|
|
552
|
+
if (vfs.statType)
|
|
553
|
+
return vfs.statType(p);
|
|
554
|
+
try {
|
|
555
|
+
return vfs.stat(p).type;
|
|
556
|
+
}
|
|
557
|
+
catch {
|
|
558
|
+
return null;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
549
561
|
export function expandGlob(pattern, cwd, vfs) {
|
|
550
562
|
// No glob chars → return as-is
|
|
551
563
|
if (!pattern.includes('*') && !pattern.includes('?'))
|
|
@@ -567,14 +579,14 @@ function matchGlob(dir, segments, vfs) {
|
|
|
567
579
|
// ** matches zero or more path segments
|
|
568
580
|
if (seg === '**') {
|
|
569
581
|
const all = walkAll(dir, vfs);
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
582
|
+
if (rest.length === 0)
|
|
583
|
+
return all;
|
|
584
|
+
const out = [];
|
|
585
|
+
for (const d of all) {
|
|
586
|
+
if (nodeType(vfs, d) === 'directory')
|
|
587
|
+
out.push(...matchGlob(d, rest, vfs));
|
|
588
|
+
}
|
|
589
|
+
return out;
|
|
578
590
|
}
|
|
579
591
|
let entries = [];
|
|
580
592
|
try {
|
|
@@ -584,20 +596,20 @@ function matchGlob(dir, segments, vfs) {
|
|
|
584
596
|
return [];
|
|
585
597
|
}
|
|
586
598
|
const re = globToRegex(seg);
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
.
|
|
599
|
+
const showHidden = seg.startsWith('.');
|
|
600
|
+
const matched = [];
|
|
601
|
+
for (const e of entries) {
|
|
602
|
+
if ((!showHidden && e.startsWith('.')) || !re.test(e))
|
|
603
|
+
continue;
|
|
591
604
|
const full = dir === '/' ? `/${e}` : `${dir}/${e}`;
|
|
592
|
-
if (rest.length === 0)
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
if (vfs.stat(full).type === 'directory')
|
|
596
|
-
return matchGlob(full, rest, vfs);
|
|
605
|
+
if (rest.length === 0) {
|
|
606
|
+
matched.push(full);
|
|
607
|
+
continue;
|
|
597
608
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
}
|
|
609
|
+
if (nodeType(vfs, full) === 'directory')
|
|
610
|
+
matched.push(...matchGlob(full, rest, vfs));
|
|
611
|
+
}
|
|
612
|
+
return matched;
|
|
601
613
|
}
|
|
602
614
|
function walkAll(dir, vfs) {
|
|
603
615
|
const results = [dir];
|
|
@@ -610,17 +622,8 @@ function walkAll(dir, vfs) {
|
|
|
610
622
|
}
|
|
611
623
|
for (const e of entries) {
|
|
612
624
|
const full = dir === '/' ? `/${e}` : `${dir}/${e}`;
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
results.push(...walkAll(full, vfs));
|
|
616
|
-
}
|
|
617
|
-
catch { }
|
|
625
|
+
if (nodeType(vfs, full) === 'directory')
|
|
626
|
+
results.push(...walkAll(full, vfs));
|
|
618
627
|
}
|
|
619
628
|
return results;
|
|
620
629
|
}
|
|
621
|
-
function globToRegex(pattern) {
|
|
622
|
-
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
623
|
-
.replace(/\*/g, '.*')
|
|
624
|
-
.replace(/\?/g, '.');
|
|
625
|
-
return new RegExp(`^${escaped}$`);
|
|
626
|
-
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert a shell glob pattern to a RegExp.
|
|
3
|
+
* Supports: * (any chars), ? (one char), [...] (char class), flags (e.g. "i").
|
|
4
|
+
* Results are memoized — same pattern+flags returns the cached instance.
|
|
5
|
+
*/
|
|
6
|
+
export declare function globToRegex(pattern: string, flags?: string): RegExp;
|