typescript-virtual-container 1.4.1 → 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/.vscode/settings.json +2 -0
- package/README.md +77 -36
- package/benchmark-virtualshell.ts +3 -11
- package/builds/self-standalone.js +224 -224
- package/builds/self-standalone.js.map +4 -4
- package/builds/standalone-wo-sftp.js +23 -23
- package/builds/standalone-wo-sftp.js.map +3 -3
- package/builds/standalone.js +23 -23
- 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/shell.d.ts.map +1 -1
- package/dist/VirtualShell/shell.js +6 -10
- package/dist/VirtualShell/shellParser.js +28 -1
- package/dist/VirtualUserManager/index.d.ts.map +1 -1
- package/dist/VirtualUserManager/index.js +4 -4
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +5 -3
- package/dist/commands/helpers.js +1 -1
- package/dist/commands/history.js +2 -2
- 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/modules/linuxRootfs.d.ts +1 -1
- package/dist/modules/linuxRootfs.d.ts.map +1 -1
- package/dist/modules/linuxRootfs.js +5 -5
- package/dist/self-standalone.js +149 -102
- 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/shell.ts +6 -11
- package/src/VirtualShell/shellParser.ts +26 -1
- package/src/VirtualUserManager/index.ts +4 -4
- package/src/commands/export.ts +5 -3
- package/src/commands/helpers.ts +1 -1
- package/src/commands/history.ts +2 -2
- 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/modules/linuxRootfs.ts +6 -6
- package/src/self-standalone.ts +190 -141
- 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
- package/tests/helpers.test.ts +3 -3
package/.vscode/settings.json
CHANGED
package/README.md
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
- [`VirtualSftpServer`](#virtualsftpserver)
|
|
25
25
|
- [`VirtualShell`](#virtualshell)
|
|
26
26
|
- [`VirtualFileSystem`](#virtualfilesystem)
|
|
27
|
+
- [Mount API](#mount-api)
|
|
27
28
|
- [`VirtualUserManager`](#virtualusermanager)
|
|
28
29
|
- [`VirtualPackageManager`](#virtualpackagemanager)
|
|
29
30
|
- [Snapshot Diff Tooling](#snapshot-diff-tooling)
|
|
@@ -53,7 +54,7 @@
|
|
|
53
54
|
| Mode | Entry point | Use case |
|
|
54
55
|
|------|-------------|----------|
|
|
55
56
|
| **SSH/SFTP server** | `VirtualSshServer` / `VirtualSftpServer` | Honeypots, remote testing, training environments |
|
|
56
|
-
| **Web shell** | `builds/web.min.js` (
|
|
57
|
+
| **Web shell** | `builds/web.min.js` / `builds/web-full-api.min.js` (ESM) | Embedded terminals, interactive tutorials, browser demos |
|
|
57
58
|
| **Standalone CLI** | `builds/self-standalone.js` (single file) | Local shell, one-liner demos, no install required |
|
|
58
59
|
|
|
59
60
|
All three modes share the same core: a pure in-memory VFS, a real shell interpreter, a virtual package manager, and a typed programmatic API.
|
|
@@ -82,6 +83,10 @@ curl -s https://raw.githubusercontent.com/itsrealfortune/typescript-virtual-cont
|
|
|
82
83
|
curl -s https://raw.githubusercontent.com/itsrealfortune/typescript-virtual-container/refs/heads/main/builds/standalone-wo-sftp.js -o standalone.js && node standalone.js && rm -f standalone.js
|
|
83
84
|
```
|
|
84
85
|
|
|
86
|
+
> [!NOTE]
|
|
87
|
+
> The standalone builds are intended for quick demos and testing. For production use, it's recommended to install the package and import the relevant classes directly in your codebase for better performance, stability, and security.
|
|
88
|
+
> It implies command `man` is not available in standalone mode because manuals are not included in the minimal build, but you can always refer to the [API Reference](#api-reference) for detailed documentation.
|
|
89
|
+
|
|
85
90
|
**`self-standalone.js` options:**
|
|
86
91
|
|
|
87
92
|
```bash
|
|
@@ -107,24 +112,22 @@ await ssh.start();
|
|
|
107
112
|
|
|
108
113
|
### Web shell (browser)
|
|
109
114
|
|
|
110
|
-
|
|
115
|
+
Two browser bundles are available:
|
|
111
116
|
|
|
112
|
-
| Bundle | Format | Entry | Use case |
|
|
113
|
-
|
|
114
|
-
| `builds/web.min.js` | ESM | `createWebShell()` |
|
|
115
|
-
| `builds/web-
|
|
116
|
-
| `builds/web-full-api.min.js` | ESM | `createVirtualShellShim()` | API surface close to `VirtualShell` |
|
|
117
|
+
| Bundle | Format | Entry point | Use case |
|
|
118
|
+
|--------|--------|-------------|----------|
|
|
119
|
+
| `builds/web.min.js` | ESM | `createWebShell()` | Embedded terminals, modern bundlers |
|
|
120
|
+
| `builds/web-full-api.min.js` | ESM | `createVirtualShellShim()` | Full `VirtualShell`-like API in the browser |
|
|
117
121
|
|
|
118
|
-
|
|
122
|
+
Both bundles persist the VFS in **IndexedDB** — state survives page reloads.
|
|
119
123
|
|
|
120
124
|
```bash
|
|
121
|
-
bun run web-build
|
|
122
|
-
bun run web-build
|
|
123
|
-
bun run
|
|
124
|
-
bun run build-all # rebuild everything
|
|
125
|
+
bun run web-build # → builds/web.min.js + examples/web.min.js
|
|
126
|
+
bun run web-full-build # → builds/web-full-api.min.js
|
|
127
|
+
bun run build-all # rebuild everything
|
|
125
128
|
```
|
|
126
129
|
|
|
127
|
-
|
|
130
|
+
**`web.min.js`** — lightweight shell with IndexedDB VFS:
|
|
128
131
|
|
|
129
132
|
```html
|
|
130
133
|
<script type="module">
|
|
@@ -140,19 +143,7 @@ bun run build-all # rebuild everything
|
|
|
140
143
|
</script>
|
|
141
144
|
```
|
|
142
145
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
```html
|
|
146
|
-
<script src="./builds/web-iife.min.js"></script>
|
|
147
|
-
<script>
|
|
148
|
-
const shell = WebShellLib.createWebShell("web-vm");
|
|
149
|
-
shell.ensureInitialized().then(() =>
|
|
150
|
-
shell.executeCommandLine("whoami").then(r => console.log(r.stdout))
|
|
151
|
-
);
|
|
152
|
-
</script>
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
**Full API shim (`web-full-api.min.js`)** — mirrors the `VirtualShell` programmatic API:
|
|
146
|
+
**`web-full-api.min.js`** — mirrors the `VirtualShell` programmatic API:
|
|
156
147
|
|
|
157
148
|
```html
|
|
158
149
|
<script type="module">
|
|
@@ -161,8 +152,7 @@ bun run build-all # rebuild everything
|
|
|
161
152
|
const shell = createVirtualShellShim("web-vm");
|
|
162
153
|
await shell.ensureInitialized();
|
|
163
154
|
await shell.executeCommandLine("mkdir -p /app && echo hello > /app/file.txt");
|
|
164
|
-
|
|
165
|
-
console.log(vfs.readFile("/app/file.txt")); // hello
|
|
155
|
+
console.log(shell.getVfs().readFile("/app/file.txt")); // hello
|
|
166
156
|
</script>
|
|
167
157
|
```
|
|
168
158
|
|
|
@@ -170,8 +160,7 @@ bun run build-all # rebuild everything
|
|
|
170
160
|
|
|
171
161
|
```bash
|
|
172
162
|
bun run example-serve
|
|
173
|
-
# Open http://localhost:8787/index.html
|
|
174
|
-
# or http://localhost:8787/index-cf.html (IIFE, Cloudflare-compatible)
|
|
163
|
+
# Open http://localhost:8787/index.html
|
|
175
164
|
```
|
|
176
165
|
|
|
177
166
|
### Programmatic API
|
|
@@ -323,6 +312,9 @@ interface ShellProperties {
|
|
|
323
312
|
| `writeFileAsUser(authUser, path, content)` | Write with quota enforcement. |
|
|
324
313
|
| `refreshProcFs(): void` | Refresh all `/proc/*` files (uptime, meminfo, cpuinfo, per-pid). |
|
|
325
314
|
| `refreshProcSessions(): void` | Lightweight refresh of `/proc/<pid>` and `/proc/self` only. |
|
|
315
|
+
| `mount(vPath, hostPath, options?): void` | Mount a host directory into the VFS. See [Mount API](#mount-api). |
|
|
316
|
+
| `unmount(vPath): void` | Remove a host directory mount. |
|
|
317
|
+
| `getMounts()` | List all active mounts as `{ vPath, hostPath, readOnly }[]`. |
|
|
326
318
|
| `syncPasswd(): void` | Sync `/etc/passwd`, `/etc/group`, `/etc/shadow` from user manager. |
|
|
327
319
|
| `getVfs(): VirtualFileSystem \| null` | Access VFS instance. |
|
|
328
320
|
| `getUsers(): VirtualUserManager \| null` | Access user manager. |
|
|
@@ -394,6 +386,40 @@ await vfs.flushMirror(); // save to disk
|
|
|
394
386
|
|
|
395
387
|
**Events:** `file:write { path, size }`, `file:read { path, size }`, `dir:create { path, mode }`, `node:remove { path }`, `symlink:create { link, target }`, `snapshot:import`, `snapshot:restore { path }`, `mirror:flush { path? }`
|
|
396
388
|
|
|
389
|
+
|
|
390
|
+
#### Mount API
|
|
391
|
+
|
|
392
|
+
Mount a host directory inside the VM — all standard VFS operations (`readFile`, `writeFile`, `exists`, `stat`, `list`) are transparently delegated to the host filesystem.
|
|
393
|
+
|
|
394
|
+
> **Node.js only.** In browser environments `mount()` is a silent no-op — the `vPath` remains an empty in-memory directory.
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
// Read-only mount (default) — shell commands can read host files
|
|
398
|
+
shell.mount("/workspace", "./my-project");
|
|
399
|
+
|
|
400
|
+
// Read-write mount — shell commands can also write back to the host
|
|
401
|
+
shell.mount("/data", "./data", { readOnly: false });
|
|
402
|
+
|
|
403
|
+
// Unmount — delegation removed, vPath stays as an empty VFS directory
|
|
404
|
+
shell.unmount("/workspace");
|
|
405
|
+
|
|
406
|
+
// Introspect
|
|
407
|
+
shell.getMounts();
|
|
408
|
+
// → [{ vPath: "/workspace", hostPath: "/abs/path/my-project", readOnly: true }]
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Direct VFS usage:
|
|
412
|
+
|
|
413
|
+
```typescript
|
|
414
|
+
shell.vfs.mount("/workspace", "./my-project");
|
|
415
|
+
shell.vfs.unmount("/workspace");
|
|
416
|
+
shell.vfs.getMounts();
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
**Events:** `mount { vPath, hostPath, readOnly }`, `unmount { vPath }`
|
|
420
|
+
|
|
421
|
+
**Snapshot behaviour:** mounted files are **not** included in `toSnapshot()` — only the in-memory VFS tree is serialised. The mount configuration itself is also not persisted; restore it after each `fromSnapshot()` or `restoreMirror()`.
|
|
422
|
+
|
|
397
423
|
#### VFSB Binary Format
|
|
398
424
|
|
|
399
425
|
In `"fs"` mode, state is persisted as a compact binary file (`vfs-snapshot.vfsb`).
|
|
@@ -427,7 +453,7 @@ new VirtualUserManager(
|
|
|
427
453
|
)
|
|
428
454
|
```
|
|
429
455
|
|
|
430
|
-
Auth data is stored at `/
|
|
456
|
+
Auth data is stored at `/etc/` inside the VFS.
|
|
431
457
|
|
|
432
458
|
**Methods**
|
|
433
459
|
|
|
@@ -650,6 +676,17 @@ interface VirtualActiveSession {
|
|
|
650
676
|
remoteAddress: string;
|
|
651
677
|
startedAt: string; // ISO-8601
|
|
652
678
|
}
|
|
679
|
+
|
|
680
|
+
/** Returned by adduser, passwd, deluser — triggers interactive password prompt in the terminal. */
|
|
681
|
+
interface PasswordChallenge {
|
|
682
|
+
preamble?: string; // Lines printed before the first prompt
|
|
683
|
+
prompt: string; // e.g. "New password: "
|
|
684
|
+
confirmPrompt?: string; // Second prompt for confirmation
|
|
685
|
+
confirmText?: string; // Destructive confirmation prompt (y/N)
|
|
686
|
+
action: "adduser" | "passwd" | "deluser" | "su";
|
|
687
|
+
targetUsername: string;
|
|
688
|
+
newUsername?: string; // adduser only
|
|
689
|
+
}
|
|
653
690
|
```
|
|
654
691
|
|
|
655
692
|
---
|
|
@@ -928,7 +965,7 @@ echo "Welcome back, root!"
|
|
|
928
965
|
<details>
|
|
929
966
|
<summary><strong>Built-in Commands (91)</strong></summary>
|
|
930
967
|
|
|
931
|
-
Type `help` in the shell for a grouped, colorized listing. Type `help <command>` for detailed usage.
|
|
968
|
+
Type `help` in the shell for a grouped, colorized listing. Type `help <command>` for detailed usage. Type `man <command>` for full manual pages — all 91 commands are documented.
|
|
932
969
|
|
|
933
970
|
### Navigation
|
|
934
971
|
|
|
@@ -1436,13 +1473,17 @@ Open:
|
|
|
1436
1473
|
- [x] Pure in-memory VFS · symlinks · binary snapshot format (VFSB, ~27% smaller than JSON+base64)
|
|
1437
1474
|
- [x] Linux rootfs on boot — `/etc`, `/proc`, `/sys`, `/dev`, `/usr`, `/var`
|
|
1438
1475
|
- [x] Virtual package manager — `apt`/`dpkg`, 25 packages, VFS file writes
|
|
1439
|
-
- [x]
|
|
1440
|
-
- [x] Real shell interpreter — `if`/`for`/`while`/`case`/functions, `$(cmd)`, `$((expr))`, `${#VAR}`,
|
|
1476
|
+
- [x] 92 built-in commands across 9 categories (`seq` added)
|
|
1477
|
+
- [x] Real shell interpreter — `if`/`for`/`while`/`case`/functions, `$(cmd)`, `$((expr))`, `${#VAR}`, `{a,b,c}` brace expansion, `{1..N}` ranges, `2>/dev/null` stderr redirect, `2>&1`, `(( x++ ))`
|
|
1441
1478
|
- [x] `curl`/`wget` as pure `fetch()` · VFS PATH resolution · `/sbin` root-only
|
|
1442
1479
|
- [x] `/proc/self` and `/proc/<pid>` per-session entries
|
|
1443
1480
|
- [x] Snapshot diff tooling — `diffSnapshots`, `formatDiff`, `assertDiff`
|
|
1444
1481
|
- [x] `node`/`python3`/`npm`/`npx` — package-gated virtual REPL stubs
|
|
1445
|
-
- [x] Web shell
|
|
1446
|
-
- [x] Self-standalone CLI (`self-standalone.js`) — single-file interactive shell,
|
|
1482
|
+
- [x] Web shell bundles (`web.min.js`, `web-full-api.min.js`) — fully browser-native with IndexedDB VFS
|
|
1483
|
+
- [x] Self-standalone CLI (`self-standalone.js`) — single-file interactive shell, per-user history, tab completion
|
|
1484
|
+
- [x] 120+ `man` pages — all built-in commands documented via `man <cmd>`
|
|
1485
|
+
- [x] Shared `tokenize.ts` — unified tokenizer for shell parser and runtime (eliminates duplication)
|
|
1486
|
+
- [x] `PasswordChallenge` type — generic interactive password flow for `adduser`, `passwd`, `deluser`
|
|
1487
|
+
- [x] `VirtualFileSystem.mount(vPath, hostPath, { readOnly })` — bind-mount host directories into the VM; read-only by default; browser-safe (silent no-op)
|
|
1447
1488
|
|
|
1448
1489
|
</details>
|
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
|
|
3
|
-
import { mkdirSync, rmSync } from "node:fs";
|
|
4
|
-
import { join } from "node:path";
|
|
5
2
|
import { VirtualShell } from "./src/index.ts";
|
|
6
3
|
|
|
7
4
|
const counts = [1, 2, 5, 10, 20, 50, 100];
|
|
8
|
-
const rootBenchmarkPath = join(process.cwd(), ".benchmark-shells");
|
|
9
5
|
|
|
10
6
|
function bytesToMb(bytes: number): string {
|
|
11
7
|
return `${Math.round(bytes / 1024 / 1024)} MB`;
|
|
12
8
|
}
|
|
13
9
|
|
|
14
10
|
async function createShell(baseName: string, index: number): Promise<VirtualShell> {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
const shell = new VirtualShell(`${baseName}-${index}`, undefined, {
|
|
12
|
+
mode: "memory",
|
|
13
|
+
});
|
|
18
14
|
await shell.ensureInitialized();
|
|
19
15
|
return shell;
|
|
20
16
|
}
|
|
@@ -59,8 +55,6 @@ async function runSingleBenchmark(count: number) {
|
|
|
59
55
|
}
|
|
60
56
|
|
|
61
57
|
async function main() {
|
|
62
|
-
rmSync(rootBenchmarkPath, { recursive: true, force: true });
|
|
63
|
-
mkdirSync(rootBenchmarkPath, { recursive: true });
|
|
64
58
|
|
|
65
59
|
console.log("Benchmarking VirtualShell concurrency:\n");
|
|
66
60
|
const results = [];
|
|
@@ -86,8 +80,6 @@ async function main() {
|
|
|
86
80
|
`${row.count}\t${row.initMs}\t${row.commandMs}\t${bytesToMb(row.initRss)}\t${bytesToMb(row.finalRss)}\t${bytesToMb(row.deltaRss)}`,
|
|
87
81
|
);
|
|
88
82
|
}
|
|
89
|
-
|
|
90
|
-
console.log(`\nBenchmark data stored under ${rootBenchmarkPath}`);
|
|
91
83
|
}
|
|
92
84
|
|
|
93
85
|
main().catch((error) => {
|