typescript-virtual-container 1.2.0 → 1.2.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.
Files changed (49) hide show
  1. package/.github/workflows/publish.yml +25 -0
  2. package/README.md +25 -8
  3. package/benchmark-results.txt +40 -0
  4. package/benchmark-virtualshell.ts +96 -0
  5. package/dist/Honeypot/index.d.ts.map +1 -1
  6. package/dist/Honeypot/index.js +9 -0
  7. package/dist/SSHClient/index.d.ts +0 -14
  8. package/dist/SSHClient/index.d.ts.map +1 -1
  9. package/dist/SSHClient/index.js +19 -0
  10. package/dist/SSHMimic/index.d.ts +0 -7
  11. package/dist/SSHMimic/index.d.ts.map +1 -1
  12. package/dist/SSHMimic/index.js +5 -0
  13. package/dist/SSHMimic/sftp.d.ts.map +1 -1
  14. package/dist/SSHMimic/sftp.js +5 -0
  15. package/dist/VirtualFileSystem/index.d.ts +0 -7
  16. package/dist/VirtualFileSystem/index.d.ts.map +1 -1
  17. package/dist/VirtualFileSystem/index.js +18 -0
  18. package/dist/VirtualShell/index.d.ts.map +1 -1
  19. package/dist/VirtualShell/index.js +14 -1
  20. package/dist/VirtualUserManager/index.d.ts +4 -1
  21. package/dist/VirtualUserManager/index.d.ts.map +1 -1
  22. package/dist/VirtualUserManager/index.js +72 -13
  23. package/dist/utils/perfLogger.d.ts +9 -0
  24. package/dist/utils/perfLogger.d.ts.map +1 -0
  25. package/dist/utils/perfLogger.js +49 -0
  26. package/package.json +6 -3
  27. package/src/Honeypot/index.ts +11 -0
  28. package/src/SSHClient/index.ts +23 -1
  29. package/src/SSHMimic/index.ts +6 -0
  30. package/src/SSHMimic/sftp.ts +8 -1
  31. package/src/VirtualFileSystem/index.ts +20 -0
  32. package/src/VirtualShell/index.ts +18 -1
  33. package/src/VirtualUserManager/index.ts +103 -26
  34. package/src/utils/perfLogger.ts +72 -0
  35. package/dist/VirtualFileSystem/archive.d.ts +0 -5
  36. package/dist/VirtualFileSystem/archive.d.ts.map +0 -1
  37. package/dist/VirtualFileSystem/archive.js +0 -56
  38. package/dist/VirtualFileSystem/snapshot.d.ts +0 -5
  39. package/dist/VirtualFileSystem/snapshot.d.ts.map +0 -1
  40. package/dist/VirtualFileSystem/snapshot.js +0 -59
  41. package/dist/VirtualFileSystem/tree.d.ts +0 -3
  42. package/dist/VirtualFileSystem/tree.d.ts.map +0 -1
  43. package/dist/VirtualFileSystem/tree.js +0 -19
  44. package/dist/honeypot.d.ts +0 -132
  45. package/dist/honeypot.d.ts.map +0 -1
  46. package/dist/honeypot.js +0 -289
  47. package/src/VirtualFileSystem/archive.ts +0 -74
  48. package/src/VirtualFileSystem/snapshot.ts +0 -84
  49. package/src/VirtualFileSystem/tree.ts +0 -34
@@ -0,0 +1,25 @@
1
+ name: Publish Package
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ id-token: write # Required for OIDC
11
+ contents: read
12
+
13
+ jobs:
14
+ publish:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v6
18
+
19
+ - uses: actions/setup-node@v6
20
+ with:
21
+ node-version: '24'
22
+ registry-url: 'https://registry.npmjs.org'
23
+ - run: npm install
24
+ - run: npm run build --if-present
25
+ - run: npm publish
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # `typescript-virtual-container`
2
2
 
3
- > In-memory SSH/SFTP server with a virtual filesystem and typed programmatic API for testing, automation, and interactive shell scripting in TypeScript/JavaScript.
3
+ > Scalable SSH/SFTP server with a virtual filesystem and typed programmatic API for testing, automation, and interactive shell scripting in TypeScript/JavaScript.
4
4
 
5
5
  [![npm version](https://badge.fury.io/js/typescript-virtual-container.svg)](https://www.npmjs.com/package/typescript-virtual-container)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -37,7 +37,7 @@
37
37
  `typescript-virtual-container` is a lightweight, fully-typed SSH/SFTP runtime written in TypeScript that provides:
38
38
 
39
39
  - **SSH + SFTP Protocol Support**: Serve SSH shell/exec sessions and SFTP file operations on configurable ports.
40
- - **Virtual Filesystem**: In-memory-like developer workflow backed by a mirror directory under `.vfs/mirror`, with optional gzip compression and programmatic access.
40
+ - **Virtual Filesystem**: Fast developer workflow backed by a mirror directory under `.vfs/mirror`, with optional gzip compression and programmatic access.
41
41
  - **User Management**: Create, authenticate, and manage virtual users with strict password hashing (scrypt) and sudo-like privilege elevation.
42
42
  - **Programmatic Shell API**: Execute shell commands and query filesystem state directly from TypeScript without SSH overhead.
43
43
  - **Event-Driven Architecture**: All core classes extend `EventEmitter` for lifecycle and operation tracking. Listen to auth events, filesystem operations, session lifecycle, and command execution for auditing and integration.
@@ -50,7 +50,7 @@
50
50
  ### What This Is
51
51
 
52
52
  - A virtual shell runtime written in TypeScript.
53
- - An in-memory environment with its own virtual filesystem, user management, and command runtime.
53
+ - A virtual environment with its own virtual filesystem, user management, and command runtime.
54
54
  - A practical tool for deterministic testing, automation pipelines, and SSH-like workflows without running real containers.
55
55
 
56
56
  ### What This Is Not
@@ -1684,17 +1684,34 @@ const ssh = new VirtualSshServer({
1684
1684
 
1685
1685
  ## Performance & Scalability
1686
1686
 
1687
- ### Memory Model
1687
+ ### Benchmarking
1688
1688
 
1689
- - **In-Memory FS**: Full filesystem tree kept in RAM (no lazy loading)
1690
- - **Typical footprint**: ~1-10 MB for 1000 files, increases with file content
1691
- - **Compression**: Use `compressFile()` or `compress: true` in `writeFile()` to reduce RAM usage
1689
+ Use the built-in benchmark script to measure initialization and command throughput under concurrent shell loads:
1690
+
1691
+ ```bash
1692
+ bun ./benchmark-virtualshell.ts
1693
+ ```
1694
+
1695
+ The benchmark reports:
1696
+
1697
+ - shell initialization time by concurrency level
1698
+ - command execution time across all active shells
1699
+ - RSS memory growth during the run
1700
+
1701
+ Recent baseline runs show strong startup behavior up to 100 concurrent shells, and the runtime is designed to scale up to **1000 environments very easily** for testing and automation workloads.
1692
1702
 
1693
1703
  ### Concurrency
1694
1704
 
1695
1705
  - SSH server handles multiple concurrent connections (event-driven)
1696
1706
  - Programmatic `SshClient` is synchronous (executes sequentially per instance)
1697
1707
  - Create multiple client instances for parallel operations
1708
+ - Horizontal shell instantiation (`new VirtualShell(...)`) is intended for high-volume scenarios, including large test matrices and multi-tenant simulation batches
1709
+
1710
+ ### Scalability Notes
1711
+
1712
+ - Use a dedicated `basePath` per isolated environment to parallelize safely
1713
+ - Reuse long-lived shell instances when you need low-latency command bursts
1714
+ - Keep performance logging enabled in development (`DEV_MODE=1` or `RENDER_PERF=1`) to locate hotspots quickly
1698
1715
 
1699
1716
  **Example:**
1700
1717
 
@@ -1740,7 +1757,7 @@ async function processResult(r: CommandResult) {
1740
1757
 
1741
1758
  ### Is this a real container runtime?
1742
1759
 
1743
- No. It emulates SSH sessions, users, and filesystem behavior in memory. It is ideal for testing, simulations, and automation workflows where full OS isolation is not required.
1760
+ No. It emulates SSH sessions, users, and filesystem behavior in a virtual runtime. It is ideal for testing, simulations, and automation workflows where full OS isolation is not required.
1744
1761
 
1745
1762
  ### Can I use this in production?
1746
1763
 
@@ -0,0 +1,40 @@
1
+ Benchmarking VirtualShell concurrency:
2
+
3
+ Running 1 shells...
4
+ Initialized 1 shells in 86ms, RSS 82 MB
5
+ Executed shell commands in 2ms, RSS now 82 MB (+0 MB)
6
+
7
+ Running 2 shells...
8
+ Initialized 2 shells in 2ms, RSS 82 MB
9
+ Executed shell commands in 1ms, RSS now 82 MB (+0 MB)
10
+
11
+ Running 5 shells...
12
+ Initialized 5 shells in 5ms, RSS 82 MB
13
+ Executed shell commands in 2ms, RSS now 82 MB (+0 MB)
14
+
15
+ Running 10 shells...
16
+ Initialized 10 shells in 5ms, RSS 84 MB
17
+ Executed shell commands in 3ms, RSS now 84 MB (+1 MB)
18
+
19
+ Running 20 shells...
20
+ Initialized 20 shells in 12ms, RSS 84 MB
21
+ Executed shell commands in 6ms, RSS now 85 MB (+1 MB)
22
+
23
+ Running 50 shells...
24
+ Initialized 50 shells in 30ms, RSS 87 MB
25
+ Executed shell commands in 12ms, RSS now 88 MB (+2 MB)
26
+
27
+ Running 100 shells...
28
+ Initialized 100 shells in 52ms, RSS 91 MB
29
+ Executed shell commands in 28ms, RSS now 92 MB (+2 MB)
30
+
31
+ Summary:
32
+
33
+ count init_ms cmd_ms init_rss final_rss
34
+ 1 86 2 82 MB 82 MB 0 MB
35
+ 2 2 1 82 MB 82 MB 0 MB
36
+ 5 5 2 82 MB 82 MB 0 MB
37
+ 10 5 3 84 MB 84 MB 1 MB
38
+ 20 12 6 84 MB 85 MB 1 MB
39
+ 50 30 12 87 MB 88 MB 2 MB
40
+ 100 52 28 91 MB 92 MB 2 MB
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { mkdirSync, rmSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { VirtualShell } from "./src/index.ts";
6
+
7
+ const counts = [1, 2, 5, 10, 20, 50, 100];
8
+ const rootBenchmarkPath = join(process.cwd(), ".benchmark-shells");
9
+
10
+ function bytesToMb(bytes: number): string {
11
+ return `${Math.round(bytes / 1024 / 1024)} MB`;
12
+ }
13
+
14
+ async function createShell(baseName: string, index: number): Promise<VirtualShell> {
15
+ const basePath = join(rootBenchmarkPath, `${baseName}-${index}`);
16
+ mkdirSync(basePath, { recursive: true });
17
+ const shell = new VirtualShell(`${baseName}-${index}`, undefined, basePath);
18
+ await shell.ensureInitialized();
19
+ return shell;
20
+ }
21
+
22
+ async function runSingleBenchmark(count: number) {
23
+ const label = `shells-${count}`;
24
+ const start = Date.now();
25
+ const shells: VirtualShell[] = await Promise.all(
26
+ Array.from({ length: count }, (_, index) => createShell(label, index)),
27
+ );
28
+ const initMs = Date.now() - start;
29
+ const initRss = process.memoryUsage().rss;
30
+
31
+ const commandStart = Date.now();
32
+ await Promise.all(
33
+ shells.map(async (shell, index) => {
34
+ const cwd = "/home/root";
35
+ shell.executeCommand(`mkdir -p /tmp/benchmark-${index}`, "root", cwd);
36
+ shell.executeCommand(
37
+ `echo "hello ${index}" > /tmp/benchmark-${index}/result.txt`,
38
+ "root",
39
+ cwd,
40
+ );
41
+ shell.executeCommand(
42
+ `cat /tmp/benchmark-${index}/result.txt`,
43
+ "root",
44
+ cwd,
45
+ );
46
+ }),
47
+ );
48
+ const commandMs = Date.now() - commandStart;
49
+ const finalRss = process.memoryUsage().rss;
50
+
51
+ return {
52
+ count,
53
+ initMs,
54
+ commandMs,
55
+ initRss,
56
+ finalRss,
57
+ deltaRss: finalRss - initRss,
58
+ };
59
+ }
60
+
61
+ async function main() {
62
+ rmSync(rootBenchmarkPath, { recursive: true, force: true });
63
+ mkdirSync(rootBenchmarkPath, { recursive: true });
64
+
65
+ console.log("Benchmarking VirtualShell concurrency:\n");
66
+ const results = [];
67
+ for (const count of counts) {
68
+ console.log(`Running ${count} shells...`);
69
+ const result = await runSingleBenchmark(count);
70
+ results.push(result);
71
+ console.log(
72
+ ` Initialized ${count} shells in ${result.initMs}ms, RSS ${bytesToMb(result.initRss)}`,
73
+ );
74
+ console.log(
75
+ ` Executed shell commands in ${result.commandMs}ms, RSS now ${bytesToMb(result.finalRss)} (+${bytesToMb(result.deltaRss)})`,
76
+ );
77
+ console.log("");
78
+ }
79
+
80
+ console.log("Summary:\n");
81
+ console.log(
82
+ "count\tinit_ms\tcmd_ms\tinit_rss\tfinal_rss\tdelta_rss",
83
+ );
84
+ for (const row of results) {
85
+ console.log(
86
+ `${row.count}\t${row.initMs}\t${row.commandMs}\t${bytesToMb(row.initRss)}\t${bytesToMb(row.finalRss)}\t${bytesToMb(row.deltaRss)}`,
87
+ );
88
+ }
89
+
90
+ console.log(`\nBenchmark data stored under ${rootBenchmarkPath}`);
91
+ }
92
+
93
+ main().catch((error) => {
94
+ console.error(error);
95
+ process.exit(1);
96
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/Honeypot/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,KAAK,iBAAiB,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;GAKG;AACH,qBAAa,QAAQ;IACpB,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,KAAK,CAaX;IAEF,OAAO,CAAC,UAAU,CAAS;IAE3B;;;;OAIG;gBACS,UAAU,GAAE,MAAc;IAItC;;;;;;;;OAQG;IACI,MAAM,CACZ,KAAK,EAAE,YAAY,EACnB,GAAG,EAAE,iBAAiB,EACtB,KAAK,EAAE,kBAAkB,EACzB,GAAG,CAAC,EAAE,QAAQ,EACd,IAAI,CAAC,EAAE,SAAS,GACd,IAAI;IAYP;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAmB1B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAoB/B;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAkChC;;OAEG;IACH,OAAO,CAAC,cAAc;IAyCtB;;OAEG;IACH,OAAO,CAAC,eAAe;IAyCvB;;;;;;OAMG;IACH,OAAO,CAAC,GAAG;IAuBX;;;;;;OAMG;IACI,WAAW,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,aAAa,EAAE;IAOnE;;;;OAIG;IACI,QAAQ,IAAI,QAAQ,CAAC,aAAa,CAAC;IAI1C;;OAEG;IACI,KAAK,IAAI,IAAI;IAkBpB;;;;;OAKG;IACI,SAAS,CAAC,KAAK,GAAE,MAAY,GAAG,aAAa,EAAE;IAItD;;;;OAIG;IACI,eAAe,IAAI,KAAK,CAAC;QAC/B,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;QACpC,OAAO,EAAE,MAAM,CAAC;KAChB,CAAC;CAkDF;AAED,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/Honeypot/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAGlD,OAAO,KAAK,iBAAiB,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;CAC1B;AAID;;;;;GAKG;AACH,qBAAa,QAAQ;IACpB,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,KAAK,CAaX;IAEF,OAAO,CAAC,UAAU,CAAS;IAE3B;;;;OAIG;gBACS,UAAU,GAAE,MAAc;IAKtC;;;;;;;;OAQG;IACI,MAAM,CACZ,KAAK,EAAE,YAAY,EACnB,GAAG,EAAE,iBAAiB,EACtB,KAAK,EAAE,kBAAkB,EACzB,GAAG,CAAC,EAAE,QAAQ,EACd,IAAI,CAAC,EAAE,SAAS,GACd,IAAI;IAaP;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAmB1B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAoB/B;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAkChC;;OAEG;IACH,OAAO,CAAC,cAAc;IAyCtB;;OAEG;IACH,OAAO,CAAC,eAAe;IAyCvB;;;;;;OAMG;IACH,OAAO,CAAC,GAAG;IAuBX;;;;;;OAMG;IACI,WAAW,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,aAAa,EAAE;IAQnE;;;;OAIG;IACI,QAAQ,IAAI,QAAQ,CAAC,aAAa,CAAC;IAK1C;;OAEG;IACI,KAAK,IAAI,IAAI;IAmBpB;;;;;OAKG;IACI,SAAS,CAAC,KAAK,GAAE,MAAY,GAAG,aAAa,EAAE;IAKtD;;;;OAIG;IACI,eAAe,IAAI,KAAK,CAAC;QAC/B,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;QACpC,OAAO,EAAE,MAAM,CAAC;KAChB,CAAC;CAmDF;AAED,eAAe,QAAQ,CAAC"}
@@ -7,6 +7,8 @@
7
7
  *
8
8
  * @module honeypot
9
9
  */
10
+ import { createPerfLogger } from "../utils/perfLogger";
11
+ const perf = createPerfLogger("HoneyPot");
10
12
  /**
11
13
  * HoneyPot audit and event tracking utility.
12
14
  *
@@ -36,6 +38,7 @@ export class HoneyPot {
36
38
  * @param maxLogSize Maximum audit log entries to retain (default: 10000).
37
39
  */
38
40
  constructor(maxLogSize = 10000) {
41
+ perf.mark("constructor");
39
42
  this.maxLogSize = maxLogSize;
40
43
  }
41
44
  /**
@@ -48,6 +51,7 @@ export class HoneyPot {
48
51
  * @param sftp SftpMimic instance (optional).
49
52
  */
50
53
  attach(shell, vfs, users, ssh, sftp) {
54
+ perf.mark("attach");
51
55
  this.attachVirtualShell(shell);
52
56
  this.attachVirtualFileSystem(vfs);
53
57
  this.attachVirtualUserManager(users);
@@ -204,6 +208,7 @@ export class HoneyPot {
204
208
  * @returns Filtered audit log entries.
205
209
  */
206
210
  getAuditLog(type, source) {
211
+ perf.mark("getAuditLog");
207
212
  return this.auditLog.filter((entry) => (!type || entry.type === type) && (!source || entry.source === source));
208
213
  }
209
214
  /**
@@ -212,12 +217,14 @@ export class HoneyPot {
212
217
  * @returns Snapshot of honeypot stats.
213
218
  */
214
219
  getStats() {
220
+ perf.mark("getStats");
215
221
  return Object.freeze({ ...this.stats });
216
222
  }
217
223
  /**
218
224
  * Clears audit log and resets statistics.
219
225
  */
220
226
  reset() {
227
+ perf.mark("reset");
221
228
  this.auditLog = [];
222
229
  this.stats = {
223
230
  authAttempts: 0,
@@ -241,6 +248,7 @@ export class HoneyPot {
241
248
  * @returns Recent audit log entries.
242
249
  */
243
250
  getRecent(limit = 100) {
251
+ perf.mark("getRecent");
244
252
  return this.auditLog.slice(Math.max(0, this.auditLog.length - limit));
245
253
  }
246
254
  /**
@@ -249,6 +257,7 @@ export class HoneyPot {
249
257
  * @returns Array of anomalies detected.
250
258
  */
251
259
  detectAnomalies() {
260
+ perf.mark("detectAnomalies");
252
261
  const anomalies = [];
253
262
  // High auth failure rate
254
263
  if (this.stats.authAttempts > 0 &&
@@ -1,19 +1,5 @@
1
1
  import type { CommandResult } from "../types/commands";
2
2
  import type { VirtualShell } from "../VirtualShell";
3
- /**
4
- * Programmatic client for executing shell commands against a virtual shell.
5
- *
6
- * Maintains working-directory state across invocations and runs commands as a
7
- * single authenticated user without SSH transport overhead.
8
- *
9
- * @example
10
- * ```ts
11
- * const shell = new VirtualShell("typescript-vm");
12
- * const client = new SshClient(shell, "alice");
13
- * const result = await client.cd("/tmp");
14
- * const list = await client.ls();
15
- * ```
16
- */
17
3
  export declare class SshClient {
18
4
  private shell;
19
5
  private username;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/SSHClient/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD;;;;;;;;;;;;;GAaG;AACH,qBAAa,SAAS;IAUpB,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,QAAQ;IAVjB,OAAO,CAAC,UAAU,CAAO;IAEzB;;;;;OAKG;gBAEM,KAAK,EAAE,YAAY,EACnB,QAAQ,EAAE,MAAM;IAGzB;;;;;OAKG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IA0BnD;;;;;OAKG;IACG,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAK/C;;;;OAIG;IACG,GAAG,IAAI,OAAO,CAAC,aAAa,CAAC;IAInC;;;;;OAKG;IACG,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAQ9C;;;;;OAKG;IACG,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAI/C;;;;;;OAMG;IACG,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,UAAQ,GAAG,OAAO,CAAC,aAAa,CAAC;IAKpE;;;;;OAKG;IACG,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAIjD;;;;;;OAMG;IACG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,UAAQ,GAAG,OAAO,CAAC,aAAa,CAAC;IAKjE;;;;;;OAMG;IACG,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAiBtE;;;;;OAKG;IACG,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAiBpD;;;;OAIG;IACH,MAAM,IAAI,MAAM;IAIhB;;;;OAIG;IACH,WAAW,IAAI,MAAM;IAIrB;;;;;OAKG;IACG,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAKjD;;;;OAIG;IACG,MAAM,IAAI,OAAO,CAAC,aAAa,CAAC;IAItC;;;;OAIG;IACG,QAAQ,IAAI,OAAO,CAAC,aAAa,CAAC;IAIxC;;;;OAIG;IACG,GAAG,IAAI,OAAO,CAAC,aAAa,CAAC;CAGnC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/SSHClient/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAkBpD,qBAAa,SAAS;IAUpB,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,QAAQ;IAVjB,OAAO,CAAC,UAAU,CAAO;IAEzB;;;;;OAKG;gBAEM,KAAK,EAAE,YAAY,EACnB,QAAQ,EAAE,MAAM;IAKzB;;;;;OAKG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IA2BnD;;;;;OAKG;IACG,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAM/C;;;;OAIG;IACG,GAAG,IAAI,OAAO,CAAC,aAAa,CAAC;IAKnC;;;;;OAKG;IACG,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAS9C;;;;;OAKG;IACG,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAK/C;;;;;;OAMG;IACG,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,UAAQ,GAAG,OAAO,CAAC,aAAa,CAAC;IAMpE;;;;;OAKG;IACG,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAKjD;;;;;;OAMG;IACG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,UAAQ,GAAG,OAAO,CAAC,aAAa,CAAC;IAMjE;;;;;;OAMG;IACG,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAkBtE;;;;;OAKG;IACG,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAkBpD;;;;OAIG;IACH,MAAM,IAAI,MAAM;IAKhB;;;;OAIG;IACH,WAAW,IAAI,MAAM;IAKrB;;;;;OAKG;IACG,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAMjD;;;;OAIG;IACG,MAAM,IAAI,OAAO,CAAC,aAAa,CAAC;IAKtC;;;;OAIG;IACG,QAAQ,IAAI,OAAO,CAAC,aAAa,CAAC;IAKxC;;;;OAIG;IACG,GAAG,IAAI,OAAO,CAAC,aAAa,CAAC;CAInC"}
@@ -1,4 +1,5 @@
1
1
  import { runCommand } from "../commands";
2
+ import { createPerfLogger } from "../utils/perfLogger";
2
3
  /**
3
4
  * Programmatic client for executing shell commands against a virtual shell.
4
5
  *
@@ -13,6 +14,7 @@ import { runCommand } from "../commands";
13
14
  * const list = await client.ls();
14
15
  * ```
15
16
  */
17
+ const perf = createPerfLogger("SshClient");
16
18
  export class SshClient {
17
19
  shell;
18
20
  username;
@@ -26,6 +28,7 @@ export class SshClient {
26
28
  constructor(shell, username) {
27
29
  this.shell = shell;
28
30
  this.username = username;
31
+ perf.mark("constructor");
29
32
  }
30
33
  /**
31
34
  * Executes raw shell command.
@@ -34,6 +37,7 @@ export class SshClient {
34
37
  * @returns Command result with stdout/stderr/exitCode.
35
38
  */
36
39
  async exec(command) {
40
+ perf.mark("exec");
37
41
  const vfs = this.shell.getVfs();
38
42
  const users = this.shell.getUsers();
39
43
  const hostname = this.shell.getHostname();
@@ -54,6 +58,7 @@ export class SshClient {
54
58
  * @returns Result with directory listing in stdout.
55
59
  */
56
60
  async ls(path) {
61
+ perf.mark("ls");
57
62
  const target = path ?? ".";
58
63
  return this.exec(`ls ${target}`);
59
64
  }
@@ -63,6 +68,7 @@ export class SshClient {
63
68
  * @returns Result with cwd path in stdout.
64
69
  */
65
70
  async pwd() {
71
+ perf.mark("pwd");
66
72
  return this.exec("pwd");
67
73
  }
68
74
  /**
@@ -72,6 +78,7 @@ export class SshClient {
72
78
  * @returns Result; updates internal cwd on success.
73
79
  */
74
80
  async cd(path) {
81
+ perf.mark("cd");
75
82
  const result = await this.exec(`cd ${path}`);
76
83
  if (result.nextCwd && result.exitCode !== 1) {
77
84
  this.currentCwd = result.nextCwd;
@@ -85,6 +92,7 @@ export class SshClient {
85
92
  * @returns Result with file content in stdout.
86
93
  */
87
94
  async cat(path) {
95
+ perf.mark("cat");
88
96
  return this.exec(`cat ${path}`);
89
97
  }
90
98
  /**
@@ -95,6 +103,7 @@ export class SshClient {
95
103
  * @returns Result from mkdir command.
96
104
  */
97
105
  async mkdir(path, recursive = false) {
106
+ perf.mark("mkdir");
98
107
  const flag = recursive ? "-p " : "";
99
108
  return this.exec(`mkdir ${flag}${path}`);
100
109
  }
@@ -105,6 +114,7 @@ export class SshClient {
105
114
  * @returns Result from touch command.
106
115
  */
107
116
  async touch(path) {
117
+ perf.mark("touch");
108
118
  return this.exec(`touch ${path}`);
109
119
  }
110
120
  /**
@@ -115,6 +125,7 @@ export class SshClient {
115
125
  * @returns Result from rm command.
116
126
  */
117
127
  async rm(path, recursive = false) {
128
+ perf.mark("rm");
118
129
  const flag = recursive ? "-r " : "";
119
130
  return this.exec(`rm ${flag}${path}`);
120
131
  }
@@ -126,6 +137,7 @@ export class SshClient {
126
137
  * @returns Result from touch/write simulation.
127
138
  */
128
139
  async writeFile(path, content) {
140
+ perf.mark("writeFile");
129
141
  const vfs = this.shell.getVfs();
130
142
  if (!vfs) {
131
143
  throw new Error("SSH client not started");
@@ -148,6 +160,7 @@ export class SshClient {
148
160
  * @returns File content as string or error in result.
149
161
  */
150
162
  async readFile(path) {
163
+ perf.mark("readFile");
151
164
  const vfs = this.shell.getVfs();
152
165
  if (!vfs) {
153
166
  throw new Error("SSH client not started");
@@ -169,6 +182,7 @@ export class SshClient {
169
182
  * @returns Normalized cwd path.
170
183
  */
171
184
  getCwd() {
185
+ perf.mark("getCwd");
172
186
  return this.currentCwd;
173
187
  }
174
188
  /**
@@ -177,6 +191,7 @@ export class SshClient {
177
191
  * @returns Associated username.
178
192
  */
179
193
  getUsername() {
194
+ perf.mark("getUsername");
180
195
  return this.username;
181
196
  }
182
197
  /**
@@ -186,6 +201,7 @@ export class SshClient {
186
201
  * @returns Result with ASCII tree in stdout.
187
202
  */
188
203
  async tree(path) {
204
+ perf.mark("tree");
189
205
  const target = path ?? ".";
190
206
  return this.exec(`tree ${target}`);
191
207
  }
@@ -195,6 +211,7 @@ export class SshClient {
195
211
  * @returns Result from whoami command.
196
212
  */
197
213
  async whoami() {
214
+ perf.mark("whoami");
198
215
  return this.exec("whoami");
199
216
  }
200
217
  /**
@@ -203,6 +220,7 @@ export class SshClient {
203
220
  * @returns Result from hostname command.
204
221
  */
205
222
  async hostname() {
223
+ perf.mark("hostname");
206
224
  return this.exec("hostname");
207
225
  }
208
226
  /**
@@ -211,6 +229,7 @@ export class SshClient {
211
229
  * @returns Result from who command.
212
230
  */
213
231
  async who() {
232
+ perf.mark("who");
214
233
  return this.exec("who");
215
234
  }
216
235
  }
@@ -1,13 +1,6 @@
1
1
  import { EventEmitter } from "node:events";
2
2
  import { Server as SshServer } from "ssh2";
3
3
  import { VirtualShell } from "../VirtualShell";
4
- /**
5
- * SSH server facade that wires the virtual shell runtime into ssh2 sessions.
6
- *
7
- * This class is exported as `VirtualSshServer` for public API compatibility.
8
- * Create an instance, call {@link SshMimic.start}, and stop it with
9
- * {@link SshMimic.stop} when your process exits.
10
- */
11
4
  declare class SshMimic extends EventEmitter {
12
5
  port: number;
13
6
  server: SshServer | null;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/SSHMimic/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,MAAM,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAI/C;;;;;;GAMG;AACH,cAAM,QAAS,SAAQ,YAAY;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,KAAK,CAAe;IAC5B,OAAO,CAAC,aAAa,CAAS;IAE9B;;;;;;OAMG;gBACS,EACX,IAAI,EACJ,QAA0B,EAC1B,KAAkC,GAClC,EAAE;QACF,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,YAAY,CAAC;KACrB;IAQD;;;;OAIG;IACU,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAwHrC;;OAEG;IACI,IAAI,IAAI,IAAI;CAQnB;AAED,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,EAAE,QAAQ,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/SSHMimic/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,MAAM,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAc/C,cAAM,QAAS,SAAQ,YAAY;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,KAAK,CAAe;IAC5B,OAAO,CAAC,aAAa,CAAS;IAE9B;;;;;;OAMG;gBACS,EACX,IAAI,EACJ,QAA0B,EAC1B,KAAkC,GAClC,EAAE;QACF,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,YAAY,CAAC;KACrB;IASD;;;;OAIG;IACU,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAyHrC;;OAEG;IACI,IAAI,IAAI,IAAI;CASnB;AAED,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,EAAE,QAAQ,EAAE,CAAC"}
@@ -1,6 +1,7 @@
1
1
  import { EventEmitter } from "node:events";
2
2
  import { Server as SshServer } from "ssh2";
3
3
  import { VirtualShell } from "../VirtualShell";
4
+ import { createPerfLogger } from "../utils/perfLogger";
4
5
  import { runExec } from "./exec";
5
6
  import { loadOrCreateHostKey } from "./hostKey";
6
7
  /**
@@ -10,6 +11,7 @@ import { loadOrCreateHostKey } from "./hostKey";
10
11
  * Create an instance, call {@link SshMimic.start}, and stop it with
11
12
  * {@link SshMimic.stop} when your process exits.
12
13
  */
14
+ const perf = createPerfLogger("SshMimic");
13
15
  class SshMimic extends EventEmitter {
14
16
  port;
15
17
  server;
@@ -24,6 +26,7 @@ class SshMimic extends EventEmitter {
24
26
  */
25
27
  constructor({ port, hostname = "typescript-vm", shell = new VirtualShell(hostname), }) {
26
28
  super();
29
+ perf.mark("constructor");
27
30
  this.port = port;
28
31
  this.shellHostname = hostname;
29
32
  this.server = null;
@@ -35,6 +38,7 @@ class SshMimic extends EventEmitter {
35
38
  * @returns Promise resolved with bound listening port.
36
39
  */
37
40
  async start() {
41
+ perf.mark("start");
38
42
  const shell = this.shell;
39
43
  const privateKey = loadOrCreateHostKey();
40
44
  // Ensure VirtualShell is fully initialized before accepting connections
@@ -118,6 +122,7 @@ class SshMimic extends EventEmitter {
118
122
  * Stops server if running.
119
123
  */
120
124
  stop() {
125
+ perf.mark("stop");
121
126
  if (this.server) {
122
127
  this.server.close(() => {
123
128
  console.log("SSH Mimic stopped");
@@ -1 +1 @@
1
- {"version":3,"file":"sftp.d.ts","sourceRoot":"","sources":["../../src/SSHMimic/sftp.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,OAAO,EAAE,MAAM,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAC3C,OAAO,KAAK,iBAAiB,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AA2HhE,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,GAAG,CAAC,EAAE,iBAAiB,CAAC;IACxB,KAAK,CAAC,EAAE,kBAAkB,CAAC;CAC3B;AAED,qBAAa,SAAU,SAAQ,YAAY;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsB;IAC5C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAoB;IACxC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;IAC3C,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,OAAO,CAAiC;gBAEpC,EACX,IAAI,EACJ,QAA0B,EAC1B,KAAK,EACL,GAAG,EACH,KAAK,GACL,EAAE,gBAAgB;IAuBnB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,QAAQ;IAIH,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAwJ9B,IAAI,IAAI,IAAI;IASnB;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAiB1B;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,WAAW;IAcnB,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,kBAAkB;CA6a1B"}
1
+ {"version":3,"file":"sftp.d.ts","sourceRoot":"","sources":["../../src/SSHMimic/sftp.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,OAAO,EAAE,MAAM,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAI3C,OAAO,KAAK,iBAAiB,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AA4HhE,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,GAAG,CAAC,EAAE,iBAAiB,CAAC;IACxB,KAAK,CAAC,EAAE,kBAAkB,CAAC;CAC3B;AAED,qBAAa,SAAU,SAAQ,YAAY;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsB;IAC5C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAoB;IACxC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;IAC3C,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,OAAO,CAAiC;gBAEpC,EACX,IAAI,EACJ,QAA0B,EAC1B,KAAK,EACL,GAAG,EACH,KAAK,GACL,EAAE,gBAAgB;IAwBnB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,QAAQ;IAIH,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAyJ9B,IAAI,IAAI,IAAI;IAUnB;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAiB1B;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,WAAW;IAcnB,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,kBAAkB;CA6a1B"}
@@ -2,6 +2,7 @@
2
2
  import { EventEmitter } from "node:events";
3
3
  import * as path from "node:path";
4
4
  import { Server as SshServer } from "ssh2";
5
+ import { createPerfLogger } from "../utils/perfLogger";
5
6
  import { VirtualShell } from "../VirtualShell";
6
7
  import { loadOrCreateHostKey } from "./hostKey";
7
8
  const SFTP_STATUS_CODE = {
@@ -23,6 +24,7 @@ const OPEN_MODE = {
23
24
  TRUNC: 0x00000010,
24
25
  EXCL: 0x00000020,
25
26
  };
27
+ const perf = createPerfLogger("SftpMimic");
26
28
  export class SftpMimic extends EventEmitter {
27
29
  port;
28
30
  server;
@@ -34,6 +36,7 @@ export class SftpMimic extends EventEmitter {
34
36
  handles = new Map();
35
37
  constructor({ port, hostname = "typescript-vm", shell, vfs, users, }) {
36
38
  super();
39
+ perf.mark("constructor");
37
40
  this.port = port;
38
41
  this.server = null;
39
42
  this.hostname = hostname;
@@ -62,6 +65,7 @@ export class SftpMimic extends EventEmitter {
62
65
  return this.shell?.users ?? this.users;
63
66
  }
64
67
  async start() {
68
+ perf.mark("start");
65
69
  const privateKey = loadOrCreateHostKey();
66
70
  // Ensure VirtualShell is fully initialized before accepting connections
67
71
  if (this.shell) {
@@ -175,6 +179,7 @@ export class SftpMimic extends EventEmitter {
175
179
  });
176
180
  }
177
181
  stop() {
182
+ perf.mark("stop");
178
183
  if (this.server) {
179
184
  this.server.close(() => {
180
185
  console.log("SFTP Mimic stopped");
@@ -1,12 +1,5 @@
1
1
  import { EventEmitter } from "node:events";
2
2
  import type { RemoveOptions, VfsNodeStats, WriteFileOptions } from "../types/vfs";
3
- /**
4
- * In-memory virtual filesystem with tar.gz mirror persistence.
5
- *
6
- * Paths are normalized to POSIX-like absolute paths. Use
7
- * {@link VirtualFileSystem.restoreMirror} on startup and
8
- * {@link VirtualFileSystem.flushMirror} to persist pending changes.
9
- */
10
3
  declare class VirtualFileSystem extends EventEmitter {
11
4
  private readonly mirrorRoot;
12
5
  private ensureMirrorRoot;
@@ -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;AAI3C,OAAO,KAAK,EACX,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,MAAM,cAAc,CAAC;AAGtB;;;;;;GAMG;AACH,cAAM,iBAAkB,SAAQ,YAAY;IAC3C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IAEpC,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,aAAa;IAarB,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,qBAAqB;IAa7B,OAAO,CAAC,eAAe;IA4BvB;;;;OAIG;gBACS,OAAO,GAAE,MAAsB;IAK3C;;;;OAIG;IACU,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3C;;;;OAIG;IACU,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAKzC;;;;;OAKG;IACI,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,GAAE,MAAc,GAAG,IAAI;IAY5D;;;;;;;;OAQG;IACI,SAAS,CACf,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GAAG,MAAM,EACxB,OAAO,GAAE,gBAAqB,GAC5B,IAAI;IAwBP;;;;;;;OAOG;IACI,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;IAc3C;;;;;OAKG;IACI,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAS1C;;;;;OAKG;IACI,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAQpD;;;;;OAKG;IACI,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,YAAY;IAqC7C;;;;;OAKG;IACI,IAAI,CAAC,OAAO,GAAE,MAAY,GAAG,MAAM,EAAE;IAS5C;;;;;OAKG;IACI,IAAI,CAAC,OAAO,GAAE,MAAY,GAAG,MAAM;IAW1C;;;;;;;;OAQG;IACI,aAAa,CAAC,UAAU,GAAE,MAAY,GAAG,MAAM;IAQtD;;;;OAIG;IACI,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAY7C;;;;OAIG;IACI,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAY/C;;;;;OAKG;IACI,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,IAAI;IA4BpE;;;;;OAKG;IACI,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;CAsBnD;AAED,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualFileSystem/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAI3C,OAAO,KAAK,EACX,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,MAAM,cAAc,CAAC;AActB,cAAM,iBAAkB,SAAQ,YAAY;IAC3C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IAEpC,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,aAAa;IAarB,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,qBAAqB;IAa7B,OAAO,CAAC,eAAe;IA4BvB;;;;OAIG;gBACS,OAAO,GAAE,MAAsB;IAM3C;;;;OAIG;IACU,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAK3C;;;;OAIG;IACU,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAMzC;;;;;OAKG;IACI,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,GAAE,MAAc,GAAG,IAAI;IAa5D;;;;;;;;OAQG;IACI,SAAS,CACf,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GAAG,MAAM,EACxB,OAAO,GAAE,gBAAqB,GAC5B,IAAI;IAyBP;;;;;;;OAOG;IACI,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;IAe3C;;;;;OAKG;IACI,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAU1C;;;;;OAKG;IACI,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IASpD;;;;;OAKG;IACI,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,YAAY;IAsC7C;;;;;OAKG;IACI,IAAI,CAAC,OAAO,GAAE,MAAY,GAAG,MAAM,EAAE;IAU5C;;;;;OAKG;IACI,IAAI,CAAC,OAAO,GAAE,MAAY,GAAG,MAAM;IAY1C;;;;;;;;OAQG;IACI,aAAa,CAAC,UAAU,GAAE,MAAY,GAAG,MAAM;IAStD;;;;OAIG;IACI,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAa7C;;;;OAIG;IACI,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAa/C;;;;;OAKG;IACI,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,IAAI;IA6BpE;;;;;OAKG;IACI,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;CAuBnD;AAED,eAAe,iBAAiB,CAAC"}