@blastlauncher/web 0.0.0-stage → 3.0.0-alpha.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.
- package/LICENSE +7 -0
- package/README.md +85 -2
- package/assets/almostnode.mjs +735 -0
- package/assets/blast-sources.json +1 -0
- package/assets/blast-web.js +35 -0
- package/assets/esbuild.mjs +2695 -0
- package/assets/esbuild.wasm +0 -0
- package/dist/browser-bundle.d.ts +54 -0
- package/dist/browser-bundle.js +441 -0
- package/dist/browser-install.d.ts +66 -0
- package/dist/browser-install.js +326 -0
- package/dist/bundle-config.d.ts +10 -0
- package/dist/bundle-config.js +31 -0
- package/dist/bundles.d.ts +29 -0
- package/dist/bundles.js +126 -0
- package/dist/capabilities.d.ts +66 -0
- package/dist/capabilities.js +288 -0
- package/dist/custom-element.d.ts +6 -0
- package/dist/custom-element.js +36 -0
- package/dist/default-renderer.d.ts +27 -0
- package/dist/default-renderer.js +145 -0
- package/dist/default-styles.d.ts +2 -0
- package/dist/default-styles.js +28 -0
- package/dist/extension-files.d.ts +21 -0
- package/dist/extension-files.js +86 -0
- package/dist/handlers.d.ts +18 -0
- package/dist/handlers.js +22 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +17 -0
- package/dist/launch.d.ts +11 -0
- package/dist/launch.js +29 -0
- package/dist/prebuilt.d.ts +24 -0
- package/dist/prebuilt.js +112 -0
- package/dist/render.d.ts +62 -0
- package/dist/render.js +288 -0
- package/dist/runtime-port.d.ts +5 -0
- package/dist/runtime-port.js +8 -0
- package/dist/sandbox.d.ts +25 -0
- package/dist/sandbox.js +71 -0
- package/dist/session.d.ts +77 -0
- package/dist/session.js +520 -0
- package/dist/source.d.ts +16 -0
- package/dist/source.js +42 -0
- package/package.json +33 -4
- package/sandbox/almostnode-full.mjs +19713 -0
- package/sandbox/index.html +1 -0
|
@@ -0,0 +1,735 @@
|
|
|
1
|
+
// .cache/browser-demo/deps/node_modules/almostnode/src/sandbox-runtime.ts
|
|
2
|
+
class SandboxRuntime {
|
|
3
|
+
iframe;
|
|
4
|
+
sandboxOrigin;
|
|
5
|
+
vfs;
|
|
6
|
+
options;
|
|
7
|
+
initialized;
|
|
8
|
+
pending = new Map;
|
|
9
|
+
messageId = 0;
|
|
10
|
+
changeListener = null;
|
|
11
|
+
deleteListener = null;
|
|
12
|
+
messageHandler = null;
|
|
13
|
+
constructor(sandboxUrl, vfs, options = {}) {
|
|
14
|
+
this.sandboxOrigin = new URL(sandboxUrl).origin;
|
|
15
|
+
this.vfs = vfs;
|
|
16
|
+
this.options = options;
|
|
17
|
+
this.iframe = document.createElement("iframe");
|
|
18
|
+
this.iframe.src = sandboxUrl;
|
|
19
|
+
this.iframe.style.display = "none";
|
|
20
|
+
this.iframe.credentialless = true;
|
|
21
|
+
this.iframe.setAttribute("credentialless", "");
|
|
22
|
+
document.body.appendChild(this.iframe);
|
|
23
|
+
this.setupMessageHandler();
|
|
24
|
+
this.initialized = this.waitForReady().then(() => this.initSandbox());
|
|
25
|
+
this.setupVFSListeners();
|
|
26
|
+
}
|
|
27
|
+
setupMessageHandler() {
|
|
28
|
+
this.messageHandler = (event) => {
|
|
29
|
+
if (event.origin !== this.sandboxOrigin)
|
|
30
|
+
return;
|
|
31
|
+
const message = event.data;
|
|
32
|
+
if (message.type === "result" && message.id) {
|
|
33
|
+
const pending = this.pending.get(message.id);
|
|
34
|
+
if (pending && message.result) {
|
|
35
|
+
pending.resolve(message.result);
|
|
36
|
+
this.pending.delete(message.id);
|
|
37
|
+
}
|
|
38
|
+
} else if (message.type === "error" && message.id) {
|
|
39
|
+
const pending = this.pending.get(message.id);
|
|
40
|
+
if (pending) {
|
|
41
|
+
pending.reject(new Error(message.error || "Unknown sandbox error"));
|
|
42
|
+
this.pending.delete(message.id);
|
|
43
|
+
}
|
|
44
|
+
} else if (message.type === "console" && this.options.onConsole) {
|
|
45
|
+
this.options.onConsole(message.consoleMethod || "log", message.consoleArgs || []);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
window.addEventListener("message", this.messageHandler);
|
|
49
|
+
}
|
|
50
|
+
waitForReady() {
|
|
51
|
+
return new Promise((resolve) => {
|
|
52
|
+
const handler = (event) => {
|
|
53
|
+
if (event.origin !== this.sandboxOrigin)
|
|
54
|
+
return;
|
|
55
|
+
const message = event.data;
|
|
56
|
+
if (message.type === "ready") {
|
|
57
|
+
window.removeEventListener("message", handler);
|
|
58
|
+
resolve();
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
window.addEventListener("message", handler);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
async initSandbox() {
|
|
65
|
+
const snapshot = this.vfs.toSnapshot();
|
|
66
|
+
const message = {
|
|
67
|
+
type: "init",
|
|
68
|
+
vfsSnapshot: snapshot,
|
|
69
|
+
options: {
|
|
70
|
+
cwd: this.options.cwd,
|
|
71
|
+
env: this.options.env
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
this.iframe.contentWindow?.postMessage(message, this.sandboxOrigin);
|
|
75
|
+
console.log("[SandboxRuntime] Sandbox initialized");
|
|
76
|
+
}
|
|
77
|
+
setupVFSListeners() {
|
|
78
|
+
this.changeListener = (path, content) => {
|
|
79
|
+
const message = {
|
|
80
|
+
type: "syncFile",
|
|
81
|
+
path,
|
|
82
|
+
content
|
|
83
|
+
};
|
|
84
|
+
this.iframe.contentWindow?.postMessage(message, this.sandboxOrigin);
|
|
85
|
+
};
|
|
86
|
+
this.vfs.on("change", this.changeListener);
|
|
87
|
+
this.deleteListener = (path) => {
|
|
88
|
+
const message = {
|
|
89
|
+
type: "syncFile",
|
|
90
|
+
path,
|
|
91
|
+
content: null
|
|
92
|
+
};
|
|
93
|
+
this.iframe.contentWindow?.postMessage(message, this.sandboxOrigin);
|
|
94
|
+
};
|
|
95
|
+
this.vfs.on("delete", this.deleteListener);
|
|
96
|
+
}
|
|
97
|
+
sendAndWait(message) {
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
const id = String(this.messageId++);
|
|
100
|
+
this.pending.set(id, { resolve, reject });
|
|
101
|
+
this.iframe.contentWindow?.postMessage({ ...message, id }, this.sandboxOrigin);
|
|
102
|
+
setTimeout(() => {
|
|
103
|
+
if (this.pending.has(id)) {
|
|
104
|
+
this.pending.delete(id);
|
|
105
|
+
reject(new Error("Sandbox execution timeout"));
|
|
106
|
+
}
|
|
107
|
+
}, 60000);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
async execute(code, filename) {
|
|
111
|
+
await this.initialized;
|
|
112
|
+
return this.sendAndWait({
|
|
113
|
+
type: "execute",
|
|
114
|
+
code,
|
|
115
|
+
filename
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
async runFile(filename) {
|
|
119
|
+
await this.initialized;
|
|
120
|
+
return this.sendAndWait({
|
|
121
|
+
type: "runFile",
|
|
122
|
+
filename
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
clearCache() {
|
|
126
|
+
const message = { type: "clearCache" };
|
|
127
|
+
this.iframe.contentWindow?.postMessage(message, this.sandboxOrigin);
|
|
128
|
+
}
|
|
129
|
+
getVFS() {
|
|
130
|
+
return this.vfs;
|
|
131
|
+
}
|
|
132
|
+
terminate() {
|
|
133
|
+
if (this.changeListener) {
|
|
134
|
+
this.vfs.off("change", this.changeListener);
|
|
135
|
+
}
|
|
136
|
+
if (this.deleteListener) {
|
|
137
|
+
this.vfs.off("delete", this.deleteListener);
|
|
138
|
+
}
|
|
139
|
+
if (this.messageHandler) {
|
|
140
|
+
window.removeEventListener("message", this.messageHandler);
|
|
141
|
+
}
|
|
142
|
+
this.iframe.remove();
|
|
143
|
+
for (const [id, { reject }] of this.pending) {
|
|
144
|
+
reject(new Error("Sandbox terminated"));
|
|
145
|
+
this.pending.delete(id);
|
|
146
|
+
}
|
|
147
|
+
console.log("[SandboxRuntime] Sandbox terminated");
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// .cache/browser-demo/deps/node_modules/almostnode/src/utils/binary-encoding.ts
|
|
152
|
+
var CHUNK = 8192;
|
|
153
|
+
function uint8ToBase64(bytes) {
|
|
154
|
+
const parts = [];
|
|
155
|
+
for (let i = 0;i < bytes.length; i += CHUNK) {
|
|
156
|
+
parts.push(String.fromCharCode.apply(null, Array.from(bytes.subarray(i, i + CHUNK))));
|
|
157
|
+
}
|
|
158
|
+
return btoa(parts.join(""));
|
|
159
|
+
}
|
|
160
|
+
function base64ToUint8(base64) {
|
|
161
|
+
const binary = atob(base64);
|
|
162
|
+
const bytes = new Uint8Array(binary.length);
|
|
163
|
+
for (let i = 0;i < binary.length; i++) {
|
|
164
|
+
bytes[i] = binary.charCodeAt(i);
|
|
165
|
+
}
|
|
166
|
+
return bytes;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// .cache/browser-demo/deps/node_modules/almostnode/src/virtual-fs.ts
|
|
170
|
+
function createNodeError(code, syscall, path, message) {
|
|
171
|
+
const errno = {
|
|
172
|
+
ENOENT: -2,
|
|
173
|
+
ENOTDIR: -20,
|
|
174
|
+
EISDIR: -21,
|
|
175
|
+
EEXIST: -17,
|
|
176
|
+
ENOTEMPTY: -39
|
|
177
|
+
};
|
|
178
|
+
const messages = {
|
|
179
|
+
ENOENT: "no such file or directory",
|
|
180
|
+
ENOTDIR: "not a directory",
|
|
181
|
+
EISDIR: "is a directory",
|
|
182
|
+
EEXIST: "file already exists",
|
|
183
|
+
ENOTEMPTY: "directory not empty"
|
|
184
|
+
};
|
|
185
|
+
const err = new Error(message || `${code}: ${messages[code]}, ${syscall} '${path}'`);
|
|
186
|
+
err.code = code;
|
|
187
|
+
err.errno = errno[code];
|
|
188
|
+
err.syscall = syscall;
|
|
189
|
+
err.path = path;
|
|
190
|
+
return err;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
class VirtualFS {
|
|
194
|
+
root;
|
|
195
|
+
encoder = new TextEncoder;
|
|
196
|
+
decoder = new TextDecoder;
|
|
197
|
+
watchers = new Map;
|
|
198
|
+
eventListeners = new Map;
|
|
199
|
+
constructor() {
|
|
200
|
+
this.root = {
|
|
201
|
+
type: "directory",
|
|
202
|
+
children: new Map,
|
|
203
|
+
mtime: Date.now()
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
on(event, listener) {
|
|
207
|
+
if (!this.eventListeners.has(event)) {
|
|
208
|
+
this.eventListeners.set(event, new Set);
|
|
209
|
+
}
|
|
210
|
+
this.eventListeners.get(event).add(listener);
|
|
211
|
+
return this;
|
|
212
|
+
}
|
|
213
|
+
off(event, listener) {
|
|
214
|
+
const listeners = this.eventListeners.get(event);
|
|
215
|
+
if (listeners) {
|
|
216
|
+
listeners.delete(listener);
|
|
217
|
+
}
|
|
218
|
+
return this;
|
|
219
|
+
}
|
|
220
|
+
emit(event, ...args) {
|
|
221
|
+
const listeners = this.eventListeners.get(event);
|
|
222
|
+
if (listeners) {
|
|
223
|
+
for (const listener of listeners) {
|
|
224
|
+
try {
|
|
225
|
+
listener(...args);
|
|
226
|
+
} catch (err) {
|
|
227
|
+
console.error("Error in VFS event listener:", err);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
toSnapshot() {
|
|
233
|
+
const files = [];
|
|
234
|
+
this.serializeNode("/", this.root, files);
|
|
235
|
+
return { files };
|
|
236
|
+
}
|
|
237
|
+
serializeNode(path, node, files) {
|
|
238
|
+
if (node.type === "file") {
|
|
239
|
+
let content = "";
|
|
240
|
+
if (node.content && node.content.length > 0) {
|
|
241
|
+
content = uint8ToBase64(node.content);
|
|
242
|
+
}
|
|
243
|
+
files.push({ path, type: "file", content });
|
|
244
|
+
} else if (node.type === "directory") {
|
|
245
|
+
files.push({ path, type: "directory" });
|
|
246
|
+
if (node.children) {
|
|
247
|
+
for (const [name, child] of node.children) {
|
|
248
|
+
const childPath = path === "/" ? `/${name}` : `${path}/${name}`;
|
|
249
|
+
this.serializeNode(childPath, child, files);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
static fromSnapshot(snapshot) {
|
|
255
|
+
const vfs = new VirtualFS;
|
|
256
|
+
const sortedFiles = snapshot.files.map((entry, i) => ({ entry, depth: entry.path.split("/").length, i })).sort((a, b) => a.depth - b.depth || a.i - b.i).map((x) => x.entry);
|
|
257
|
+
for (const entry of sortedFiles) {
|
|
258
|
+
if (entry.path === "/")
|
|
259
|
+
continue;
|
|
260
|
+
if (entry.type === "directory") {
|
|
261
|
+
vfs.mkdirSync(entry.path, { recursive: true });
|
|
262
|
+
} else if (entry.type === "file") {
|
|
263
|
+
let content;
|
|
264
|
+
if (entry.content) {
|
|
265
|
+
content = base64ToUint8(entry.content);
|
|
266
|
+
} else {
|
|
267
|
+
content = new Uint8Array(0);
|
|
268
|
+
}
|
|
269
|
+
const parentPath = entry.path.substring(0, entry.path.lastIndexOf("/")) || "/";
|
|
270
|
+
if (parentPath !== "/" && !vfs.existsSync(parentPath)) {
|
|
271
|
+
vfs.mkdirSync(parentPath, { recursive: true });
|
|
272
|
+
}
|
|
273
|
+
vfs.writeFileSyncInternal(entry.path, content, false);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return vfs;
|
|
277
|
+
}
|
|
278
|
+
writeFileSyncInternal(path, data, emitEvent) {
|
|
279
|
+
const normalized = this.normalizePath(path);
|
|
280
|
+
const parentPath = this.getParentPath(normalized);
|
|
281
|
+
const basename = this.getBasename(normalized);
|
|
282
|
+
if (!basename) {
|
|
283
|
+
throw new Error(`EISDIR: illegal operation on a directory, '${path}'`);
|
|
284
|
+
}
|
|
285
|
+
const parent = this.ensureDirectory(parentPath);
|
|
286
|
+
const existed = parent.children.has(basename);
|
|
287
|
+
const content = typeof data === "string" ? this.encoder.encode(data) : data;
|
|
288
|
+
parent.children.set(basename, {
|
|
289
|
+
type: "file",
|
|
290
|
+
content,
|
|
291
|
+
mtime: Date.now()
|
|
292
|
+
});
|
|
293
|
+
if (emitEvent) {
|
|
294
|
+
this.notifyWatchers(normalized, existed ? "change" : "rename");
|
|
295
|
+
this.emit("change", normalized, typeof data === "string" ? data : this.decoder.decode(data));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
normalizePath(path) {
|
|
299
|
+
if (!path.startsWith("/")) {
|
|
300
|
+
path = "/" + path;
|
|
301
|
+
}
|
|
302
|
+
const parts = path.split("/").filter(Boolean);
|
|
303
|
+
const resolved = [];
|
|
304
|
+
for (const part of parts) {
|
|
305
|
+
if (part === "..") {
|
|
306
|
+
resolved.pop();
|
|
307
|
+
} else if (part !== ".") {
|
|
308
|
+
resolved.push(part);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return "/" + resolved.join("/");
|
|
312
|
+
}
|
|
313
|
+
getPathSegments(path) {
|
|
314
|
+
return this.normalizePath(path).split("/").filter(Boolean);
|
|
315
|
+
}
|
|
316
|
+
getParentPath(path) {
|
|
317
|
+
const normalized = this.normalizePath(path);
|
|
318
|
+
const lastSlash = normalized.lastIndexOf("/");
|
|
319
|
+
return lastSlash <= 0 ? "/" : normalized.slice(0, lastSlash);
|
|
320
|
+
}
|
|
321
|
+
getBasename(path) {
|
|
322
|
+
const normalized = this.normalizePath(path);
|
|
323
|
+
const lastSlash = normalized.lastIndexOf("/");
|
|
324
|
+
return normalized.slice(lastSlash + 1);
|
|
325
|
+
}
|
|
326
|
+
getNode(path) {
|
|
327
|
+
const segments = this.getPathSegments(path);
|
|
328
|
+
let current = this.root;
|
|
329
|
+
for (const segment of segments) {
|
|
330
|
+
if (current.type !== "directory" || !current.children) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const child = current.children.get(segment);
|
|
334
|
+
if (!child) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
current = child;
|
|
338
|
+
}
|
|
339
|
+
return current;
|
|
340
|
+
}
|
|
341
|
+
ensureDirectory(path) {
|
|
342
|
+
const segments = this.getPathSegments(path);
|
|
343
|
+
let current = this.root;
|
|
344
|
+
for (const segment of segments) {
|
|
345
|
+
if (!current.children) {
|
|
346
|
+
current.children = new Map;
|
|
347
|
+
}
|
|
348
|
+
let child = current.children.get(segment);
|
|
349
|
+
if (!child) {
|
|
350
|
+
child = { type: "directory", children: new Map, mtime: Date.now() };
|
|
351
|
+
current.children.set(segment, child);
|
|
352
|
+
} else if (child.type !== "directory") {
|
|
353
|
+
throw new Error(`ENOTDIR: not a directory, '${path}'`);
|
|
354
|
+
}
|
|
355
|
+
current = child;
|
|
356
|
+
}
|
|
357
|
+
return current;
|
|
358
|
+
}
|
|
359
|
+
existsSync(path) {
|
|
360
|
+
return this.getNode(path) !== undefined;
|
|
361
|
+
}
|
|
362
|
+
statSync(path) {
|
|
363
|
+
const node = this.getNode(path);
|
|
364
|
+
if (!node) {
|
|
365
|
+
throw createNodeError("ENOENT", "stat", path);
|
|
366
|
+
}
|
|
367
|
+
const size = node.type === "file" ? node.content?.length || 0 : 0;
|
|
368
|
+
const mtime = node.mtime;
|
|
369
|
+
return {
|
|
370
|
+
isFile: () => node.type === "file",
|
|
371
|
+
isDirectory: () => node.type === "directory",
|
|
372
|
+
isSymbolicLink: () => false,
|
|
373
|
+
isBlockDevice: () => false,
|
|
374
|
+
isCharacterDevice: () => false,
|
|
375
|
+
isFIFO: () => false,
|
|
376
|
+
isSocket: () => false,
|
|
377
|
+
size,
|
|
378
|
+
mode: node.type === "directory" ? 493 : 420,
|
|
379
|
+
mtime: new Date(mtime),
|
|
380
|
+
atime: new Date(mtime),
|
|
381
|
+
ctime: new Date(mtime),
|
|
382
|
+
birthtime: new Date(mtime),
|
|
383
|
+
mtimeMs: mtime,
|
|
384
|
+
atimeMs: mtime,
|
|
385
|
+
ctimeMs: mtime,
|
|
386
|
+
birthtimeMs: mtime,
|
|
387
|
+
nlink: 1,
|
|
388
|
+
uid: 1000,
|
|
389
|
+
gid: 1000,
|
|
390
|
+
dev: 0,
|
|
391
|
+
ino: 0,
|
|
392
|
+
rdev: 0,
|
|
393
|
+
blksize: 4096,
|
|
394
|
+
blocks: Math.ceil(size / 512)
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
lstatSync(path) {
|
|
398
|
+
return this.statSync(path);
|
|
399
|
+
}
|
|
400
|
+
readFileSync(path, encoding) {
|
|
401
|
+
const node = this.getNode(path);
|
|
402
|
+
if (!node) {
|
|
403
|
+
throw createNodeError("ENOENT", "open", path);
|
|
404
|
+
}
|
|
405
|
+
if (node.type !== "file") {
|
|
406
|
+
throw createNodeError("EISDIR", "read", path);
|
|
407
|
+
}
|
|
408
|
+
const content = node.content || new Uint8Array(0);
|
|
409
|
+
if (encoding === "utf8" || encoding === "utf-8") {
|
|
410
|
+
return this.decoder.decode(content);
|
|
411
|
+
}
|
|
412
|
+
return content;
|
|
413
|
+
}
|
|
414
|
+
writeFileSync(path, data) {
|
|
415
|
+
this.writeFileSyncInternal(path, data, true);
|
|
416
|
+
}
|
|
417
|
+
mkdirSync(path, options) {
|
|
418
|
+
const normalized = this.normalizePath(path);
|
|
419
|
+
if (options?.recursive) {
|
|
420
|
+
this.ensureDirectory(normalized);
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
const parentPath = this.getParentPath(normalized);
|
|
424
|
+
const basename = this.getBasename(normalized);
|
|
425
|
+
if (!basename) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
const parent = this.getNode(parentPath);
|
|
429
|
+
if (!parent) {
|
|
430
|
+
throw createNodeError("ENOENT", "mkdir", parentPath);
|
|
431
|
+
}
|
|
432
|
+
if (parent.type !== "directory") {
|
|
433
|
+
throw createNodeError("ENOTDIR", "mkdir", parentPath);
|
|
434
|
+
}
|
|
435
|
+
if (parent.children.has(basename)) {
|
|
436
|
+
throw createNodeError("EEXIST", "mkdir", path);
|
|
437
|
+
}
|
|
438
|
+
parent.children.set(basename, {
|
|
439
|
+
type: "directory",
|
|
440
|
+
children: new Map,
|
|
441
|
+
mtime: Date.now()
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
readdirSync(path) {
|
|
445
|
+
const node = this.getNode(path);
|
|
446
|
+
if (!node) {
|
|
447
|
+
throw createNodeError("ENOENT", "scandir", path);
|
|
448
|
+
}
|
|
449
|
+
if (node.type !== "directory") {
|
|
450
|
+
throw createNodeError("ENOTDIR", "scandir", path);
|
|
451
|
+
}
|
|
452
|
+
return Array.from(node.children.keys());
|
|
453
|
+
}
|
|
454
|
+
unlinkSync(path) {
|
|
455
|
+
const normalized = this.normalizePath(path);
|
|
456
|
+
const parentPath = this.getParentPath(normalized);
|
|
457
|
+
const basename = this.getBasename(normalized);
|
|
458
|
+
const parent = this.getNode(parentPath);
|
|
459
|
+
if (!parent || parent.type !== "directory") {
|
|
460
|
+
throw createNodeError("ENOENT", "unlink", path);
|
|
461
|
+
}
|
|
462
|
+
const node = parent.children.get(basename);
|
|
463
|
+
if (!node) {
|
|
464
|
+
throw createNodeError("ENOENT", "unlink", path);
|
|
465
|
+
}
|
|
466
|
+
if (node.type !== "file") {
|
|
467
|
+
throw createNodeError("EISDIR", "unlink", path);
|
|
468
|
+
}
|
|
469
|
+
parent.children.delete(basename);
|
|
470
|
+
this.notifyWatchers(normalized, "rename");
|
|
471
|
+
this.emit("delete", normalized);
|
|
472
|
+
}
|
|
473
|
+
rmdirSync(path) {
|
|
474
|
+
const normalized = this.normalizePath(path);
|
|
475
|
+
const parentPath = this.getParentPath(normalized);
|
|
476
|
+
const basename = this.getBasename(normalized);
|
|
477
|
+
if (!basename) {
|
|
478
|
+
throw new Error(`EPERM: operation not permitted, '${path}'`);
|
|
479
|
+
}
|
|
480
|
+
const parent = this.getNode(parentPath);
|
|
481
|
+
if (!parent || parent.type !== "directory") {
|
|
482
|
+
throw createNodeError("ENOENT", "rmdir", path);
|
|
483
|
+
}
|
|
484
|
+
const node = parent.children.get(basename);
|
|
485
|
+
if (!node) {
|
|
486
|
+
throw createNodeError("ENOENT", "rmdir", path);
|
|
487
|
+
}
|
|
488
|
+
if (node.type !== "directory") {
|
|
489
|
+
throw createNodeError("ENOTDIR", "rmdir", path);
|
|
490
|
+
}
|
|
491
|
+
if (node.children.size > 0) {
|
|
492
|
+
throw createNodeError("ENOTEMPTY", "rmdir", path);
|
|
493
|
+
}
|
|
494
|
+
parent.children.delete(basename);
|
|
495
|
+
}
|
|
496
|
+
renameSync(oldPath, newPath) {
|
|
497
|
+
const normalizedOld = this.normalizePath(oldPath);
|
|
498
|
+
const normalizedNew = this.normalizePath(newPath);
|
|
499
|
+
const oldParentPath = this.getParentPath(normalizedOld);
|
|
500
|
+
const oldBasename = this.getBasename(normalizedOld);
|
|
501
|
+
const newParentPath = this.getParentPath(normalizedNew);
|
|
502
|
+
const newBasename = this.getBasename(normalizedNew);
|
|
503
|
+
const oldParent = this.getNode(oldParentPath);
|
|
504
|
+
if (!oldParent || oldParent.type !== "directory") {
|
|
505
|
+
throw createNodeError("ENOENT", "rename", oldPath);
|
|
506
|
+
}
|
|
507
|
+
const node = oldParent.children.get(oldBasename);
|
|
508
|
+
if (!node) {
|
|
509
|
+
throw createNodeError("ENOENT", "rename", oldPath);
|
|
510
|
+
}
|
|
511
|
+
const newParent = this.ensureDirectory(newParentPath);
|
|
512
|
+
oldParent.children.delete(oldBasename);
|
|
513
|
+
newParent.children.set(newBasename, node);
|
|
514
|
+
this.notifyWatchers(normalizedOld, "rename");
|
|
515
|
+
this.notifyWatchers(normalizedNew, "rename");
|
|
516
|
+
}
|
|
517
|
+
readFile(path, optionsOrCallback, callback) {
|
|
518
|
+
const actualCallback = typeof optionsOrCallback === "function" ? optionsOrCallback : callback;
|
|
519
|
+
const options = typeof optionsOrCallback === "object" ? optionsOrCallback : undefined;
|
|
520
|
+
try {
|
|
521
|
+
const data = options?.encoding ? this.readFileSync(path, options.encoding) : this.readFileSync(path);
|
|
522
|
+
if (actualCallback) {
|
|
523
|
+
setTimeout(() => actualCallback(null, data), 0);
|
|
524
|
+
}
|
|
525
|
+
} catch (err) {
|
|
526
|
+
if (actualCallback) {
|
|
527
|
+
setTimeout(() => actualCallback(err), 0);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
stat(path, callback) {
|
|
532
|
+
try {
|
|
533
|
+
const stats = this.statSync(path);
|
|
534
|
+
setTimeout(() => callback(null, stats), 0);
|
|
535
|
+
} catch (err) {
|
|
536
|
+
setTimeout(() => callback(err), 0);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
lstat(path, callback) {
|
|
540
|
+
this.stat(path, callback);
|
|
541
|
+
}
|
|
542
|
+
readdir(path, optionsOrCallback, callback) {
|
|
543
|
+
const actualCallback = typeof optionsOrCallback === "function" ? optionsOrCallback : callback;
|
|
544
|
+
try {
|
|
545
|
+
const files = this.readdirSync(path);
|
|
546
|
+
if (actualCallback) {
|
|
547
|
+
setTimeout(() => actualCallback(null, files), 0);
|
|
548
|
+
}
|
|
549
|
+
} catch (err) {
|
|
550
|
+
if (actualCallback) {
|
|
551
|
+
setTimeout(() => actualCallback(err), 0);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
realpath(path, callback) {
|
|
556
|
+
try {
|
|
557
|
+
const resolved = this.realpathSync(path);
|
|
558
|
+
setTimeout(() => callback(null, resolved), 0);
|
|
559
|
+
} catch (err) {
|
|
560
|
+
setTimeout(() => callback(err), 0);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
realpathSync(path) {
|
|
564
|
+
const normalized = this.normalizePath(path);
|
|
565
|
+
if (!this.existsSync(normalized)) {
|
|
566
|
+
throw createNodeError("ENOENT", "realpath", path);
|
|
567
|
+
}
|
|
568
|
+
return normalized;
|
|
569
|
+
}
|
|
570
|
+
watch(filename, optionsOrListener, listener) {
|
|
571
|
+
const normalized = this.normalizePath(filename);
|
|
572
|
+
let options = {};
|
|
573
|
+
let actualListener;
|
|
574
|
+
if (typeof optionsOrListener === "function") {
|
|
575
|
+
actualListener = optionsOrListener;
|
|
576
|
+
} else if (optionsOrListener) {
|
|
577
|
+
options = optionsOrListener;
|
|
578
|
+
actualListener = listener;
|
|
579
|
+
} else {
|
|
580
|
+
actualListener = listener;
|
|
581
|
+
}
|
|
582
|
+
const entry = {
|
|
583
|
+
listener: actualListener || (() => {}),
|
|
584
|
+
recursive: options.recursive || false,
|
|
585
|
+
closed: false
|
|
586
|
+
};
|
|
587
|
+
if (!this.watchers.has(normalized)) {
|
|
588
|
+
this.watchers.set(normalized, new Set);
|
|
589
|
+
}
|
|
590
|
+
this.watchers.get(normalized).add(entry);
|
|
591
|
+
const watcher = {
|
|
592
|
+
close: () => {
|
|
593
|
+
entry.closed = true;
|
|
594
|
+
const watcherSet = this.watchers.get(normalized);
|
|
595
|
+
if (watcherSet) {
|
|
596
|
+
watcherSet.delete(entry);
|
|
597
|
+
if (watcherSet.size === 0) {
|
|
598
|
+
this.watchers.delete(normalized);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
},
|
|
602
|
+
ref: () => watcher,
|
|
603
|
+
unref: () => watcher
|
|
604
|
+
};
|
|
605
|
+
return watcher;
|
|
606
|
+
}
|
|
607
|
+
notifyWatchers(path, eventType) {
|
|
608
|
+
const normalized = this.normalizePath(path);
|
|
609
|
+
const basename = this.getBasename(normalized);
|
|
610
|
+
const directWatchers = this.watchers.get(normalized);
|
|
611
|
+
if (directWatchers) {
|
|
612
|
+
for (const entry of directWatchers) {
|
|
613
|
+
if (!entry.closed) {
|
|
614
|
+
try {
|
|
615
|
+
entry.listener(eventType, basename);
|
|
616
|
+
} catch (err) {
|
|
617
|
+
console.error("Error in file watcher:", err);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
let currentPath = this.getParentPath(normalized);
|
|
623
|
+
let relativePath = basename;
|
|
624
|
+
while (currentPath) {
|
|
625
|
+
const parentWatchers = this.watchers.get(currentPath);
|
|
626
|
+
if (parentWatchers) {
|
|
627
|
+
for (const entry of parentWatchers) {
|
|
628
|
+
if (!entry.closed) {
|
|
629
|
+
const isDirectChild = this.getParentPath(normalized) === currentPath;
|
|
630
|
+
if (entry.recursive || isDirectChild) {
|
|
631
|
+
try {
|
|
632
|
+
entry.listener(eventType, relativePath);
|
|
633
|
+
} catch (err) {
|
|
634
|
+
console.error("Error in file watcher:", err);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
if (currentPath === "/")
|
|
641
|
+
break;
|
|
642
|
+
relativePath = this.getBasename(currentPath) + "/" + relativePath;
|
|
643
|
+
currentPath = this.getParentPath(currentPath);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
accessSync(path, mode) {
|
|
647
|
+
if (!this.existsSync(path)) {
|
|
648
|
+
throw createNodeError("ENOENT", "access", path);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
access(path, modeOrCallback, callback) {
|
|
652
|
+
const actualCallback = typeof modeOrCallback === "function" ? modeOrCallback : callback;
|
|
653
|
+
try {
|
|
654
|
+
this.accessSync(path);
|
|
655
|
+
if (actualCallback)
|
|
656
|
+
setTimeout(() => actualCallback(null), 0);
|
|
657
|
+
} catch (err) {
|
|
658
|
+
if (actualCallback)
|
|
659
|
+
setTimeout(() => actualCallback(err), 0);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
copyFileSync(src, dest) {
|
|
663
|
+
const content = this.readFileSync(src);
|
|
664
|
+
this.writeFileSync(dest, content);
|
|
665
|
+
}
|
|
666
|
+
createReadStream(path) {
|
|
667
|
+
const self = this;
|
|
668
|
+
const listeners = {};
|
|
669
|
+
const stream = {
|
|
670
|
+
on(event, cb) {
|
|
671
|
+
if (!listeners[event])
|
|
672
|
+
listeners[event] = [];
|
|
673
|
+
listeners[event].push(cb);
|
|
674
|
+
return stream;
|
|
675
|
+
},
|
|
676
|
+
pipe(dest) {
|
|
677
|
+
return dest;
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
setTimeout(() => {
|
|
681
|
+
try {
|
|
682
|
+
const data = self.readFileSync(path);
|
|
683
|
+
listeners["data"]?.forEach((cb) => cb(data));
|
|
684
|
+
listeners["end"]?.forEach((cb) => cb());
|
|
685
|
+
} catch (err) {
|
|
686
|
+
listeners["error"]?.forEach((cb) => cb(err));
|
|
687
|
+
}
|
|
688
|
+
}, 0);
|
|
689
|
+
return stream;
|
|
690
|
+
}
|
|
691
|
+
createWriteStream(path) {
|
|
692
|
+
const self = this;
|
|
693
|
+
const chunks = [];
|
|
694
|
+
const listeners = {};
|
|
695
|
+
const encoder = new TextEncoder;
|
|
696
|
+
return {
|
|
697
|
+
write(data) {
|
|
698
|
+
const chunk = typeof data === "string" ? encoder.encode(data) : data;
|
|
699
|
+
chunks.push(chunk);
|
|
700
|
+
return true;
|
|
701
|
+
},
|
|
702
|
+
end(data) {
|
|
703
|
+
if (data) {
|
|
704
|
+
const chunk = typeof data === "string" ? encoder.encode(data) : data;
|
|
705
|
+
chunks.push(chunk);
|
|
706
|
+
}
|
|
707
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
708
|
+
const combined = new Uint8Array(totalLength);
|
|
709
|
+
let offset = 0;
|
|
710
|
+
for (const chunk of chunks) {
|
|
711
|
+
combined.set(chunk, offset);
|
|
712
|
+
offset += chunk.length;
|
|
713
|
+
}
|
|
714
|
+
self.writeFileSync(path, combined);
|
|
715
|
+
listeners["finish"]?.forEach((cb) => cb());
|
|
716
|
+
listeners["close"]?.forEach((cb) => cb());
|
|
717
|
+
},
|
|
718
|
+
on(event, cb) {
|
|
719
|
+
if (!listeners[event])
|
|
720
|
+
listeners[event] = [];
|
|
721
|
+
listeners[event].push(cb);
|
|
722
|
+
return this;
|
|
723
|
+
}
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// .cache/browser-demo/host.ts
|
|
729
|
+
async function createRuntime(vfs, options) {
|
|
730
|
+
return new SandboxRuntime(options.sandbox, vfs);
|
|
731
|
+
}
|
|
732
|
+
export {
|
|
733
|
+
VirtualFS,
|
|
734
|
+
createRuntime
|
|
735
|
+
};
|