typescript-virtual-container 1.1.7 → 1.2.1
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/benchmark-results.txt +40 -0
- package/benchmark-virtualshell.ts +96 -0
- package/dist/Honeypot/index.d.ts.map +1 -1
- package/dist/Honeypot/index.js +9 -0
- package/dist/SSHClient/index.d.ts +0 -14
- package/dist/SSHClient/index.d.ts.map +1 -1
- package/dist/SSHClient/index.js +19 -0
- package/dist/SSHMimic/index.d.ts +0 -7
- package/dist/SSHMimic/index.d.ts.map +1 -1
- package/dist/SSHMimic/index.js +5 -0
- package/dist/SSHMimic/sftp.d.ts.map +1 -1
- package/dist/SSHMimic/sftp.js +5 -0
- package/dist/VirtualFileSystem/index.d.ts +0 -7
- package/dist/VirtualFileSystem/index.d.ts.map +1 -1
- package/dist/VirtualFileSystem/index.js +18 -0
- package/dist/VirtualShell/index.d.ts.map +1 -1
- package/dist/VirtualShell/index.js +14 -1
- package/dist/VirtualUserManager/index.d.ts +4 -1
- package/dist/VirtualUserManager/index.d.ts.map +1 -1
- package/dist/VirtualUserManager/index.js +74 -14
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/utils/perfLogger.d.ts +9 -0
- package/dist/utils/perfLogger.d.ts.map +1 -0
- package/dist/utils/perfLogger.js +49 -0
- package/package.json +1 -1
- package/src/Honeypot/index.ts +11 -0
- package/src/SSHClient/index.ts +23 -1
- package/src/SSHMimic/index.ts +6 -0
- package/src/SSHMimic/sftp.ts +8 -1
- package/src/VirtualFileSystem/index.ts +20 -0
- package/src/VirtualShell/index.ts +18 -1
- package/src/VirtualUserManager/index.ts +103 -26
- package/src/index.ts +6 -6
- package/src/utils/perfLogger.ts +72 -0
|
@@ -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;
|
|
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"}
|
package/dist/Honeypot/index.js
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/SSHClient/index.js
CHANGED
|
@@ -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
|
}
|
package/dist/SSHMimic/index.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/SSHMimic/index.js
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/SSHMimic/sftp.js
CHANGED
|
@@ -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;
|
|
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"}
|
|
@@ -2,6 +2,7 @@ import { EventEmitter } from "node:events";
|
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { gunzipSync, gzipSync } from "node:zlib";
|
|
5
|
+
import { createPerfLogger } from "../utils/perfLogger";
|
|
5
6
|
import { normalizePath } from "./path";
|
|
6
7
|
/**
|
|
7
8
|
* In-memory virtual filesystem with tar.gz mirror persistence.
|
|
@@ -10,6 +11,7 @@ import { normalizePath } from "./path";
|
|
|
10
11
|
* {@link VirtualFileSystem.restoreMirror} on startup and
|
|
11
12
|
* {@link VirtualFileSystem.flushMirror} to persist pending changes.
|
|
12
13
|
*/
|
|
14
|
+
const perf = createPerfLogger("VirtualFileSystem");
|
|
13
15
|
class VirtualFileSystem extends EventEmitter {
|
|
14
16
|
mirrorRoot;
|
|
15
17
|
ensureMirrorRoot() {
|
|
@@ -77,6 +79,7 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
77
79
|
*/
|
|
78
80
|
constructor(baseDir = process.cwd()) {
|
|
79
81
|
super();
|
|
82
|
+
perf.mark("constructor");
|
|
80
83
|
this.mirrorRoot = path.resolve(baseDir, ".vfs", "mirror");
|
|
81
84
|
}
|
|
82
85
|
/**
|
|
@@ -85,6 +88,7 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
85
88
|
* If archive does not exist or cannot be read, creates fresh mirror file.
|
|
86
89
|
*/
|
|
87
90
|
async restoreMirror() {
|
|
91
|
+
perf.mark("restoreMirror");
|
|
88
92
|
this.ensureMirrorRoot();
|
|
89
93
|
}
|
|
90
94
|
/**
|
|
@@ -93,6 +97,7 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
93
97
|
* No-op when nothing changed and archive already exists.
|
|
94
98
|
*/
|
|
95
99
|
async flushMirror() {
|
|
100
|
+
perf.mark("flushMirror");
|
|
96
101
|
this.ensureMirrorRoot();
|
|
97
102
|
this.emit("mirror:flush");
|
|
98
103
|
}
|
|
@@ -103,6 +108,7 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
103
108
|
* @param mode POSIX-like mode bits for new directories.
|
|
104
109
|
*/
|
|
105
110
|
mkdir(targetPath, mode = 0o755) {
|
|
111
|
+
perf.mark("mkdir");
|
|
106
112
|
this.ensureMirrorRoot();
|
|
107
113
|
const fsPath = this.resolveFsPath(targetPath);
|
|
108
114
|
if (fs.existsSync(fsPath) && !fs.statSync(fsPath).isDirectory()) {
|
|
@@ -121,6 +127,7 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
121
127
|
* @param options Optional write behavior (mode, compression).
|
|
122
128
|
*/
|
|
123
129
|
writeFile(targetPath, content, options = {}) {
|
|
130
|
+
perf.mark("writeFile");
|
|
124
131
|
this.ensureMirrorRoot();
|
|
125
132
|
const normalized = normalizePath(targetPath);
|
|
126
133
|
const fsPath = this.resolveFsPath(normalized);
|
|
@@ -147,6 +154,7 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
147
154
|
* @returns UTF-8 string content.
|
|
148
155
|
*/
|
|
149
156
|
readFile(targetPath) {
|
|
157
|
+
perf.mark("readFile");
|
|
150
158
|
this.ensureMirrorRoot();
|
|
151
159
|
const fsPath = this.resolveFsPath(targetPath);
|
|
152
160
|
if (!fs.existsSync(fsPath) || !fs.statSync(fsPath).isFile()) {
|
|
@@ -165,6 +173,7 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
165
173
|
* @returns True when file or directory exists.
|
|
166
174
|
*/
|
|
167
175
|
exists(targetPath) {
|
|
176
|
+
perf.mark("exists");
|
|
168
177
|
try {
|
|
169
178
|
const fsPath = this.resolveFsPath(targetPath);
|
|
170
179
|
return fs.existsSync(fsPath);
|
|
@@ -180,6 +189,7 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
180
189
|
* @param mode New POSIX-like mode.
|
|
181
190
|
*/
|
|
182
191
|
chmod(targetPath, mode) {
|
|
192
|
+
perf.mark("chmod");
|
|
183
193
|
const fsPath = this.resolveFsPath(targetPath);
|
|
184
194
|
if (!fs.existsSync(fsPath)) {
|
|
185
195
|
throw new Error(`Path '${normalizePath(targetPath)}' does not exist.`);
|
|
@@ -193,6 +203,7 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
193
203
|
* @returns Typed stat object based on node type.
|
|
194
204
|
*/
|
|
195
205
|
stat(targetPath) {
|
|
206
|
+
perf.mark("stat");
|
|
196
207
|
this.ensureMirrorRoot();
|
|
197
208
|
const normalized = normalizePath(targetPath);
|
|
198
209
|
const fsPath = this.resolveFsPath(normalized);
|
|
@@ -231,6 +242,7 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
231
242
|
* @returns Sorted child names.
|
|
232
243
|
*/
|
|
233
244
|
list(dirPath = "/") {
|
|
245
|
+
perf.mark("list");
|
|
234
246
|
const fsPath = this.resolveFsPath(dirPath);
|
|
235
247
|
if (!fs.existsSync(fsPath) || !fs.statSync(fsPath).isDirectory()) {
|
|
236
248
|
throw new Error(`Cannot list '${dirPath}': not a directory.`);
|
|
@@ -244,6 +256,7 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
244
256
|
* @returns Multi-line tree string.
|
|
245
257
|
*/
|
|
246
258
|
tree(dirPath = "/") {
|
|
259
|
+
perf.mark("tree");
|
|
247
260
|
const fsPath = this.resolveFsPath(dirPath);
|
|
248
261
|
if (!fs.existsSync(fsPath) || !fs.statSync(fsPath).isDirectory()) {
|
|
249
262
|
throw new Error(`Cannot render tree for '${dirPath}': not a directory.`);
|
|
@@ -261,6 +274,7 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
261
274
|
* @returns Total byte usage for file content under target path.
|
|
262
275
|
*/
|
|
263
276
|
getUsageBytes(targetPath = "/") {
|
|
277
|
+
perf.mark("getUsageBytes");
|
|
264
278
|
const fsPath = this.resolveFsPath(targetPath);
|
|
265
279
|
if (!fs.existsSync(fsPath)) {
|
|
266
280
|
throw new Error(`Path '${normalizePath(targetPath)}' does not exist.`);
|
|
@@ -273,6 +287,7 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
273
287
|
* @param targetPath Path to file.
|
|
274
288
|
*/
|
|
275
289
|
compressFile(targetPath) {
|
|
290
|
+
perf.mark("compressFile");
|
|
276
291
|
const fsPath = this.resolveFsPath(targetPath);
|
|
277
292
|
if (!fs.existsSync(fsPath) || !fs.statSync(fsPath).isFile()) {
|
|
278
293
|
throw new Error(`Cannot compress '${targetPath}': not a file.`);
|
|
@@ -288,6 +303,7 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
288
303
|
* @param targetPath Path to file.
|
|
289
304
|
*/
|
|
290
305
|
decompressFile(targetPath) {
|
|
306
|
+
perf.mark("decompressFile");
|
|
291
307
|
const fsPath = this.resolveFsPath(targetPath);
|
|
292
308
|
if (!fs.existsSync(fsPath) || !fs.statSync(fsPath).isFile()) {
|
|
293
309
|
throw new Error(`Cannot decompress '${targetPath}': not a file.`);
|
|
@@ -304,6 +320,7 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
304
320
|
* @param options Removal options, including recursive delete.
|
|
305
321
|
*/
|
|
306
322
|
remove(targetPath, options = {}) {
|
|
323
|
+
perf.mark("remove");
|
|
307
324
|
const normalized = normalizePath(targetPath);
|
|
308
325
|
if (normalized === "/") {
|
|
309
326
|
throw new Error("Cannot remove root directory.");
|
|
@@ -333,6 +350,7 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
333
350
|
* @param toPath Destination path.
|
|
334
351
|
*/
|
|
335
352
|
move(fromPath, toPath) {
|
|
353
|
+
perf.mark("move");
|
|
336
354
|
const fromNormalized = normalizePath(fromPath);
|
|
337
355
|
const toNormalized = normalizePath(toPath);
|
|
338
356
|
if (fromNormalized === "/" || toNormalized === "/") {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualShell/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualShell/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGpD,OAAO,iBAAiB,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAG3D,MAAM,WAAW,eAAe;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACb;AAwCD;;;;;GAKG;AACH,cAAM,YAAa,SAAQ,YAAY;IACtC,QAAQ,EAAE,MAAM,CAAO;IACvB,GAAG,EAAE,iBAAiB,CAAC;IACvB,KAAK,EAAE,kBAAkB,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,eAAe,CAAC;IAC5B,OAAO,CAAC,WAAW,CAAgB;IAEnC;;;;;;OAMG;gBAEF,QAAQ,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,eAAe,EAC5B,QAAQ,CAAC,EAAE,MAAM;IA0BlB;;;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;;;;;;OAMG;IACH,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAMrE;;;;;;;OAOG;IAEH,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;IAgBP;;;;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"}
|