typescript-virtual-container 1.3.1 → 1.3.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 +61 -1
- package/builds/self-standalone.js +482 -0
- package/builds/self-standalone.js.map +7 -0
- package/{standalone-wo-sftp.js → builds/standalone-wo-sftp.js} +144 -153
- package/{standalone-wo-sftp.js.map → builds/standalone-wo-sftp.js.map} +4 -4
- package/{standalone.js → builds/standalone.js} +61 -70
- package/{standalone.js.map → builds/standalone.js.map} +4 -4
- package/builds/web-full-api.min.js +13 -0
- package/builds/web-full-api.min.js.map +7 -0
- package/builds/web-iife.min.js +13 -0
- package/builds/web-iife.min.js.map +7 -0
- package/builds/web.min.js +13 -0
- package/builds/web.min.js.map +7 -0
- package/dist/SSHMimic/loginBanner.d.ts +7 -0
- package/dist/SSHMimic/loginBanner.d.ts.map +1 -0
- package/dist/SSHMimic/loginBanner.js +22 -0
- package/dist/VirtualShell/index.d.ts +21 -1
- package/dist/VirtualShell/index.d.ts.map +1 -1
- package/dist/VirtualShell/index.js +34 -2
- package/dist/VirtualShell/shell.d.ts.map +1 -1
- package/dist/VirtualShell/shell.js +2 -17
- package/dist/self-standalone.d.ts +2 -0
- package/dist/self-standalone.d.ts.map +1 -0
- package/dist/self-standalone.js +147 -0
- package/dist/web-api.d.ts +26 -0
- package/dist/web-api.d.ts.map +1 -0
- package/dist/web-api.js +46 -0
- package/dist/web-full.d.ts +4 -0
- package/dist/web-full.d.ts.map +1 -0
- package/dist/web-full.js +8 -0
- package/dist/web.d.ts +108 -0
- package/dist/web.d.ts.map +1 -0
- package/dist/web.js +773 -0
- package/examples/README.md +81 -3
- package/examples/app-iife.js +58 -0
- package/examples/app.js +28 -0
- package/examples/index-cf.html +27 -0
- package/examples/index.html +27 -0
- package/examples/server.js +55 -0
- package/examples/web-iife.min.js +13 -0
- package/examples/web.min.js +13 -0
- package/package.json +12 -5
- package/polyfills/node_child_process/index.js +2 -0
- package/polyfills/node_crypto/index.js +7 -0
- package/polyfills/node_events/index.js +9 -0
- package/polyfills/node_fs/index.js +8 -0
- package/polyfills/node_fs/promises.js +4 -0
- package/polyfills/node_os/index.js +9 -0
- package/polyfills/node_path/index.js +14 -0
- package/polyfills/node_vm/index.js +7 -0
- package/polyfills/node_zlib/index.js +3 -0
- package/src/SSHMimic/loginBanner.ts +36 -0
- package/src/VirtualShell/index.ts +60 -2
- package/src/VirtualShell/shell.ts +3 -31
- package/src/self-standalone.ts +183 -0
- package/src/web-api.ts +62 -0
- package/src/web-full.ts +11 -0
- package/src/web.ts +930 -0
- package/tests/web.test.ts +182 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { createWebShell } from "../src/web";
|
|
3
|
+
|
|
4
|
+
type Listener = () => void;
|
|
5
|
+
|
|
6
|
+
class MockRequest<T> {
|
|
7
|
+
public result: T;
|
|
8
|
+
public error: unknown = null;
|
|
9
|
+
private readonly listeners = new Map<string, Listener[]>();
|
|
10
|
+
|
|
11
|
+
constructor(result: T) {
|
|
12
|
+
this.result = result;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
addEventListener(
|
|
16
|
+
type: "success" | "error" | "complete" | "abort" | "upgradeneeded",
|
|
17
|
+
listener: Listener,
|
|
18
|
+
): void {
|
|
19
|
+
const bucket = this.listeners.get(type) ?? [];
|
|
20
|
+
bucket.push(listener);
|
|
21
|
+
this.listeners.set(type, bucket);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
emit(type: "success" | "error" | "complete" | "abort" | "upgradeneeded"): void {
|
|
25
|
+
for (const listener of this.listeners.get(type) ?? []) {
|
|
26
|
+
listener();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class MockTransaction {
|
|
32
|
+
public error: unknown = null;
|
|
33
|
+
private readonly listeners = new Map<string, Listener[]>();
|
|
34
|
+
|
|
35
|
+
constructor(private readonly store: Map<string, string>) {}
|
|
36
|
+
|
|
37
|
+
objectStore(_name: string): {
|
|
38
|
+
get(key: string): MockRequest<string | undefined>;
|
|
39
|
+
put(value: string, key: string): MockRequest<unknown>;
|
|
40
|
+
} {
|
|
41
|
+
return {
|
|
42
|
+
get: (key: string) => {
|
|
43
|
+
const request = new MockRequest<string | undefined>(this.store.get(key));
|
|
44
|
+
setTimeout(() => request.emit("success"), 0);
|
|
45
|
+
return request;
|
|
46
|
+
},
|
|
47
|
+
put: (value: string, key: string) => {
|
|
48
|
+
const request = new MockRequest<unknown>(undefined);
|
|
49
|
+
setTimeout(() => {
|
|
50
|
+
this.store.set(key, value);
|
|
51
|
+
request.emit("success");
|
|
52
|
+
setTimeout(() => {
|
|
53
|
+
for (const listener of this.listeners.get("complete") ?? []) {
|
|
54
|
+
listener();
|
|
55
|
+
}
|
|
56
|
+
}, 0);
|
|
57
|
+
}, 0);
|
|
58
|
+
return request;
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
addEventListener(type: "complete" | "error" | "abort", listener: Listener): void {
|
|
64
|
+
const bucket = this.listeners.get(type) ?? [];
|
|
65
|
+
bucket.push(listener);
|
|
66
|
+
this.listeners.set(type, bucket);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
class MockDatabase {
|
|
71
|
+
public readonly objectStoreNames = {
|
|
72
|
+
contains: (name: string) => this.stores.has(name),
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
constructor(private readonly stores: Map<string, Map<string, string>>) {}
|
|
76
|
+
|
|
77
|
+
createObjectStore(name: string): unknown {
|
|
78
|
+
if (!this.stores.has(name)) {
|
|
79
|
+
this.stores.set(name, new Map());
|
|
80
|
+
}
|
|
81
|
+
return {};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
transaction(name: string, _mode: "readonly" | "readwrite"): MockTransaction {
|
|
85
|
+
const store = this.stores.get(name) ?? new Map<string, string>();
|
|
86
|
+
this.stores.set(name, store);
|
|
87
|
+
return new MockTransaction(store);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
close(): void {}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
class MockOpenRequest {
|
|
94
|
+
public readonly result: MockDatabase;
|
|
95
|
+
public error: unknown = null;
|
|
96
|
+
private readonly listeners = new Map<string, Listener[]>();
|
|
97
|
+
|
|
98
|
+
constructor(result: MockDatabase) {
|
|
99
|
+
this.result = result;
|
|
100
|
+
setTimeout(() => {
|
|
101
|
+
for (const listener of this.listeners.get("upgradeneeded") ?? []) {
|
|
102
|
+
listener();
|
|
103
|
+
}
|
|
104
|
+
for (const listener of this.listeners.get("success") ?? []) {
|
|
105
|
+
listener();
|
|
106
|
+
}
|
|
107
|
+
}, 0);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
addEventListener(type: "upgradeneeded" | "success" | "error", listener: Listener): void {
|
|
111
|
+
const bucket = this.listeners.get(type) ?? [];
|
|
112
|
+
bucket.push(listener);
|
|
113
|
+
this.listeners.set(type, bucket);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
class MockIndexedDbFactory {
|
|
118
|
+
private readonly databases = new Map<string, MockDatabase>();
|
|
119
|
+
|
|
120
|
+
open(name: string, _version = 1): MockOpenRequest {
|
|
121
|
+
const database = this.databases.get(name) ?? new MockDatabase(new Map());
|
|
122
|
+
this.databases.set(name, database);
|
|
123
|
+
return new MockOpenRequest(database);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const originalIndexedDb = Reflect.get(globalThis, "indexedDB");
|
|
128
|
+
const factory = new MockIndexedDbFactory();
|
|
129
|
+
|
|
130
|
+
beforeEach(() => {
|
|
131
|
+
Reflect.set(globalThis, "indexedDB", factory);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
afterEach(() => {
|
|
135
|
+
Reflect.set(globalThis, "indexedDB", originalIndexedDb);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe("web shell", () => {
|
|
139
|
+
test("executes basic commands", async () => {
|
|
140
|
+
const shell = createWebShell("web-test", { vfs: { databaseName: "web-test-basic" } });
|
|
141
|
+
await shell.ensureInitialized();
|
|
142
|
+
|
|
143
|
+
const pwd = await shell.executeCommandLine("pwd", false);
|
|
144
|
+
expect(pwd.exitCode).toBe(0);
|
|
145
|
+
expect(pwd.stdout?.trim()).toBe("/home/root");
|
|
146
|
+
|
|
147
|
+
const echo = await shell.executeCommandLine("echo hello", false);
|
|
148
|
+
expect(echo.exitCode).toBe(0);
|
|
149
|
+
expect(echo.stdout).toBe("hello\n");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("supports pipes and redirections", async () => {
|
|
153
|
+
const shell = createWebShell("web-test", { vfs: { databaseName: "web-test-pipes" } });
|
|
154
|
+
await shell.ensureInitialized();
|
|
155
|
+
|
|
156
|
+
const piped = await shell.executeCommandLine("echo hello | cat", false);
|
|
157
|
+
expect(piped.exitCode).toBe(0);
|
|
158
|
+
expect(piped.stdout).toContain("hello");
|
|
159
|
+
|
|
160
|
+
const redirected = await shell.executeCommandLine("echo stored > /tmp/web-shell.txt", false);
|
|
161
|
+
expect(redirected.exitCode).toBe(0);
|
|
162
|
+
|
|
163
|
+
const cat = await shell.executeCommandLine("cat /tmp/web-shell.txt", false);
|
|
164
|
+
expect(cat.exitCode).toBe(0);
|
|
165
|
+
expect(cat.stdout).toBe("stored\n");
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("persists snapshot through indexeddb", async () => {
|
|
169
|
+
const databaseName = "web-test-persist";
|
|
170
|
+
const firstShell = createWebShell("web-test", { vfs: { databaseName } });
|
|
171
|
+
await firstShell.ensureInitialized();
|
|
172
|
+
await firstShell.executeCommandLine("echo persisted > /tmp/persisted.txt", false);
|
|
173
|
+
await firstShell.vfs.flushMirror();
|
|
174
|
+
|
|
175
|
+
const secondShell = createWebShell("web-test", { vfs: { databaseName } });
|
|
176
|
+
await secondShell.ensureInitialized();
|
|
177
|
+
const cat = await secondShell.executeCommandLine("cat /tmp/persisted.txt", false);
|
|
178
|
+
|
|
179
|
+
expect(cat.exitCode).toBe(0);
|
|
180
|
+
expect(cat.stdout).toBe("persisted\n");
|
|
181
|
+
});
|
|
182
|
+
});
|