typescript-virtual-container 1.4.2 → 1.4.3
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 +72 -35
- package/builds/self-standalone.js +160 -160
- package/builds/self-standalone.js.map +4 -4
- package/builds/standalone-wo-sftp.js +18 -18
- package/builds/standalone-wo-sftp.js.map +3 -3
- package/builds/standalone.js +46 -46
- package/builds/standalone.js.map +3 -3
- package/dist/VirtualFileSystem/index.d.ts +47 -0
- package/dist/VirtualFileSystem/index.d.ts.map +1 -1
- package/dist/VirtualFileSystem/index.js +159 -0
- package/dist/VirtualShell/index.d.ts +29 -0
- package/dist/VirtualShell/index.d.ts.map +1 -1
- package/dist/VirtualShell/index.js +29 -0
- package/dist/VirtualShell/shellParser.js +28 -1
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +5 -3
- package/dist/commands/registry.d.ts.map +1 -1
- package/dist/commands/registry.js +2 -0
- package/dist/commands/runtime.d.ts.map +1 -1
- package/dist/commands/runtime.js +28 -3
- package/dist/commands/seq.d.ts +4 -0
- package/dist/commands/seq.d.ts.map +1 -0
- package/dist/commands/seq.js +50 -0
- package/dist/commands/sh.d.ts +0 -6
- package/dist/commands/sh.d.ts.map +1 -1
- package/dist/commands/sh.js +153 -10
- package/dist/types/pipeline.d.ts +6 -0
- package/dist/types/pipeline.d.ts.map +1 -1
- package/dist/types/vfs.d.ts +15 -0
- package/dist/types/vfs.d.ts.map +1 -1
- package/dist/utils/expand.d.ts +9 -0
- package/dist/utils/expand.d.ts.map +1 -1
- package/dist/utils/expand.js +84 -2
- package/dist/utils/tokenize.d.ts.map +1 -1
- package/dist/utils/tokenize.js +40 -0
- package/package.json +1 -1
- package/src/VirtualFileSystem/index.ts +164 -1
- package/src/VirtualShell/index.ts +36 -0
- package/src/VirtualShell/shellParser.ts +26 -1
- package/src/commands/export.ts +5 -3
- package/src/commands/registry.ts +2 -0
- package/src/commands/runtime.ts +30 -3
- package/src/commands/seq.ts +43 -0
- package/src/commands/sh.ts +144 -19
- package/src/types/pipeline.ts +6 -0
- package/src/types/vfs.ts +17 -0
- package/src/utils/expand.ts +75 -2
- package/src/utils/tokenize.ts +20 -0
|
@@ -50,6 +50,10 @@ declare class VirtualFileSystem extends EventEmitter {
|
|
|
50
50
|
private root;
|
|
51
51
|
private readonly mode;
|
|
52
52
|
private readonly snapshotFile;
|
|
53
|
+
/** Active host-directory mounts: vPath → { hostPath, readOnly } */
|
|
54
|
+
private readonly mounts;
|
|
55
|
+
/** True when running in a browser environment (no host FS access). */
|
|
56
|
+
private static readonly isBrowser;
|
|
53
57
|
constructor(options?: VfsOptions);
|
|
54
58
|
private makeDir;
|
|
55
59
|
private makeFile;
|
|
@@ -75,6 +79,49 @@ declare class VirtualFileSystem extends EventEmitter {
|
|
|
75
79
|
/** Returns the snapshot file path used in `"fs"` mode, or `null`. */
|
|
76
80
|
getSnapshotPath(): string | null;
|
|
77
81
|
/** Creates a directory (and any missing parents). */
|
|
82
|
+
/**
|
|
83
|
+
* Mount a host directory into the VFS at `vPath`.
|
|
84
|
+
*
|
|
85
|
+
* Files inside `vPath` are read directly from the host filesystem via
|
|
86
|
+
* `node:fs`. All standard VFS operations (`readFile`, `writeFile`,
|
|
87
|
+
* `exists`, `stat`, `list`) are transparently delegated.
|
|
88
|
+
*
|
|
89
|
+
* In browser environments the mount is silently ignored — `vPath` remains
|
|
90
|
+
* an empty in-memory directory.
|
|
91
|
+
*
|
|
92
|
+
* @param vPath Absolute path inside the VM (e.g. `"/app"`).
|
|
93
|
+
* @param hostPath Path on the host filesystem — relative paths are
|
|
94
|
+
* resolved from `process.cwd()`.
|
|
95
|
+
* @param readOnly When `true` (default), write operations inside the
|
|
96
|
+
* mount throw `EROFS: read-only file system`.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* shell.vfs.mount("/app", "./src", { readOnly: true });
|
|
101
|
+
* // cat /app/index.ts — reads ./src/index.ts from host
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
mount(vPath: string, hostPath: string, { readOnly }?: {
|
|
105
|
+
readOnly?: boolean;
|
|
106
|
+
}): void;
|
|
107
|
+
/**
|
|
108
|
+
* Unmount a previously mounted host directory.
|
|
109
|
+
* The in-memory VFS directory at `vPath` is preserved but the host
|
|
110
|
+
* delegation is removed.
|
|
111
|
+
*/
|
|
112
|
+
unmount(vPath: string): void;
|
|
113
|
+
/** List all active mounts. */
|
|
114
|
+
getMounts(): Array<{
|
|
115
|
+
vPath: string;
|
|
116
|
+
hostPath: string;
|
|
117
|
+
readOnly: boolean;
|
|
118
|
+
}>;
|
|
119
|
+
/**
|
|
120
|
+
* If `targetPath` is inside a mount, return `{ hostPath, readOnly, relPath }`.
|
|
121
|
+
* `relPath` is the path relative to the mount's host directory.
|
|
122
|
+
* Returns `null` if the path is not under any mount.
|
|
123
|
+
*/
|
|
124
|
+
private resolveMount;
|
|
78
125
|
mkdir(targetPath: string, mode?: number): void;
|
|
79
126
|
/**
|
|
80
127
|
* Writes UTF-8 text or binary content into a file.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualFileSystem/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAW3C,OAAO,KAAK,EACX,aAAa,EACb,YAAY,EACZ,WAAW,EAIX,gBAAgB,EAChB,MAAM,cAAc,CAAC;AAItB;;;;;;GAMG;AACH,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,IAAI,CAAC;AAEjD,MAAM,WAAW,UAAU;IAC1B;;;;;OAKG;IACH,IAAI,CAAC,EAAE,kBAAkB,CAAC;IAC1B;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAID;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,cAAM,iBAAkB,SAAQ,YAAY;IAC3C,OAAO,CAAC,IAAI,CAAwB;IACpC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAqB;IAC1C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAgB;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualFileSystem/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -32,6 +32,10 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
32
32
|
root;
|
|
33
33
|
mode;
|
|
34
34
|
snapshotFile;
|
|
35
|
+
/** Active host-directory mounts: vPath → { hostPath, readOnly } */
|
|
36
|
+
mounts = new Map();
|
|
37
|
+
/** True when running in a browser environment (no host FS access). */
|
|
38
|
+
static isBrowser = typeof process === "undefined" || typeof process.versions?.node === "undefined";
|
|
35
39
|
constructor(options = {}) {
|
|
36
40
|
super();
|
|
37
41
|
this.mode = options.mode ?? "memory";
|
|
@@ -151,6 +155,80 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
151
155
|
}
|
|
152
156
|
// ── Public filesystem API ─────────────────────────────────────────────────
|
|
153
157
|
/** Creates a directory (and any missing parents). */
|
|
158
|
+
// ── Mount API ─────────────────────────────────────────────────────────────
|
|
159
|
+
/**
|
|
160
|
+
* Mount a host directory into the VFS at `vPath`.
|
|
161
|
+
*
|
|
162
|
+
* Files inside `vPath` are read directly from the host filesystem via
|
|
163
|
+
* `node:fs`. All standard VFS operations (`readFile`, `writeFile`,
|
|
164
|
+
* `exists`, `stat`, `list`) are transparently delegated.
|
|
165
|
+
*
|
|
166
|
+
* In browser environments the mount is silently ignored — `vPath` remains
|
|
167
|
+
* an empty in-memory directory.
|
|
168
|
+
*
|
|
169
|
+
* @param vPath Absolute path inside the VM (e.g. `"/app"`).
|
|
170
|
+
* @param hostPath Path on the host filesystem — relative paths are
|
|
171
|
+
* resolved from `process.cwd()`.
|
|
172
|
+
* @param readOnly When `true` (default), write operations inside the
|
|
173
|
+
* mount throw `EROFS: read-only file system`.
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```ts
|
|
177
|
+
* shell.vfs.mount("/app", "./src", { readOnly: true });
|
|
178
|
+
* // cat /app/index.ts — reads ./src/index.ts from host
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
mount(vPath, hostPath, { readOnly = true } = {}) {
|
|
182
|
+
if (VirtualFileSystem.isBrowser)
|
|
183
|
+
return; // silently degrade in browser
|
|
184
|
+
const normalized = normalizePath(vPath);
|
|
185
|
+
const resolved = path.resolve(hostPath);
|
|
186
|
+
if (!fsSync.existsSync(resolved)) {
|
|
187
|
+
throw new Error(`VirtualFileSystem.mount: host path does not exist: "${resolved}"`);
|
|
188
|
+
}
|
|
189
|
+
if (!fsSync.statSync(resolved).isDirectory()) {
|
|
190
|
+
throw new Error(`VirtualFileSystem.mount: host path is not a directory: "${resolved}"`);
|
|
191
|
+
}
|
|
192
|
+
// Ensure the mount point exists in the VFS tree
|
|
193
|
+
this.mkdir(normalized);
|
|
194
|
+
this.mounts.set(normalized, { hostPath: resolved, readOnly });
|
|
195
|
+
this.emit("mount", { vPath: normalized, hostPath: resolved, readOnly });
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Unmount a previously mounted host directory.
|
|
199
|
+
* The in-memory VFS directory at `vPath` is preserved but the host
|
|
200
|
+
* delegation is removed.
|
|
201
|
+
*/
|
|
202
|
+
unmount(vPath) {
|
|
203
|
+
const normalized = normalizePath(vPath);
|
|
204
|
+
if (this.mounts.delete(normalized)) {
|
|
205
|
+
this.emit("unmount", { vPath: normalized });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/** List all active mounts. */
|
|
209
|
+
getMounts() {
|
|
210
|
+
return [...this.mounts.entries()].map(([vPath, opts]) => ({
|
|
211
|
+
vPath, ...opts,
|
|
212
|
+
}));
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* If `targetPath` is inside a mount, return `{ hostPath, readOnly, relPath }`.
|
|
216
|
+
* `relPath` is the path relative to the mount's host directory.
|
|
217
|
+
* Returns `null` if the path is not under any mount.
|
|
218
|
+
*/
|
|
219
|
+
resolveMount(targetPath) {
|
|
220
|
+
const normalized = normalizePath(targetPath);
|
|
221
|
+
// Iterate mounts from most specific to least specific
|
|
222
|
+
const sorted = [...this.mounts.entries()].sort(([a], [b]) => b.length - a.length);
|
|
223
|
+
for (const [vBase, opts] of sorted) {
|
|
224
|
+
if (normalized === vBase || normalized.startsWith(`${vBase}/`)) {
|
|
225
|
+
const relPath = normalized.slice(vBase.length).replace(/^\//, "");
|
|
226
|
+
const fullHostPath = relPath ? path.join(opts.hostPath, relPath) : opts.hostPath;
|
|
227
|
+
return { hostPath: opts.hostPath, readOnly: opts.readOnly, relPath, fullHostPath };
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
154
232
|
mkdir(targetPath, mode = 0o755) {
|
|
155
233
|
const normalized = normalizePath(targetPath);
|
|
156
234
|
const existing = (() => {
|
|
@@ -171,6 +249,17 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
171
249
|
* Parent directories are created when missing.
|
|
172
250
|
*/
|
|
173
251
|
writeFile(targetPath, content, options = {}) {
|
|
252
|
+
// Delegate to host FS if inside a mount
|
|
253
|
+
const m = this.resolveMount(targetPath);
|
|
254
|
+
if (m) {
|
|
255
|
+
if (m.readOnly)
|
|
256
|
+
throw new Error(`EROFS: read-only file system, open '${m.fullHostPath}'`);
|
|
257
|
+
const dir = path.dirname(m.fullHostPath);
|
|
258
|
+
if (!fsSync.existsSync(dir))
|
|
259
|
+
fsSync.mkdirSync(dir, { recursive: true });
|
|
260
|
+
fsSync.writeFileSync(m.fullHostPath, Buffer.isBuffer(content) ? content : Buffer.from(content, "utf8"));
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
174
263
|
const normalized = normalizePath(targetPath);
|
|
175
264
|
const { parent, name } = getParentDirectory(this.root, normalized, true, (p) => this.mkdirRecursive(p, 0o755));
|
|
176
265
|
const existing = parent.children.get(name);
|
|
@@ -200,6 +289,12 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
200
289
|
* Gzip-compressed files are transparently decompressed.
|
|
201
290
|
*/
|
|
202
291
|
readFile(targetPath) {
|
|
292
|
+
const m = this.resolveMount(targetPath);
|
|
293
|
+
if (m) {
|
|
294
|
+
if (!fsSync.existsSync(m.fullHostPath))
|
|
295
|
+
throw new Error(`ENOENT: no such file or directory, open '${m.fullHostPath}'`);
|
|
296
|
+
return fsSync.readFileSync(m.fullHostPath, "utf8");
|
|
297
|
+
}
|
|
203
298
|
const normalized = normalizePath(targetPath);
|
|
204
299
|
const node = getNode(this.root, normalized);
|
|
205
300
|
if (node.type !== "file") {
|
|
@@ -212,6 +307,12 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
212
307
|
}
|
|
213
308
|
/** Reads file content as a Buffer (decompresses if needed). */
|
|
214
309
|
readFileRaw(targetPath) {
|
|
310
|
+
const m = this.resolveMount(targetPath);
|
|
311
|
+
if (m) {
|
|
312
|
+
if (!fsSync.existsSync(m.fullHostPath))
|
|
313
|
+
throw new Error(`ENOENT: no such file or directory, open '${m.fullHostPath}'`);
|
|
314
|
+
return fsSync.readFileSync(m.fullHostPath);
|
|
315
|
+
}
|
|
215
316
|
const normalized = normalizePath(targetPath);
|
|
216
317
|
const node = getNode(this.root, normalized);
|
|
217
318
|
if (node.type !== "file") {
|
|
@@ -224,6 +325,9 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
224
325
|
}
|
|
225
326
|
/** Returns true when a file or directory exists at path. */
|
|
226
327
|
exists(targetPath) {
|
|
328
|
+
const m = this.resolveMount(targetPath);
|
|
329
|
+
if (m)
|
|
330
|
+
return fsSync.existsSync(m.fullHostPath);
|
|
227
331
|
try {
|
|
228
332
|
getNode(this.root, normalizePath(targetPath));
|
|
229
333
|
return true;
|
|
@@ -238,6 +342,35 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
238
342
|
}
|
|
239
343
|
/** Returns metadata for a file or directory. */
|
|
240
344
|
stat(targetPath) {
|
|
345
|
+
const m = this.resolveMount(targetPath);
|
|
346
|
+
if (m) {
|
|
347
|
+
if (!fsSync.existsSync(m.fullHostPath))
|
|
348
|
+
throw new Error(`ENOENT: stat '${m.fullHostPath}'`);
|
|
349
|
+
const hst = fsSync.statSync(m.fullHostPath);
|
|
350
|
+
const name = m.relPath.split("/").pop() ?? m.fullHostPath.split("/").pop() ?? "";
|
|
351
|
+
const now = hst.mtime;
|
|
352
|
+
if (hst.isDirectory()) {
|
|
353
|
+
return {
|
|
354
|
+
type: "directory",
|
|
355
|
+
name,
|
|
356
|
+
path: normalizePath(targetPath),
|
|
357
|
+
mode: 0o755,
|
|
358
|
+
createdAt: hst.birthtime,
|
|
359
|
+
updatedAt: now,
|
|
360
|
+
childrenCount: fsSync.readdirSync(m.fullHostPath).length,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
return {
|
|
364
|
+
type: "file",
|
|
365
|
+
name,
|
|
366
|
+
path: normalizePath(targetPath),
|
|
367
|
+
mode: m.readOnly ? 0o444 : 0o644,
|
|
368
|
+
createdAt: hst.birthtime,
|
|
369
|
+
updatedAt: now,
|
|
370
|
+
compressed: false,
|
|
371
|
+
size: hst.size,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
241
374
|
const normalized = normalizePath(targetPath);
|
|
242
375
|
const node = getNode(this.root, normalized);
|
|
243
376
|
const name = normalized === "/" ? "" : path.posix.basename(normalized);
|
|
@@ -267,6 +400,17 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
267
400
|
}
|
|
268
401
|
/** Lists direct children names of a directory (sorted). */
|
|
269
402
|
list(dirPath = "/") {
|
|
403
|
+
const m = this.resolveMount(dirPath);
|
|
404
|
+
if (m) {
|
|
405
|
+
if (!fsSync.existsSync(m.fullHostPath))
|
|
406
|
+
return [];
|
|
407
|
+
try {
|
|
408
|
+
return fsSync.readdirSync(m.fullHostPath).sort();
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
return [];
|
|
412
|
+
}
|
|
413
|
+
}
|
|
270
414
|
const normalized = normalizePath(dirPath);
|
|
271
415
|
const node = getNode(this.root, normalized);
|
|
272
416
|
if (node.type !== "directory") {
|
|
@@ -402,6 +546,21 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
402
546
|
}
|
|
403
547
|
/** Removes a file or directory node. */
|
|
404
548
|
remove(targetPath, options = {}) {
|
|
549
|
+
const m = this.resolveMount(targetPath);
|
|
550
|
+
if (m) {
|
|
551
|
+
if (m.readOnly)
|
|
552
|
+
throw new Error(`EROFS: read-only file system, unlink '${m.fullHostPath}'`);
|
|
553
|
+
if (!fsSync.existsSync(m.fullHostPath))
|
|
554
|
+
throw new Error(`ENOENT: no such file or directory, unlink '${m.fullHostPath}'`);
|
|
555
|
+
const hst = fsSync.statSync(m.fullHostPath);
|
|
556
|
+
if (hst.isDirectory()) {
|
|
557
|
+
fsSync.rmSync(m.fullHostPath, { recursive: options.recursive ?? false });
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
fsSync.unlinkSync(m.fullHostPath);
|
|
561
|
+
}
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
405
564
|
const normalized = normalizePath(targetPath);
|
|
406
565
|
if (normalized === "/")
|
|
407
566
|
throw new Error("Cannot remove root directory.");
|
|
@@ -147,6 +147,35 @@ declare class VirtualShell extends EventEmitter {
|
|
|
147
147
|
* reading `/proc` files for up-to-date values.
|
|
148
148
|
*/
|
|
149
149
|
refreshProcFs(): void;
|
|
150
|
+
/**
|
|
151
|
+
* Mount a host directory into the VFS at `vPath`.
|
|
152
|
+
*
|
|
153
|
+
* Delegates file operations inside `vPath` to the host filesystem via
|
|
154
|
+
* `node:fs`. Silently ignored in browser environments.
|
|
155
|
+
*
|
|
156
|
+
* @param vPath Absolute path inside the VM (e.g. `"/app"`).
|
|
157
|
+
* @param hostPath Path on the host — relative paths are resolved from `process.cwd()`.
|
|
158
|
+
* @param options `{ readOnly?: boolean }` — default `true`.
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```ts
|
|
162
|
+
* const shell = new VirtualShell("dev-vm");
|
|
163
|
+
* await shell.ensureInitialized();
|
|
164
|
+
* shell.mount("/workspace", "./my-project");
|
|
165
|
+
* // shell commands can now read ./my-project files via /workspace
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
mount(vPath: string, hostPath: string, options?: {
|
|
169
|
+
readOnly?: boolean;
|
|
170
|
+
}): void;
|
|
171
|
+
/** Remove a previously mounted host directory. */
|
|
172
|
+
unmount(vPath: string): void;
|
|
173
|
+
/** List all active mounts. */
|
|
174
|
+
getMounts(): Array<{
|
|
175
|
+
vPath: string;
|
|
176
|
+
hostPath: string;
|
|
177
|
+
readOnly: boolean;
|
|
178
|
+
}>;
|
|
150
179
|
/**
|
|
151
180
|
* Updates only the session-dependent `/proc` entries (`/proc/<pid>`,
|
|
152
181
|
* `/proc/self`). Cheaper than a full `refreshProcFs()` — call this
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualShell/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualShell/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAQ3C,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAGjD,OAAO,iBAAiB,EAAE,EAAE,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAG3D;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,eAAe;IAC/B,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,EAAE,EAAE,MAAM,CAAC;IACX,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,mBAAmB;IACnC,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC;IAClE,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC;IACrC,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/C,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;IACpC,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,YAAY,CAAC;IACvC,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACnC,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACpE,KAAK,CAAC,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/C,OAAO,CAAC,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACrD,aAAa,CAAC,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC5C;AAED,MAAM,WAAW,sBAAsB;IACtC,WAAW,CAAC,EAAE,mBAAmB,CAAC;CAClC;AAkDD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,cAAM,YAAa,SAAQ,YAAY;IACtC,mEAAmE;IACnE,GAAG,EAAE,iBAAiB,CAAC;IACvB,0EAA0E;IAC1E,KAAK,EAAE,kBAAkB,CAAC;IAC1B,wEAAwE;IACxE,cAAc,EAAE,qBAAqB,CAAC;IACtC,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,UAAU,EAAE,eAAe,CAAC;IAC5B,iFAAiF;IACjF,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,WAAW,CAAgB;IAEnC;;;;;;OAMG;gBAEF,QAAQ,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,eAAe,EAC5B,oBAAoB,CAAC,EAAE,UAAU,GAAG,mBAAmB,GAAG,sBAAsB;IAsCjF;;;OAGG;IACU,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAK/C;;;;;;OAMG;IACH,UAAU,CACT,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EAAE,EAChB,QAAQ,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,GACvE,IAAI;IASP;;;;;;;;;;;OAWG;IACH,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAMrE;;;;;;;;;;;;;OAaG;IACH,uBAAuB,CACtB,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,aAAa,EAAE,MAAM,EACrB,YAAY,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAC1C,IAAI;IAkBP;;;;;;;;;OASG;IACI,aAAa,IAAI,IAAI;IAU5B;;;;;;;;;;;;;;;;;OAiBG;IACI,KAAK,CACX,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAO,GAClC,IAAI;IAIP,kDAAkD;IAC3C,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAInC,8BAA8B;IACvB,SAAS,IAAI,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC;IAIjF;;;;OAIG;IACI,mBAAmB,IAAI,IAAI;IAUlC;;;;;;;OAOG;IACI,UAAU,IAAI,IAAI;IAIzB;;;;OAIG;IACI,MAAM,IAAI,iBAAiB,GAAG,IAAI;IAIzC;;;;OAIG;IACI,QAAQ,IAAI,kBAAkB,GAAG,IAAI;IAI5C;;;;OAIG;IACI,WAAW,IAAI,MAAM;IAI5B;;;;;;OAMG;IACI,eAAe,CACrB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GAAG,MAAM,GACtB,IAAI;CAKP;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}
|
|
@@ -196,6 +196,35 @@ class VirtualShell extends EventEmitter {
|
|
|
196
196
|
refreshProcFs() {
|
|
197
197
|
refreshProc(this.vfs, this.properties, this.hostname, this.startTime, this.users.listActiveSessions());
|
|
198
198
|
}
|
|
199
|
+
/**
|
|
200
|
+
* Mount a host directory into the VFS at `vPath`.
|
|
201
|
+
*
|
|
202
|
+
* Delegates file operations inside `vPath` to the host filesystem via
|
|
203
|
+
* `node:fs`. Silently ignored in browser environments.
|
|
204
|
+
*
|
|
205
|
+
* @param vPath Absolute path inside the VM (e.g. `"/app"`).
|
|
206
|
+
* @param hostPath Path on the host — relative paths are resolved from `process.cwd()`.
|
|
207
|
+
* @param options `{ readOnly?: boolean }` — default `true`.
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* ```ts
|
|
211
|
+
* const shell = new VirtualShell("dev-vm");
|
|
212
|
+
* await shell.ensureInitialized();
|
|
213
|
+
* shell.mount("/workspace", "./my-project");
|
|
214
|
+
* // shell commands can now read ./my-project files via /workspace
|
|
215
|
+
* ```
|
|
216
|
+
*/
|
|
217
|
+
mount(vPath, hostPath, options = {}) {
|
|
218
|
+
this.vfs.mount(vPath, hostPath, options);
|
|
219
|
+
}
|
|
220
|
+
/** Remove a previously mounted host directory. */
|
|
221
|
+
unmount(vPath) {
|
|
222
|
+
this.vfs.unmount(vPath);
|
|
223
|
+
}
|
|
224
|
+
/** List all active mounts. */
|
|
225
|
+
getMounts() {
|
|
226
|
+
return this.vfs.getMounts();
|
|
227
|
+
}
|
|
199
228
|
/**
|
|
200
229
|
* Updates only the session-dependent `/proc` entries (`/proc/<pid>`,
|
|
201
230
|
* `/proc/self`). Cheaper than a full `refreshProcFs()` — call this
|
|
@@ -202,6 +202,9 @@ function parseCommandWithRedirections(token) {
|
|
|
202
202
|
let outputFile;
|
|
203
203
|
let appendOutput = false;
|
|
204
204
|
let i = 0;
|
|
205
|
+
let stderrFile;
|
|
206
|
+
let stderrAppend = false;
|
|
207
|
+
let stderrToStdout = false;
|
|
205
208
|
while (i < parts.length) {
|
|
206
209
|
const part = parts[i];
|
|
207
210
|
if (part === "<") {
|
|
@@ -227,11 +230,35 @@ function parseCommandWithRedirections(token) {
|
|
|
227
230
|
appendOutput = false;
|
|
228
231
|
i++;
|
|
229
232
|
}
|
|
233
|
+
else if (part === "2>&1") {
|
|
234
|
+
stderrToStdout = true;
|
|
235
|
+
i++;
|
|
236
|
+
}
|
|
237
|
+
else if (part === "2>>") {
|
|
238
|
+
i++;
|
|
239
|
+
if (i >= parts.length)
|
|
240
|
+
throw new Error("Syntax error: expected filename after 2>>");
|
|
241
|
+
stderrFile = parts[i];
|
|
242
|
+
stderrAppend = true;
|
|
243
|
+
i++;
|
|
244
|
+
}
|
|
245
|
+
else if (part === "2>") {
|
|
246
|
+
i++;
|
|
247
|
+
if (i >= parts.length)
|
|
248
|
+
throw new Error("Syntax error: expected filename after 2>");
|
|
249
|
+
stderrFile = parts[i];
|
|
250
|
+
stderrAppend = false;
|
|
251
|
+
i++;
|
|
252
|
+
}
|
|
230
253
|
else {
|
|
231
254
|
cmdParts.push(part);
|
|
232
255
|
i++;
|
|
233
256
|
}
|
|
234
257
|
}
|
|
235
258
|
const name = (cmdParts[0] ?? "").toLowerCase();
|
|
236
|
-
return {
|
|
259
|
+
return {
|
|
260
|
+
name, args: cmdParts.slice(1),
|
|
261
|
+
inputFile, outputFile, appendOutput,
|
|
262
|
+
stderrFile, stderrAppend, stderrToStdout,
|
|
263
|
+
};
|
|
237
264
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../../src/commands/export.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAAE,
|
|
1
|
+
{"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../../src/commands/export.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAAE,WA0B3B,CAAC"}
|
package/dist/commands/export.js
CHANGED
|
@@ -9,13 +9,15 @@ export const exportCommand = {
|
|
|
9
9
|
category: "shell",
|
|
10
10
|
params: ["[VAR=value]"],
|
|
11
11
|
run: ({ args, env }) => {
|
|
12
|
-
|
|
12
|
+
// export -p or export with no args — list all exported vars
|
|
13
|
+
if (args.length === 0 || (args.length === 1 && args[0] === "-p")) {
|
|
13
14
|
const out = Object.entries(env.vars)
|
|
15
|
+
.filter(([k]) => k && /^[A-Za-z_][A-Za-z0-9_]*$/.test(k))
|
|
14
16
|
.map(([k, v]) => `declare -x ${k}="${v}"`)
|
|
15
17
|
.join("\n");
|
|
16
|
-
return { stdout: out, exitCode: 0 };
|
|
18
|
+
return { stdout: out ? `${out}\n` : "", exitCode: 0 };
|
|
17
19
|
}
|
|
18
|
-
for (const arg of args) {
|
|
20
|
+
for (const arg of args.filter((a) => a !== "-p")) {
|
|
19
21
|
if (arg.includes("=")) {
|
|
20
22
|
const eq = arg.indexOf("=");
|
|
21
23
|
const name = arg.slice(0, eq);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/commands/registry.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/commands/registry.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAqNpF,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAYzD;AAED,wBAAgB,mBAAmB,CAClC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EAAE,EAChB,GAAG,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,GAClE,WAAW,CAEb;AAED,wBAAgB,eAAe,IAAI,MAAM,EAAE,CAG1C;AAED,wBAAgB,uBAAuB,IAAI,WAAW,EAAE,CAEvD;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAGnE"}
|
|
@@ -34,6 +34,7 @@ import { htopCommand } from "./htop";
|
|
|
34
34
|
import { idCommand } from "./id";
|
|
35
35
|
import { killCommand } from "./kill";
|
|
36
36
|
import { lnCommand, readlinkCommand } from "./ln";
|
|
37
|
+
import { seqCommand } from "./seq";
|
|
37
38
|
import { statCommand } from "./stat";
|
|
38
39
|
import { lsCommand } from "./ls";
|
|
39
40
|
import { lsbReleaseCommand } from "./lsb-release";
|
|
@@ -96,6 +97,7 @@ const BASE_COMMANDS = [
|
|
|
96
97
|
lnCommand,
|
|
97
98
|
readlinkCommand,
|
|
98
99
|
chmodCommand,
|
|
100
|
+
seqCommand,
|
|
99
101
|
statCommand,
|
|
100
102
|
findCommand,
|
|
101
103
|
// Text processing
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/commands/runtime.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD,OAAO,KAAK,EACR,WAAW,EACX,aAAa,EACb,QAAQ,EACX,MAAM,mBAAmB,CAAC;AAK3B,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAc3E;AAyCD,wBAAsB,gBAAgB,CACrC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EAAE,EACd,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,WAAW,EACjB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,GAAG,EAAE,QAAQ,GACX,OAAO,CAAC,aAAa,CAAC,CA4ExB;AAED,wBAAsB,UAAU,CAC/B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,WAAW,EACjB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,YAAY,EACnB,KAAK,CAAC,EAAE,MAAM,EACd,GAAG,CAAC,EAAE,QAAQ,GACZ,OAAO,CAAC,aAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/commands/runtime.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD,OAAO,KAAK,EACR,WAAW,EACX,aAAa,EACb,QAAQ,EACX,MAAM,mBAAmB,CAAC;AAK3B,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAc3E;AAyCD,wBAAsB,gBAAgB,CACrC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EAAE,EACd,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,WAAW,EACjB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,GAAG,EAAE,QAAQ,GACX,OAAO,CAAC,aAAa,CAAC,CA4ExB;AAED,wBAAsB,UAAU,CAC/B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,WAAW,EACjB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,YAAY,EACnB,KAAK,CAAC,EAAE,MAAM,EACd,GAAG,CAAC,EAAE,QAAQ,GACZ,OAAO,CAAC,aAAa,CAAC,CA0JxB"}
|
package/dist/commands/runtime.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** biome-ignore-all lint/style/useNamingConvention: ENV VARIABLES */
|
|
2
2
|
import { executeStatements } from "../SSHMimic/executor";
|
|
3
3
|
import { parseScript } from "../VirtualShell/shellParser";
|
|
4
|
-
import { expandAsync } from "../utils/expand";
|
|
4
|
+
import { expandAsync, expandBraces } from "../utils/expand";
|
|
5
5
|
import { tokenizeCommand } from "../utils/tokenize";
|
|
6
6
|
import { resolveModule } from "./registry";
|
|
7
7
|
export function makeDefaultEnv(authUser, hostname) {
|
|
@@ -135,13 +135,37 @@ export async function runCommand(rawInput, authUser, hostname, mode, cwd, shell,
|
|
|
135
135
|
const aliasExpanded = aliasVal
|
|
136
136
|
? trimmed.replace(rawFirstWord, aliasVal)
|
|
137
137
|
: trimmed;
|
|
138
|
+
// Detect sh-syntax constructs that must be handled by the sh interpreter
|
|
139
|
+
const isShScript = /\bfor\s+\w+\s+in\b/.test(aliasExpanded) ||
|
|
140
|
+
/\bwhile\s+/.test(aliasExpanded) ||
|
|
141
|
+
/\bif\s+/.test(aliasExpanded) ||
|
|
142
|
+
/\w+\s*\(\s*\)\s*\{/.test(aliasExpanded) ||
|
|
143
|
+
/\bfunction\s+\w+/.test(aliasExpanded) ||
|
|
144
|
+
/\(\(\s*.+\s*\)\)/.test(aliasExpanded);
|
|
138
145
|
const hasOperators = /(?<![|&])[|](?![|])/.test(aliasExpanded) ||
|
|
139
146
|
aliasExpanded.includes(">") ||
|
|
140
147
|
aliasExpanded.includes("<") ||
|
|
141
148
|
aliasExpanded.includes("&&") ||
|
|
142
149
|
aliasExpanded.includes("||") ||
|
|
143
150
|
aliasExpanded.includes(";");
|
|
144
|
-
if (hasOperators) {
|
|
151
|
+
if ((isShScript && rawFirstWord !== "sh" && rawFirstWord !== "bash") || hasOperators) {
|
|
152
|
+
// sh-syntax: route through sh interpreter to handle for/while/functions
|
|
153
|
+
if (isShScript && rawFirstWord !== "sh" && rawFirstWord !== "bash") {
|
|
154
|
+
const shMod = resolveModule("sh");
|
|
155
|
+
if (shMod) {
|
|
156
|
+
return await shMod.run({
|
|
157
|
+
authUser, hostname,
|
|
158
|
+
activeSessions: shell.users.listActiveSessions(),
|
|
159
|
+
rawInput: aliasExpanded,
|
|
160
|
+
mode,
|
|
161
|
+
args: ["-c", aliasExpanded],
|
|
162
|
+
stdin: undefined,
|
|
163
|
+
cwd,
|
|
164
|
+
shell,
|
|
165
|
+
env: shellEnv,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
145
169
|
const script = parseScript(aliasExpanded);
|
|
146
170
|
if (!script.isValid)
|
|
147
171
|
return { stderr: script.error || "Syntax error", exitCode: 1 };
|
|
@@ -158,7 +182,8 @@ export async function runCommand(rawInput, authUser, hostname, mode, cwd, shell,
|
|
|
158
182
|
const expanded = await expandAsync(aliasExpanded, shellEnv.vars, shellEnv.lastExitCode, (sub) => runCommand(sub, authUser, hostname, mode, cwd, shell, undefined, shellEnv).then((r) => r.stdout ?? ""));
|
|
159
183
|
const parts = tokenizeCommand(expanded.trim());
|
|
160
184
|
const commandName = parts[0]?.toLowerCase() ?? "";
|
|
161
|
-
|
|
185
|
+
// Apply brace expansion to each arg token
|
|
186
|
+
const args = parts.slice(1).flatMap(expandBraces);
|
|
162
187
|
const mod = resolveModule(commandName);
|
|
163
188
|
if (!mod) {
|
|
164
189
|
const vfsBinary = resolveVfsBinary(commandName, shellEnv, shell, authUser);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seq.d.ts","sourceRoot":"","sources":["../../src/commands/seq.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,0FAA0F;AAC1F,eAAO,MAAM,UAAU,EAAE,WAuCxB,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** Generate sequences of numbers. seq LAST / seq FIRST LAST / seq FIRST INCREMENT LAST */
|
|
2
|
+
export const seqCommand = {
|
|
3
|
+
name: "seq",
|
|
4
|
+
description: "Print a sequence of numbers",
|
|
5
|
+
category: "text",
|
|
6
|
+
params: ["[FIRST [INCREMENT]] LAST"],
|
|
7
|
+
run: ({ args }) => {
|
|
8
|
+
const nums = args.filter((a) => !a.startsWith("-") || /^-[\d.]/.test(a)).map(Number);
|
|
9
|
+
const sep = (() => { const i = args.indexOf("-s"); return i !== -1 ? (args[i + 1] ?? "\n") : "\n"; })();
|
|
10
|
+
const fmt = (() => { const i = args.indexOf("-f"); return i !== -1 ? (args[i + 1] ?? "%g") : null; })();
|
|
11
|
+
const width = args.includes("-w");
|
|
12
|
+
let first = 1, inc = 1, last;
|
|
13
|
+
if (nums.length === 1) {
|
|
14
|
+
last = nums[0];
|
|
15
|
+
}
|
|
16
|
+
else if (nums.length === 2) {
|
|
17
|
+
first = nums[0];
|
|
18
|
+
last = nums[1];
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
first = nums[0];
|
|
22
|
+
inc = nums[1];
|
|
23
|
+
last = nums[2];
|
|
24
|
+
}
|
|
25
|
+
if (inc === 0)
|
|
26
|
+
return { stderr: "seq: zero increment\n", exitCode: 1 };
|
|
27
|
+
if ((inc > 0 && first > last) || (inc < 0 && first < last))
|
|
28
|
+
return { stdout: "", exitCode: 0 };
|
|
29
|
+
const results = [];
|
|
30
|
+
const maxSteps = 100000;
|
|
31
|
+
let steps = 0;
|
|
32
|
+
for (let n = first; inc > 0 ? n <= last : n >= last; n = Math.round((n + inc) * 1e10) / 1e10) {
|
|
33
|
+
if (++steps > maxSteps)
|
|
34
|
+
break;
|
|
35
|
+
let s;
|
|
36
|
+
if (fmt) {
|
|
37
|
+
s = fmt.replace("%g", String(n)).replace("%f", n.toFixed(6)).replace("%d", String(Math.trunc(n)));
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
s = Number.isInteger(n) ? String(n) : n.toPrecision(12).replace(/\.?0+$/, "");
|
|
41
|
+
}
|
|
42
|
+
if (width) {
|
|
43
|
+
const maxLen = String(Math.trunc(last)).length;
|
|
44
|
+
s = s.padStart(maxLen, "0");
|
|
45
|
+
}
|
|
46
|
+
results.push(s);
|
|
47
|
+
}
|
|
48
|
+
return { stdout: `${results.join(sep)}\n`, exitCode: 0 };
|
|
49
|
+
},
|
|
50
|
+
};
|
package/dist/commands/sh.d.ts
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
1
|
import type { ShellModule } from "../types/commands";
|
|
2
|
-
/**
|
|
3
|
-
* Execute shell scripts or commands with a minimal shell interpreter.
|
|
4
|
-
* Supports if/elif/else, for loops, while loops, and variable expansion.
|
|
5
|
-
* @category shell
|
|
6
|
-
* @params ["-c <script>", "[<file>]"]
|
|
7
|
-
*/
|
|
8
2
|
export declare const shCommand: ShellModule;
|
|
9
3
|
//# sourceMappingURL=sh.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sh.d.ts","sourceRoot":"","sources":["../../src/commands/sh.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGX,WAAW,EACX,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"sh.d.ts","sourceRoot":"","sources":["../../src/commands/sh.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGX,WAAW,EACX,MAAM,mBAAmB,CAAC;AAqa3B,eAAO,MAAM,SAAS,EAAE,WAsCvB,CAAC"}
|