sandlot 0.1.2 → 0.1.4
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 +138 -408
- package/dist/build-emitter.d.ts +31 -13
- package/dist/build-emitter.d.ts.map +1 -1
- package/dist/builder.d.ts +370 -0
- package/dist/builder.d.ts.map +1 -0
- package/dist/bundler.d.ts +6 -2
- package/dist/bundler.d.ts.map +1 -1
- package/dist/commands/compile.d.ts +13 -0
- package/dist/commands/compile.d.ts.map +1 -0
- package/dist/commands/index.d.ts +17 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/packages.d.ts +17 -0
- package/dist/commands/packages.d.ts.map +1 -0
- package/dist/commands/run.d.ts +40 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/types.d.ts +141 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/fs.d.ts +53 -49
- package/dist/fs.d.ts.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +300 -511
- package/dist/internal.js +161 -171
- package/dist/runner.d.ts +314 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/sandbox-manager.d.ts +45 -21
- package/dist/sandbox-manager.d.ts.map +1 -1
- package/dist/sandbox.d.ts +144 -62
- package/dist/sandbox.d.ts.map +1 -1
- package/dist/shared-modules.d.ts +22 -3
- package/dist/shared-modules.d.ts.map +1 -1
- package/dist/shared-resources.d.ts +0 -3
- package/dist/shared-resources.d.ts.map +1 -1
- package/dist/ts-libs.d.ts +7 -20
- package/dist/ts-libs.d.ts.map +1 -1
- package/dist/typechecker.d.ts +1 -1
- package/package.json +5 -5
- package/src/build-emitter.ts +32 -29
- package/src/builder.ts +498 -0
- package/src/bundler.ts +76 -55
- package/src/commands/compile.ts +236 -0
- package/src/commands/index.ts +51 -0
- package/src/commands/packages.ts +154 -0
- package/src/commands/run.ts +245 -0
- package/src/commands/types.ts +172 -0
- package/src/fs.ts +82 -221
- package/src/index.ts +17 -12
- package/src/sandbox.ts +219 -149
- package/src/shared-modules.ts +74 -4
- package/src/shared-resources.ts +0 -3
- package/src/ts-libs.ts +19 -121
- package/src/typechecker.ts +1 -1
- package/dist/react.d.ts +0 -159
- package/dist/react.d.ts.map +0 -1
- package/dist/react.js +0 -149
- package/src/commands.ts +0 -733
- package/src/sandbox-manager.ts +0 -409
package/dist/index.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined")
|
|
5
|
+
return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
|
|
1
9
|
// src/sandbox.ts
|
|
2
10
|
import { Bash } from "just-bash/browser";
|
|
3
11
|
|
|
@@ -7,47 +15,14 @@ var DEFAULT_DIR_MODE = 493;
|
|
|
7
15
|
var DEFAULT_SYMLINK_MODE = 511;
|
|
8
16
|
var DEFAULT_MAX_SIZE_BYTES = 50 * 1024 * 1024;
|
|
9
17
|
|
|
10
|
-
class
|
|
18
|
+
class Filesystem {
|
|
11
19
|
entries;
|
|
12
|
-
db = null;
|
|
13
|
-
dbName;
|
|
14
20
|
maxSizeBytes;
|
|
15
|
-
|
|
16
|
-
constructor(entries, db, dbName, maxSizeBytes) {
|
|
21
|
+
constructor(entries, maxSizeBytes) {
|
|
17
22
|
this.entries = entries;
|
|
18
|
-
this.db = db;
|
|
19
|
-
this.dbName = dbName;
|
|
20
23
|
this.maxSizeBytes = maxSizeBytes;
|
|
21
24
|
}
|
|
22
|
-
static
|
|
23
|
-
const dbName = options.dbName ?? "sandlot-fs";
|
|
24
|
-
const maxSizeBytes = options.maxSizeBytes ?? DEFAULT_MAX_SIZE_BYTES;
|
|
25
|
-
const db = await IndexedDbFs.openDatabase(dbName);
|
|
26
|
-
const entries = await IndexedDbFs.loadEntries(db);
|
|
27
|
-
if (entries.size === 0) {
|
|
28
|
-
entries.set("/", {
|
|
29
|
-
type: "directory",
|
|
30
|
-
mode: DEFAULT_DIR_MODE,
|
|
31
|
-
mtime: new Date
|
|
32
|
-
});
|
|
33
|
-
if (options.initialFiles) {
|
|
34
|
-
for (const [path, value] of Object.entries(options.initialFiles)) {
|
|
35
|
-
const normalizedPath = IndexedDbFs.normalizePath(path);
|
|
36
|
-
const init = IndexedDbFs.parseFileInit(value);
|
|
37
|
-
IndexedDbFs.ensureParentDirs(entries, normalizedPath);
|
|
38
|
-
entries.set(normalizedPath, {
|
|
39
|
-
type: "file",
|
|
40
|
-
content: init.content,
|
|
41
|
-
mode: init.mode ?? DEFAULT_FILE_MODE,
|
|
42
|
-
mtime: init.mtime ?? new Date
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
const fs = new IndexedDbFs(entries, db, dbName, maxSizeBytes);
|
|
48
|
-
return fs;
|
|
49
|
-
}
|
|
50
|
-
static createInMemory(options = {}) {
|
|
25
|
+
static create(options = {}) {
|
|
51
26
|
const maxSizeBytes = options.maxSizeBytes ?? DEFAULT_MAX_SIZE_BYTES;
|
|
52
27
|
const entries = new Map;
|
|
53
28
|
entries.set("/", {
|
|
@@ -57,9 +32,9 @@ class IndexedDbFs {
|
|
|
57
32
|
});
|
|
58
33
|
if (options.initialFiles) {
|
|
59
34
|
for (const [path, value] of Object.entries(options.initialFiles)) {
|
|
60
|
-
const normalizedPath =
|
|
61
|
-
const init =
|
|
62
|
-
|
|
35
|
+
const normalizedPath = Filesystem.normalizePath(path);
|
|
36
|
+
const init = Filesystem.parseFileInit(value);
|
|
37
|
+
Filesystem.ensureParentDirs(entries, normalizedPath);
|
|
63
38
|
entries.set(normalizedPath, {
|
|
64
39
|
type: "file",
|
|
65
40
|
content: init.content,
|
|
@@ -68,31 +43,21 @@ class IndexedDbFs {
|
|
|
68
43
|
});
|
|
69
44
|
}
|
|
70
45
|
}
|
|
71
|
-
return new
|
|
46
|
+
return new Filesystem(entries, maxSizeBytes);
|
|
72
47
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
const tx = this.db.transaction("entries", "readwrite");
|
|
78
|
-
const store = tx.objectStore("entries");
|
|
79
|
-
await this.promisifyRequest(store.clear());
|
|
48
|
+
getFiles() {
|
|
49
|
+
const files = {};
|
|
80
50
|
for (const [path, entry] of this.entries) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return;
|
|
51
|
+
if (entry.type === "file") {
|
|
52
|
+
if (typeof entry.content === "string") {
|
|
53
|
+
files[path] = entry.content;
|
|
54
|
+
} else {
|
|
55
|
+
const base64 = this.encodeBase64(entry.content);
|
|
56
|
+
files[path] = `data:application/octet-stream;base64,${base64}`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
90
59
|
}
|
|
91
|
-
|
|
92
|
-
this.dirty = false;
|
|
93
|
-
}
|
|
94
|
-
isDirty() {
|
|
95
|
-
return this.dirty;
|
|
60
|
+
return files;
|
|
96
61
|
}
|
|
97
62
|
getSize() {
|
|
98
63
|
let size = 0;
|
|
@@ -108,12 +73,6 @@ class IndexedDbFs {
|
|
|
108
73
|
}
|
|
109
74
|
return size;
|
|
110
75
|
}
|
|
111
|
-
close() {
|
|
112
|
-
if (this.db) {
|
|
113
|
-
this.db.close();
|
|
114
|
-
this.db = null;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
76
|
async readFile(path, options) {
|
|
118
77
|
const normalizedPath = this.normalizePath(path);
|
|
119
78
|
const entry = this.resolveSymlinks(normalizedPath);
|
|
@@ -159,7 +118,6 @@ class IndexedDbFs {
|
|
|
159
118
|
mode: existing?.mode ?? DEFAULT_FILE_MODE,
|
|
160
119
|
mtime: new Date
|
|
161
120
|
});
|
|
162
|
-
this.dirty = true;
|
|
163
121
|
}
|
|
164
122
|
async appendFile(path, content, options) {
|
|
165
123
|
const normalizedPath = this.normalizePath(path);
|
|
@@ -211,7 +169,6 @@ class IndexedDbFs {
|
|
|
211
169
|
mode: DEFAULT_DIR_MODE,
|
|
212
170
|
mtime: new Date
|
|
213
171
|
});
|
|
214
|
-
this.dirty = true;
|
|
215
172
|
}
|
|
216
173
|
async readdir(path) {
|
|
217
174
|
const normalizedPath = this.normalizePath(path);
|
|
@@ -287,7 +244,6 @@ class IndexedDbFs {
|
|
|
287
244
|
}
|
|
288
245
|
}
|
|
289
246
|
this.entries.delete(normalizedPath);
|
|
290
|
-
this.dirty = true;
|
|
291
247
|
}
|
|
292
248
|
async cp(src, dest, options) {
|
|
293
249
|
const srcPath = this.normalizePath(src);
|
|
@@ -314,7 +270,6 @@ class IndexedDbFs {
|
|
|
314
270
|
this.ensureParentDirs(destPath);
|
|
315
271
|
this.entries.set(destPath, this.cloneEntry(entry));
|
|
316
272
|
}
|
|
317
|
-
this.dirty = true;
|
|
318
273
|
}
|
|
319
274
|
async mv(src, dest) {
|
|
320
275
|
const srcPath = this.normalizePath(src);
|
|
@@ -341,7 +296,6 @@ class IndexedDbFs {
|
|
|
341
296
|
this.entries.delete(srcPath);
|
|
342
297
|
this.entries.set(destPath, entry);
|
|
343
298
|
}
|
|
344
|
-
this.dirty = true;
|
|
345
299
|
}
|
|
346
300
|
resolvePath(base, path) {
|
|
347
301
|
if (path.startsWith("/")) {
|
|
@@ -371,7 +325,6 @@ class IndexedDbFs {
|
|
|
371
325
|
}
|
|
372
326
|
entry.mode = mode;
|
|
373
327
|
entry.mtime = new Date;
|
|
374
|
-
this.dirty = true;
|
|
375
328
|
}
|
|
376
329
|
async symlink(target, linkPath) {
|
|
377
330
|
const normalizedLinkPath = this.normalizePath(linkPath);
|
|
@@ -385,7 +338,6 @@ class IndexedDbFs {
|
|
|
385
338
|
mode: DEFAULT_SYMLINK_MODE,
|
|
386
339
|
mtime: new Date
|
|
387
340
|
});
|
|
388
|
-
this.dirty = true;
|
|
389
341
|
}
|
|
390
342
|
async link(existingPath, newPath) {
|
|
391
343
|
const srcPath = this.normalizePath(existingPath);
|
|
@@ -407,7 +359,6 @@ class IndexedDbFs {
|
|
|
407
359
|
mode: entry.mode,
|
|
408
360
|
mtime: new Date
|
|
409
361
|
});
|
|
410
|
-
this.dirty = true;
|
|
411
362
|
}
|
|
412
363
|
async readlink(path) {
|
|
413
364
|
const normalizedPath = this.normalizePath(path);
|
|
@@ -453,10 +404,9 @@ class IndexedDbFs {
|
|
|
453
404
|
throw new Error(`ENOENT: no such file or directory, utimes '${path}'`);
|
|
454
405
|
}
|
|
455
406
|
entry.mtime = mtime;
|
|
456
|
-
this.dirty = true;
|
|
457
407
|
}
|
|
458
408
|
normalizePath(path) {
|
|
459
|
-
return
|
|
409
|
+
return Filesystem.normalizePath(path);
|
|
460
410
|
}
|
|
461
411
|
static normalizePath(path) {
|
|
462
412
|
if (!path || path === ".")
|
|
@@ -484,8 +434,7 @@ class IndexedDbFs {
|
|
|
484
434
|
return lastSlash === 0 ? "/" : path.slice(0, lastSlash);
|
|
485
435
|
}
|
|
486
436
|
ensureParentDirs(path) {
|
|
487
|
-
|
|
488
|
-
this.dirty = true;
|
|
437
|
+
Filesystem.ensureParentDirs(this.entries, path);
|
|
489
438
|
}
|
|
490
439
|
static ensureParentDirs(entries, path) {
|
|
491
440
|
const parts = path.split("/").filter(Boolean);
|
|
@@ -562,17 +511,20 @@ class IndexedDbFs {
|
|
|
562
511
|
return new TextDecoder("utf-8").decode(buffer);
|
|
563
512
|
}
|
|
564
513
|
if (encoding === "base64") {
|
|
565
|
-
|
|
566
|
-
for (let i = 0;i < buffer.byteLength; i++) {
|
|
567
|
-
binary += String.fromCharCode(buffer[i]);
|
|
568
|
-
}
|
|
569
|
-
return btoa(binary);
|
|
514
|
+
return this.encodeBase64(buffer);
|
|
570
515
|
}
|
|
571
516
|
if (encoding === "hex") {
|
|
572
517
|
return Array.from(buffer).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
573
518
|
}
|
|
574
519
|
return new TextDecoder("utf-8").decode(buffer);
|
|
575
520
|
}
|
|
521
|
+
encodeBase64(buffer) {
|
|
522
|
+
let binary = "";
|
|
523
|
+
for (let i = 0;i < buffer.byteLength; i++) {
|
|
524
|
+
binary += String.fromCharCode(buffer[i]);
|
|
525
|
+
}
|
|
526
|
+
return btoa(binary);
|
|
527
|
+
}
|
|
576
528
|
concatBuffers(a, b) {
|
|
577
529
|
const result = new Uint8Array(a.byteLength + b.byteLength);
|
|
578
530
|
result.set(a, 0);
|
|
@@ -585,77 +537,9 @@ class IndexedDbFs {
|
|
|
585
537
|
}
|
|
586
538
|
return value;
|
|
587
539
|
}
|
|
588
|
-
static openDatabase(dbName) {
|
|
589
|
-
return new Promise((resolve, reject) => {
|
|
590
|
-
const request = indexedDB.open(dbName, 1);
|
|
591
|
-
request.onerror = () => reject(request.error);
|
|
592
|
-
request.onsuccess = () => resolve(request.result);
|
|
593
|
-
request.onupgradeneeded = (event) => {
|
|
594
|
-
const db = event.target.result;
|
|
595
|
-
if (!db.objectStoreNames.contains("entries")) {
|
|
596
|
-
db.createObjectStore("entries", { keyPath: "path" });
|
|
597
|
-
}
|
|
598
|
-
};
|
|
599
|
-
});
|
|
600
|
-
}
|
|
601
|
-
static async loadEntries(db) {
|
|
602
|
-
const tx = db.transaction("entries", "readonly");
|
|
603
|
-
const store = tx.objectStore("entries");
|
|
604
|
-
return new Promise((resolve, reject) => {
|
|
605
|
-
const request = store.getAll();
|
|
606
|
-
request.onerror = () => reject(request.error);
|
|
607
|
-
request.onsuccess = () => {
|
|
608
|
-
const entries = new Map;
|
|
609
|
-
for (const record of request.result) {
|
|
610
|
-
entries.set(record.path, IndexedDbFs.deserializeEntry(record.entry));
|
|
611
|
-
}
|
|
612
|
-
resolve(entries);
|
|
613
|
-
};
|
|
614
|
-
});
|
|
615
|
-
}
|
|
616
|
-
serializeEntry(entry) {
|
|
617
|
-
if (entry.type === "file" && entry.content instanceof Uint8Array) {
|
|
618
|
-
return {
|
|
619
|
-
...entry,
|
|
620
|
-
content: Array.from(entry.content),
|
|
621
|
-
contentType: "uint8array",
|
|
622
|
-
mtime: entry.mtime.toISOString()
|
|
623
|
-
};
|
|
624
|
-
}
|
|
625
|
-
return {
|
|
626
|
-
...entry,
|
|
627
|
-
mtime: entry.mtime.toISOString()
|
|
628
|
-
};
|
|
629
|
-
}
|
|
630
|
-
static deserializeEntry(data) {
|
|
631
|
-
const mtime = new Date(data.mtime);
|
|
632
|
-
if (data.type === "file") {
|
|
633
|
-
let content = data.content;
|
|
634
|
-
if (data.contentType === "uint8array" && Array.isArray(content)) {
|
|
635
|
-
content = new Uint8Array(content);
|
|
636
|
-
}
|
|
637
|
-
return { type: "file", content, mode: data.mode, mtime };
|
|
638
|
-
}
|
|
639
|
-
if (data.type === "symlink") {
|
|
640
|
-
return { type: "symlink", target: data.target, mode: data.mode, mtime };
|
|
641
|
-
}
|
|
642
|
-
return { type: "directory", mode: data.mode, mtime };
|
|
643
|
-
}
|
|
644
|
-
promisifyRequest(request) {
|
|
645
|
-
return new Promise((resolve, reject) => {
|
|
646
|
-
request.onerror = () => reject(request.error);
|
|
647
|
-
request.onsuccess = () => resolve(request.result);
|
|
648
|
-
});
|
|
649
|
-
}
|
|
650
|
-
promisifyTransaction(tx) {
|
|
651
|
-
return new Promise((resolve, reject) => {
|
|
652
|
-
tx.onerror = () => reject(tx.error);
|
|
653
|
-
tx.oncomplete = () => resolve();
|
|
654
|
-
});
|
|
655
|
-
}
|
|
656
540
|
}
|
|
657
|
-
function
|
|
658
|
-
return
|
|
541
|
+
function createFilesystem(options) {
|
|
542
|
+
return Filesystem.create(options);
|
|
659
543
|
}
|
|
660
544
|
|
|
661
545
|
// src/packages.ts
|
|
@@ -1122,16 +1006,18 @@ var GLOBAL_KEY = "__sandlot_shared_modules__";
|
|
|
1122
1006
|
|
|
1123
1007
|
class SharedModuleRegistry {
|
|
1124
1008
|
modules = new Map;
|
|
1009
|
+
exportNames = new Map;
|
|
1125
1010
|
constructor() {
|
|
1126
1011
|
globalThis[GLOBAL_KEY] = this;
|
|
1127
1012
|
}
|
|
1128
1013
|
register(moduleId, module) {
|
|
1129
1014
|
this.modules.set(moduleId, module);
|
|
1015
|
+
this.exportNames.set(moduleId, introspectExports(module));
|
|
1130
1016
|
return this;
|
|
1131
1017
|
}
|
|
1132
1018
|
registerAll(modules) {
|
|
1133
1019
|
for (const [id, mod] of Object.entries(modules)) {
|
|
1134
|
-
this.
|
|
1020
|
+
this.register(id, mod);
|
|
1135
1021
|
}
|
|
1136
1022
|
return this;
|
|
1137
1023
|
}
|
|
@@ -1152,13 +1038,44 @@ class SharedModuleRegistry {
|
|
|
1152
1038
|
list() {
|
|
1153
1039
|
return [...this.modules.keys()];
|
|
1154
1040
|
}
|
|
1041
|
+
getExportNames(moduleId) {
|
|
1042
|
+
return this.exportNames.get(moduleId) ?? [];
|
|
1043
|
+
}
|
|
1155
1044
|
clear() {
|
|
1156
1045
|
this.modules.clear();
|
|
1046
|
+
this.exportNames.clear();
|
|
1157
1047
|
}
|
|
1158
1048
|
get size() {
|
|
1159
1049
|
return this.modules.size;
|
|
1160
1050
|
}
|
|
1161
1051
|
}
|
|
1052
|
+
function introspectExports(module) {
|
|
1053
|
+
if (module === null || module === undefined) {
|
|
1054
|
+
return [];
|
|
1055
|
+
}
|
|
1056
|
+
if (typeof module !== "object" && typeof module !== "function") {
|
|
1057
|
+
return [];
|
|
1058
|
+
}
|
|
1059
|
+
const exports = [];
|
|
1060
|
+
for (const key of Object.keys(module)) {
|
|
1061
|
+
if (isValidIdentifier(key)) {
|
|
1062
|
+
exports.push(key);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
return exports;
|
|
1066
|
+
}
|
|
1067
|
+
function isValidIdentifier(name) {
|
|
1068
|
+
if (name.length === 0)
|
|
1069
|
+
return false;
|
|
1070
|
+
if (!/^[a-zA-Z_$]/.test(name))
|
|
1071
|
+
return false;
|
|
1072
|
+
if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name))
|
|
1073
|
+
return false;
|
|
1074
|
+
const reserved = ["default", "class", "function", "var", "let", "const", "import", "export"];
|
|
1075
|
+
if (reserved.includes(name))
|
|
1076
|
+
return false;
|
|
1077
|
+
return true;
|
|
1078
|
+
}
|
|
1162
1079
|
var defaultRegistry = null;
|
|
1163
1080
|
function getSharedModuleRegistry() {
|
|
1164
1081
|
if (!defaultRegistry) {
|
|
@@ -1178,6 +1095,9 @@ function unregisterSharedModule(moduleId) {
|
|
|
1178
1095
|
function clearSharedModules() {
|
|
1179
1096
|
getSharedModuleRegistry().clear();
|
|
1180
1097
|
}
|
|
1098
|
+
function getSharedModuleExports(moduleId) {
|
|
1099
|
+
return getSharedModuleRegistry().getExportNames(moduleId);
|
|
1100
|
+
}
|
|
1181
1101
|
function getSharedModuleRuntimeCode(moduleId) {
|
|
1182
1102
|
return `
|
|
1183
1103
|
(function() {
|
|
@@ -1195,15 +1115,32 @@ function getSharedModuleRuntimeCode(moduleId) {
|
|
|
1195
1115
|
|
|
1196
1116
|
// src/bundler.ts
|
|
1197
1117
|
var esbuild = null;
|
|
1118
|
+
function isServerEnvironment() {
|
|
1119
|
+
if (typeof globalThis !== "undefined" && "Bun" in globalThis) {
|
|
1120
|
+
return true;
|
|
1121
|
+
}
|
|
1122
|
+
if (typeof globalThis !== "undefined" && "Deno" in globalThis) {
|
|
1123
|
+
return true;
|
|
1124
|
+
}
|
|
1125
|
+
if (typeof process !== "undefined" && process.versions?.node) {
|
|
1126
|
+
return true;
|
|
1127
|
+
}
|
|
1128
|
+
return false;
|
|
1129
|
+
}
|
|
1198
1130
|
async function getEsbuild() {
|
|
1199
1131
|
if (esbuild)
|
|
1200
1132
|
return esbuild;
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1133
|
+
if (isServerEnvironment()) {
|
|
1134
|
+
const mod = await import("esbuild");
|
|
1135
|
+
esbuild = mod.default ?? mod;
|
|
1136
|
+
} else {
|
|
1137
|
+
const cdnUrl = `https://esm.sh/esbuild-wasm@${ESBUILD_VERSION}`;
|
|
1138
|
+
const mod = await import(cdnUrl);
|
|
1139
|
+
esbuild = mod.default ?? mod;
|
|
1140
|
+
if (typeof esbuild?.initialize !== "function") {
|
|
1141
|
+
console.error("esbuild-wasm module structure:", mod);
|
|
1142
|
+
throw new Error("Failed to load esbuild-wasm: initialize function not found");
|
|
1143
|
+
}
|
|
1207
1144
|
}
|
|
1208
1145
|
return esbuild;
|
|
1209
1146
|
}
|
|
@@ -1231,12 +1168,14 @@ async function initBundler() {
|
|
|
1231
1168
|
await initPromise;
|
|
1232
1169
|
return;
|
|
1233
1170
|
}
|
|
1234
|
-
checkCrossOriginIsolation();
|
|
1235
1171
|
initPromise = (async () => {
|
|
1236
1172
|
const es = await getEsbuild();
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1173
|
+
if (!isServerEnvironment() && typeof es.initialize === "function") {
|
|
1174
|
+
checkCrossOriginIsolation();
|
|
1175
|
+
await es.initialize({
|
|
1176
|
+
wasmURL: getWasmUrl()
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1240
1179
|
})();
|
|
1241
1180
|
await initPromise;
|
|
1242
1181
|
initialized = true;
|
|
@@ -1375,55 +1314,12 @@ ${generateNamedExports(args.path)}
|
|
|
1375
1314
|
};
|
|
1376
1315
|
}
|
|
1377
1316
|
function generateNamedExports(moduleId) {
|
|
1378
|
-
const
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
"useEffect",
|
|
1382
|
-
"useContext",
|
|
1383
|
-
"useReducer",
|
|
1384
|
-
"useCallback",
|
|
1385
|
-
"useMemo",
|
|
1386
|
-
"useRef",
|
|
1387
|
-
"useImperativeHandle",
|
|
1388
|
-
"useLayoutEffect",
|
|
1389
|
-
"useDebugValue",
|
|
1390
|
-
"useDeferredValue",
|
|
1391
|
-
"useTransition",
|
|
1392
|
-
"useId",
|
|
1393
|
-
"useSyncExternalStore",
|
|
1394
|
-
"useInsertionEffect",
|
|
1395
|
-
"useOptimistic",
|
|
1396
|
-
"useActionState",
|
|
1397
|
-
"createElement",
|
|
1398
|
-
"cloneElement",
|
|
1399
|
-
"createContext",
|
|
1400
|
-
"forwardRef",
|
|
1401
|
-
"lazy",
|
|
1402
|
-
"memo",
|
|
1403
|
-
"startTransition",
|
|
1404
|
-
"Children",
|
|
1405
|
-
"Component",
|
|
1406
|
-
"PureComponent",
|
|
1407
|
-
"Fragment",
|
|
1408
|
-
"Profiler",
|
|
1409
|
-
"StrictMode",
|
|
1410
|
-
"Suspense",
|
|
1411
|
-
"version",
|
|
1412
|
-
"isValidElement"
|
|
1413
|
-
],
|
|
1414
|
-
"react-dom": ["createPortal", "flushSync", "version"],
|
|
1415
|
-
"react-dom/client": ["createRoot", "hydrateRoot"],
|
|
1416
|
-
"react-dom/server": ["renderToString", "renderToStaticMarkup", "renderToPipeableStream"]
|
|
1417
|
-
};
|
|
1418
|
-
const exports = knownExports[moduleId];
|
|
1419
|
-
if (!exports) {
|
|
1420
|
-
return `
|
|
1421
|
-
// Dynamic re-export for unknown module
|
|
1422
|
-
export const __moduleProxy__ = __sandlot_mod__;
|
|
1423
|
-
`;
|
|
1424
|
-
}
|
|
1425
|
-
return exports.map((name) => `export const ${name} = __sandlot_mod__.${name};`).join(`
|
|
1317
|
+
const exports = getSharedModuleExports(moduleId);
|
|
1318
|
+
if (exports.length > 0) {
|
|
1319
|
+
return exports.map((name) => `export const ${name} = __sandlot_mod__.${name};`).join(`
|
|
1426
1320
|
`);
|
|
1321
|
+
}
|
|
1322
|
+
return `// No exports discovered for "${moduleId}" - use default import or call registerSharedModules() first`;
|
|
1427
1323
|
}
|
|
1428
1324
|
async function bundle(options) {
|
|
1429
1325
|
await initBundler();
|
|
@@ -1466,7 +1362,8 @@ async function bundle(options) {
|
|
|
1466
1362
|
globalName,
|
|
1467
1363
|
target,
|
|
1468
1364
|
external,
|
|
1469
|
-
plugins: [plugin]
|
|
1365
|
+
plugins: [plugin],
|
|
1366
|
+
jsx: "automatic"
|
|
1470
1367
|
});
|
|
1471
1368
|
const code = result.outputFiles?.[0]?.text ?? "";
|
|
1472
1369
|
return {
|
|
@@ -1489,7 +1386,21 @@ async function bundleAndImport(options) {
|
|
|
1489
1386
|
}
|
|
1490
1387
|
}
|
|
1491
1388
|
|
|
1492
|
-
// src/commands.ts
|
|
1389
|
+
// src/commands/types.ts
|
|
1390
|
+
function formatEsbuildMessages(messages) {
|
|
1391
|
+
if (messages.length === 0)
|
|
1392
|
+
return "";
|
|
1393
|
+
return messages.map((msg) => {
|
|
1394
|
+
if (msg.location) {
|
|
1395
|
+
const { file, line, column } = msg.location;
|
|
1396
|
+
const loc = file ? `${file}${line ? `:${line}` : ""}${column ? `:${column}` : ""}` : "";
|
|
1397
|
+
return loc ? `${loc}: ${msg.text}` : msg.text;
|
|
1398
|
+
}
|
|
1399
|
+
return msg.text;
|
|
1400
|
+
}).join(`
|
|
1401
|
+
`);
|
|
1402
|
+
}
|
|
1403
|
+
// src/commands/compile.ts
|
|
1493
1404
|
import { defineCommand } from "just-bash/browser";
|
|
1494
1405
|
|
|
1495
1406
|
// src/typechecker.ts
|
|
@@ -1880,20 +1791,7 @@ async function hasExport(result, exportName) {
|
|
|
1880
1791
|
return exportName in module;
|
|
1881
1792
|
}
|
|
1882
1793
|
|
|
1883
|
-
// src/commands.ts
|
|
1884
|
-
function formatEsbuildMessages(messages) {
|
|
1885
|
-
if (messages.length === 0)
|
|
1886
|
-
return "";
|
|
1887
|
-
return messages.map((msg) => {
|
|
1888
|
-
if (msg.location) {
|
|
1889
|
-
const { file, line, column } = msg.location;
|
|
1890
|
-
const loc = file ? `${file}${line ? `:${line}` : ""}${column ? `:${column}` : ""}` : "";
|
|
1891
|
-
return loc ? `${loc}: ${msg.text}` : msg.text;
|
|
1892
|
-
}
|
|
1893
|
-
return msg.text;
|
|
1894
|
-
}).join(`
|
|
1895
|
-
`);
|
|
1896
|
-
}
|
|
1794
|
+
// src/commands/compile.ts
|
|
1897
1795
|
function createTscCommand(deps) {
|
|
1898
1796
|
const { fs, libFiles, tsconfigPath } = deps;
|
|
1899
1797
|
return defineCommand("tsc", async (args, _ctx) => {
|
|
@@ -1958,7 +1856,7 @@ ${formatDiagnosticsForAgent(result.diagnostics.filter((d) => d.category === "war
|
|
|
1958
1856
|
});
|
|
1959
1857
|
}
|
|
1960
1858
|
function createBuildCommand(deps) {
|
|
1961
|
-
const { fs, libFiles, tsconfigPath, onBuild, sharedModules } = deps;
|
|
1859
|
+
const { fs, libFiles, tsconfigPath, onBuild, getValidation, sharedModules } = deps;
|
|
1962
1860
|
return defineCommand("build", async (args, _ctx) => {
|
|
1963
1861
|
let entryPoint = null;
|
|
1964
1862
|
let skipTypecheck = false;
|
|
@@ -2030,8 +1928,39 @@ ${formatted}
|
|
|
2030
1928
|
minify,
|
|
2031
1929
|
sharedModules
|
|
2032
1930
|
});
|
|
1931
|
+
let loadedModule;
|
|
1932
|
+
try {
|
|
1933
|
+
loadedModule = await loadModule(bundleResult);
|
|
1934
|
+
} catch (err) {
|
|
1935
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
1936
|
+
return {
|
|
1937
|
+
stdout: "",
|
|
1938
|
+
stderr: `Build failed: Module failed to load.
|
|
1939
|
+
|
|
1940
|
+
${errorMessage}
|
|
1941
|
+
`,
|
|
1942
|
+
exitCode: 1
|
|
1943
|
+
};
|
|
1944
|
+
}
|
|
1945
|
+
const validateFn = getValidation?.();
|
|
1946
|
+
let validatedModule = loadedModule;
|
|
1947
|
+
if (validateFn) {
|
|
1948
|
+
try {
|
|
1949
|
+
validatedModule = validateFn(loadedModule);
|
|
1950
|
+
} catch (err) {
|
|
1951
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
1952
|
+
return {
|
|
1953
|
+
stdout: "",
|
|
1954
|
+
stderr: `Build failed: Validation error.
|
|
1955
|
+
|
|
1956
|
+
${errorMessage}
|
|
1957
|
+
`,
|
|
1958
|
+
exitCode: 1
|
|
1959
|
+
};
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
2033
1962
|
if (onBuild) {
|
|
2034
|
-
await onBuild(bundleResult);
|
|
1963
|
+
await onBuild({ bundle: bundleResult, module: validatedModule });
|
|
2035
1964
|
}
|
|
2036
1965
|
let output = `Build successful!
|
|
2037
1966
|
`;
|
|
@@ -2047,6 +1976,15 @@ ${formatted}
|
|
|
2047
1976
|
}
|
|
2048
1977
|
output += `Bundled: ${bundleResult.includedFiles.length} file(s)
|
|
2049
1978
|
`;
|
|
1979
|
+
const exportNames = Object.keys(loadedModule).filter((k) => !k.startsWith("__"));
|
|
1980
|
+
if (exportNames.length > 0) {
|
|
1981
|
+
output += `Exports: ${exportNames.join(", ")}
|
|
1982
|
+
`;
|
|
1983
|
+
}
|
|
1984
|
+
if (validateFn) {
|
|
1985
|
+
output += `Validation: passed
|
|
1986
|
+
`;
|
|
1987
|
+
}
|
|
2050
1988
|
if (bundleResult.warnings.length > 0) {
|
|
2051
1989
|
output += `
|
|
2052
1990
|
Build warnings:
|
|
@@ -2078,9 +2016,11 @@ ${formatDiagnosticsForAgent(warnings)}
|
|
|
2078
2016
|
}
|
|
2079
2017
|
});
|
|
2080
2018
|
}
|
|
2019
|
+
// src/commands/packages.ts
|
|
2020
|
+
import { defineCommand as defineCommand2 } from "just-bash/browser";
|
|
2081
2021
|
function createInstallCommand(deps) {
|
|
2082
2022
|
const { fs, typesCache } = deps;
|
|
2083
|
-
return
|
|
2023
|
+
return defineCommand2("install", async (args, _ctx) => {
|
|
2084
2024
|
if (args.length === 0) {
|
|
2085
2025
|
return {
|
|
2086
2026
|
stdout: "",
|
|
@@ -2134,7 +2074,7 @@ Examples:
|
|
|
2134
2074
|
}
|
|
2135
2075
|
function createUninstallCommand(deps) {
|
|
2136
2076
|
const { fs } = deps;
|
|
2137
|
-
return
|
|
2077
|
+
return defineCommand2("uninstall", async (args, _ctx) => {
|
|
2138
2078
|
if (args.length === 0) {
|
|
2139
2079
|
return {
|
|
2140
2080
|
stdout: "",
|
|
@@ -2179,7 +2119,7 @@ function createUninstallCommand(deps) {
|
|
|
2179
2119
|
}
|
|
2180
2120
|
function createListCommand(deps) {
|
|
2181
2121
|
const { fs } = deps;
|
|
2182
|
-
return
|
|
2122
|
+
return defineCommand2("list", async (_args, _ctx) => {
|
|
2183
2123
|
try {
|
|
2184
2124
|
const packages = await listPackages(fs);
|
|
2185
2125
|
if (packages.length === 0) {
|
|
@@ -2209,9 +2149,11 @@ function createListCommand(deps) {
|
|
|
2209
2149
|
}
|
|
2210
2150
|
});
|
|
2211
2151
|
}
|
|
2152
|
+
// src/commands/run.ts
|
|
2153
|
+
import { defineCommand as defineCommand3 } from "just-bash/browser";
|
|
2212
2154
|
function createRunCommand(deps) {
|
|
2213
2155
|
const { fs, libFiles, tsconfigPath, runOptions = {}, sharedModules } = deps;
|
|
2214
|
-
return
|
|
2156
|
+
return defineCommand3("run", async (args, _ctx) => {
|
|
2215
2157
|
let entryPoint = null;
|
|
2216
2158
|
let skipTypecheck = runOptions.skipTypecheck ?? false;
|
|
2217
2159
|
let timeout = runOptions.timeout ?? 30000;
|
|
@@ -2388,6 +2330,7 @@ ${err.stack}` : "";
|
|
|
2388
2330
|
}
|
|
2389
2331
|
});
|
|
2390
2332
|
}
|
|
2333
|
+
// src/commands/index.ts
|
|
2391
2334
|
function createDefaultCommands(deps) {
|
|
2392
2335
|
return [
|
|
2393
2336
|
createTscCommand(deps),
|
|
@@ -2402,9 +2345,6 @@ function createDefaultCommands(deps) {
|
|
|
2402
2345
|
// src/ts-libs.ts
|
|
2403
2346
|
var TS_VERSION = "5.9.3";
|
|
2404
2347
|
var CDN_BASE = `https://cdn.jsdelivr.net/npm/typescript@${TS_VERSION}/lib`;
|
|
2405
|
-
var DB_NAME = "ts-lib-cache";
|
|
2406
|
-
var DB_VERSION = 1;
|
|
2407
|
-
var STORE_NAME = "libs";
|
|
2408
2348
|
function getDefaultBrowserLibs() {
|
|
2409
2349
|
return ["es2020", "dom", "dom.iterable"];
|
|
2410
2350
|
}
|
|
@@ -2469,94 +2409,36 @@ async function fetchAllLibs(libs) {
|
|
|
2469
2409
|
}
|
|
2470
2410
|
return result;
|
|
2471
2411
|
}
|
|
2472
|
-
|
|
2473
|
-
return new Promise((resolve, reject) => {
|
|
2474
|
-
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
2475
|
-
request.onerror = () => reject(request.error);
|
|
2476
|
-
request.onsuccess = () => resolve(request.result);
|
|
2477
|
-
request.onupgradeneeded = (event) => {
|
|
2478
|
-
const db = event.target.result;
|
|
2479
|
-
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
2480
|
-
db.createObjectStore(STORE_NAME);
|
|
2481
|
-
}
|
|
2482
|
-
};
|
|
2483
|
-
});
|
|
2484
|
-
}
|
|
2485
|
-
function promisifyRequest(request) {
|
|
2486
|
-
return new Promise((resolve, reject) => {
|
|
2487
|
-
request.onsuccess = () => resolve(request.result);
|
|
2488
|
-
request.onerror = () => reject(request.error);
|
|
2489
|
-
});
|
|
2490
|
-
}
|
|
2491
|
-
function getCacheKey() {
|
|
2492
|
-
return `libs-${TS_VERSION}`;
|
|
2493
|
-
}
|
|
2412
|
+
var memoryCache = null;
|
|
2494
2413
|
|
|
2495
2414
|
class LibCache {
|
|
2496
|
-
db;
|
|
2497
|
-
constructor(db) {
|
|
2498
|
-
this.db = db;
|
|
2499
|
-
}
|
|
2500
|
-
static async create() {
|
|
2501
|
-
const db = await openDatabase();
|
|
2502
|
-
return new LibCache(db);
|
|
2503
|
-
}
|
|
2504
2415
|
async getOrFetch(libs) {
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
const missing = libs.filter((lib) => !cached.has(lib));
|
|
2416
|
+
if (memoryCache) {
|
|
2417
|
+
const missing = libs.filter((lib) => !memoryCache.has(lib));
|
|
2508
2418
|
if (missing.length === 0) {
|
|
2509
|
-
return
|
|
2419
|
+
return memoryCache;
|
|
2510
2420
|
}
|
|
2511
2421
|
console.log(`Cache missing libs: ${missing.join(", ")}, fetching all...`);
|
|
2512
2422
|
}
|
|
2513
2423
|
console.log(`Fetching TypeScript libs from CDN: ${libs.join(", ")}...`);
|
|
2514
2424
|
const fetched = await fetchAllLibs(libs);
|
|
2515
2425
|
console.log(`Fetched ${fetched.size} lib files`);
|
|
2516
|
-
|
|
2426
|
+
memoryCache = fetched;
|
|
2517
2427
|
return fetched;
|
|
2518
2428
|
}
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
const store = tx.objectStore(STORE_NAME);
|
|
2522
|
-
const key = getCacheKey();
|
|
2523
|
-
const cached = await promisifyRequest(store.get(key));
|
|
2524
|
-
if (!cached) {
|
|
2525
|
-
return null;
|
|
2526
|
-
}
|
|
2527
|
-
if (cached.version !== TS_VERSION) {
|
|
2528
|
-
console.log(`Cache version mismatch: ${cached.version} vs ${TS_VERSION}`);
|
|
2529
|
-
return null;
|
|
2530
|
-
}
|
|
2531
|
-
return new Map(Object.entries(cached.libs));
|
|
2532
|
-
}
|
|
2533
|
-
async set(libs) {
|
|
2534
|
-
const tx = this.db.transaction(STORE_NAME, "readwrite");
|
|
2535
|
-
const store = tx.objectStore(STORE_NAME);
|
|
2536
|
-
const key = getCacheKey();
|
|
2537
|
-
const cached = {
|
|
2538
|
-
version: TS_VERSION,
|
|
2539
|
-
timestamp: Date.now(),
|
|
2540
|
-
libs: Object.fromEntries(libs)
|
|
2541
|
-
};
|
|
2542
|
-
await promisifyRequest(store.put(cached, key));
|
|
2429
|
+
get() {
|
|
2430
|
+
return memoryCache;
|
|
2543
2431
|
}
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
const store = tx.objectStore(STORE_NAME);
|
|
2547
|
-
await promisifyRequest(store.clear());
|
|
2432
|
+
set(libs) {
|
|
2433
|
+
memoryCache = libs;
|
|
2548
2434
|
}
|
|
2549
|
-
|
|
2550
|
-
|
|
2435
|
+
clear() {
|
|
2436
|
+
memoryCache = null;
|
|
2551
2437
|
}
|
|
2552
2438
|
}
|
|
2553
2439
|
async function fetchAndCacheLibs(libs = getDefaultBrowserLibs()) {
|
|
2554
|
-
const cache =
|
|
2555
|
-
|
|
2556
|
-
return await cache.getOrFetch(libs);
|
|
2557
|
-
} finally {
|
|
2558
|
-
cache.close();
|
|
2559
|
-
}
|
|
2440
|
+
const cache = new LibCache;
|
|
2441
|
+
return cache.getOrFetch(libs);
|
|
2560
2442
|
}
|
|
2561
2443
|
|
|
2562
2444
|
// src/shared-resources.ts
|
|
@@ -2597,9 +2479,7 @@ function hasDefaultResources() {
|
|
|
2597
2479
|
// src/build-emitter.ts
|
|
2598
2480
|
class BuildEmitter {
|
|
2599
2481
|
listeners = new Set;
|
|
2600
|
-
lastResult = null;
|
|
2601
2482
|
emit = async (result) => {
|
|
2602
|
-
this.lastResult = result;
|
|
2603
2483
|
const promises = [];
|
|
2604
2484
|
for (const listener of this.listeners) {
|
|
2605
2485
|
const ret = listener(result);
|
|
@@ -2615,245 +2495,156 @@ class BuildEmitter {
|
|
|
2615
2495
|
this.listeners.delete(callback);
|
|
2616
2496
|
};
|
|
2617
2497
|
}
|
|
2618
|
-
waitFor() {
|
|
2619
|
-
if (this.lastResult) {
|
|
2620
|
-
const result = this.lastResult;
|
|
2621
|
-
this.lastResult = null;
|
|
2622
|
-
return Promise.resolve(result);
|
|
2623
|
-
}
|
|
2624
|
-
return new Promise((resolve) => {
|
|
2625
|
-
const unsub = this.on((result) => {
|
|
2626
|
-
unsub();
|
|
2627
|
-
this.lastResult = null;
|
|
2628
|
-
resolve(result);
|
|
2629
|
-
});
|
|
2630
|
-
});
|
|
2631
|
-
}
|
|
2632
2498
|
}
|
|
2633
2499
|
|
|
2634
2500
|
// src/sandbox.ts
|
|
2635
2501
|
async function createSandbox(options = {}) {
|
|
2636
|
-
const {
|
|
2637
|
-
fsOptions = {},
|
|
2638
|
-
tsconfigPath = "/tsconfig.json",
|
|
2639
|
-
resources: providedResources,
|
|
2640
|
-
onBuild: onBuildCallback,
|
|
2641
|
-
customCommands = [],
|
|
2642
|
-
sharedModules
|
|
2643
|
-
} = options;
|
|
2644
|
-
const fsPromise = IndexedDbFs.create(fsOptions);
|
|
2645
|
-
const resourcesPromise = providedResources ? Promise.resolve(providedResources) : getDefaultResources();
|
|
2646
|
-
const bundlerPromise = initBundler();
|
|
2647
|
-
const [fs, resources] = await Promise.all([fsPromise, resourcesPromise, bundlerPromise]);
|
|
2648
|
-
const libFiles = resources.libFiles;
|
|
2649
|
-
const typesCache = resources.typesCache;
|
|
2650
|
-
const buildEmitter = new BuildEmitter;
|
|
2651
|
-
if (onBuildCallback) {
|
|
2652
|
-
buildEmitter.on(onBuildCallback);
|
|
2653
|
-
}
|
|
2654
|
-
const commandDeps = {
|
|
2655
|
-
fs,
|
|
2656
|
-
libFiles,
|
|
2657
|
-
tsconfigPath,
|
|
2658
|
-
onBuild: buildEmitter.emit,
|
|
2659
|
-
typesCache,
|
|
2660
|
-
sharedModules
|
|
2661
|
-
};
|
|
2662
|
-
const defaultCommands = createDefaultCommands(commandDeps);
|
|
2663
|
-
const bash = new Bash({
|
|
2664
|
-
fs,
|
|
2665
|
-
cwd: "/",
|
|
2666
|
-
customCommands: [...defaultCommands, ...customCommands]
|
|
2667
|
-
});
|
|
2668
|
-
return {
|
|
2669
|
-
fs,
|
|
2670
|
-
bash,
|
|
2671
|
-
isDirty: () => fs.isDirty(),
|
|
2672
|
-
save: () => fs.save(),
|
|
2673
|
-
close: () => fs.close(),
|
|
2674
|
-
onBuild: (callback) => buildEmitter.on(callback)
|
|
2675
|
-
};
|
|
2676
|
-
}
|
|
2677
|
-
async function createInMemorySandbox(options = {}) {
|
|
2678
2502
|
const {
|
|
2679
2503
|
initialFiles,
|
|
2504
|
+
maxFilesystemSize,
|
|
2680
2505
|
tsconfigPath = "/tsconfig.json",
|
|
2681
2506
|
resources: providedResources,
|
|
2682
2507
|
onBuild: onBuildCallback,
|
|
2683
2508
|
customCommands = [],
|
|
2684
|
-
sharedModules
|
|
2509
|
+
sharedModules,
|
|
2510
|
+
bashOptions = {}
|
|
2685
2511
|
} = options;
|
|
2512
|
+
const fs = Filesystem.create({
|
|
2513
|
+
initialFiles,
|
|
2514
|
+
maxSizeBytes: maxFilesystemSize
|
|
2515
|
+
});
|
|
2686
2516
|
const resourcesPromise = providedResources ? Promise.resolve(providedResources) : getDefaultResources();
|
|
2687
2517
|
const bundlerPromise = initBundler();
|
|
2688
|
-
const fs = IndexedDbFs.createInMemory({ initialFiles });
|
|
2689
2518
|
const [resources] = await Promise.all([resourcesPromise, bundlerPromise]);
|
|
2690
2519
|
const libFiles = resources.libFiles;
|
|
2691
2520
|
const typesCache = resources.typesCache;
|
|
2521
|
+
if (sharedModules && sharedModules.length > 0) {
|
|
2522
|
+
const basePackages = new Set;
|
|
2523
|
+
for (const moduleId of sharedModules) {
|
|
2524
|
+
const { packageName } = parseImportPath(moduleId);
|
|
2525
|
+
basePackages.add(packageName);
|
|
2526
|
+
}
|
|
2527
|
+
await Promise.all(Array.from(basePackages).map(async (packageName) => {
|
|
2528
|
+
try {
|
|
2529
|
+
await installPackage(fs, packageName, { cache: typesCache });
|
|
2530
|
+
} catch (err) {
|
|
2531
|
+
console.warn(`[sandlot] Failed to install types for shared module "${packageName}":`, err);
|
|
2532
|
+
}
|
|
2533
|
+
}));
|
|
2534
|
+
}
|
|
2692
2535
|
const buildEmitter = new BuildEmitter;
|
|
2536
|
+
let lastBuild = null;
|
|
2537
|
+
buildEmitter.on((result) => {
|
|
2538
|
+
lastBuild = result;
|
|
2539
|
+
});
|
|
2693
2540
|
if (onBuildCallback) {
|
|
2694
2541
|
buildEmitter.on(onBuildCallback);
|
|
2695
2542
|
}
|
|
2543
|
+
let validationFn = null;
|
|
2696
2544
|
const commandDeps = {
|
|
2697
2545
|
fs,
|
|
2698
2546
|
libFiles,
|
|
2699
2547
|
tsconfigPath,
|
|
2700
2548
|
onBuild: buildEmitter.emit,
|
|
2549
|
+
getValidation: () => validationFn,
|
|
2701
2550
|
typesCache,
|
|
2702
2551
|
sharedModules
|
|
2703
2552
|
};
|
|
2704
2553
|
const defaultCommands = createDefaultCommands(commandDeps);
|
|
2705
2554
|
const bash = new Bash({
|
|
2706
|
-
|
|
2555
|
+
...bashOptions,
|
|
2707
2556
|
cwd: "/",
|
|
2557
|
+
fs,
|
|
2708
2558
|
customCommands: [...defaultCommands, ...customCommands]
|
|
2709
2559
|
});
|
|
2710
2560
|
return {
|
|
2711
2561
|
fs,
|
|
2712
2562
|
bash,
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2563
|
+
get lastBuild() {
|
|
2564
|
+
return lastBuild;
|
|
2565
|
+
},
|
|
2566
|
+
getState: () => ({ files: fs.getFiles() }),
|
|
2567
|
+
onBuild: (callback) => buildEmitter.on(callback),
|
|
2568
|
+
setValidation: (fn) => {
|
|
2569
|
+
validationFn = fn;
|
|
2570
|
+
},
|
|
2571
|
+
clearValidation: () => {
|
|
2572
|
+
validationFn = null;
|
|
2573
|
+
}
|
|
2717
2574
|
};
|
|
2718
2575
|
}
|
|
2719
|
-
// src/
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
initPromise = null;
|
|
2726
|
-
nextId = 1;
|
|
2727
|
-
options;
|
|
2728
|
-
constructor(options = {}) {
|
|
2729
|
-
this.options = {
|
|
2730
|
-
libs: options.libs ?? getDefaultBrowserLibs(),
|
|
2731
|
-
...options
|
|
2732
|
-
};
|
|
2733
|
-
}
|
|
2734
|
-
async initialize() {
|
|
2735
|
-
if (this.initialized)
|
|
2736
|
-
return;
|
|
2737
|
-
if (this.initPromise) {
|
|
2738
|
-
await this.initPromise;
|
|
2739
|
-
return;
|
|
2740
|
-
}
|
|
2741
|
-
this.initPromise = this.doInitialize();
|
|
2742
|
-
await this.initPromise;
|
|
2743
|
-
this.initialized = true;
|
|
2744
|
-
}
|
|
2745
|
-
async doInitialize() {
|
|
2746
|
-
this.resources = await createSharedResources({
|
|
2747
|
-
libs: this.options.libs,
|
|
2748
|
-
skipLibs: this.options.skipLibs,
|
|
2749
|
-
skipBundler: this.options.skipBundler
|
|
2750
|
-
});
|
|
2751
|
-
}
|
|
2752
|
-
async createSandbox(options = {}) {
|
|
2753
|
-
await this.initialize();
|
|
2754
|
-
const {
|
|
2755
|
-
id = `sandbox-${this.nextId++}`,
|
|
2756
|
-
fsOptions = {},
|
|
2757
|
-
initialFiles,
|
|
2758
|
-
tsconfigPath = "/tsconfig.json",
|
|
2759
|
-
onBuild,
|
|
2760
|
-
customCommands = [],
|
|
2761
|
-
inMemory = true,
|
|
2762
|
-
sharedModules = this.options.sharedModules
|
|
2763
|
-
} = options;
|
|
2764
|
-
let fs;
|
|
2765
|
-
if (inMemory) {
|
|
2766
|
-
fs = IndexedDbFs.createInMemory({
|
|
2767
|
-
initialFiles,
|
|
2768
|
-
maxSizeBytes: fsOptions.maxSizeBytes
|
|
2769
|
-
});
|
|
2770
|
-
} else {
|
|
2771
|
-
fs = await IndexedDbFs.create({
|
|
2772
|
-
dbName: fsOptions.dbName ?? id,
|
|
2773
|
-
initialFiles,
|
|
2774
|
-
maxSizeBytes: fsOptions.maxSizeBytes
|
|
2775
|
-
});
|
|
2576
|
+
// src/builder.ts
|
|
2577
|
+
function createBuilder(options) {
|
|
2578
|
+
return async (prompt, callOptions) => {
|
|
2579
|
+
const sandbox = options.sandbox ?? await createSandbox(options.sandboxOptions);
|
|
2580
|
+
if (callOptions?.validate) {
|
|
2581
|
+
sandbox.setValidation(callOptions.validate);
|
|
2776
2582
|
}
|
|
2777
|
-
const
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
}
|
|
2781
|
-
const commandDeps = {
|
|
2782
|
-
fs,
|
|
2783
|
-
libFiles: this.resources.libFiles,
|
|
2784
|
-
tsconfigPath,
|
|
2785
|
-
onBuild: buildEmitter.emit,
|
|
2786
|
-
typesCache: this.resources.typesCache,
|
|
2787
|
-
sharedModules
|
|
2788
|
-
};
|
|
2789
|
-
const defaultCommands = createDefaultCommands(commandDeps);
|
|
2790
|
-
const bash = new Bash2({
|
|
2791
|
-
fs,
|
|
2792
|
-
cwd: "/",
|
|
2793
|
-
customCommands: [...defaultCommands, ...customCommands]
|
|
2583
|
+
const captured = { output: null };
|
|
2584
|
+
const unsubscribe = sandbox.onBuild((output) => {
|
|
2585
|
+
captured.output = output;
|
|
2794
2586
|
});
|
|
2795
|
-
const
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
}
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
return Array.from(this.sandboxes.values());
|
|
2815
|
-
}
|
|
2816
|
-
closeSandbox(id) {
|
|
2817
|
-
const sandbox = this.sandboxes.get(id);
|
|
2818
|
-
if (sandbox) {
|
|
2819
|
-
sandbox.close();
|
|
2820
|
-
return true;
|
|
2821
|
-
}
|
|
2822
|
-
return false;
|
|
2823
|
-
}
|
|
2824
|
-
destroyAll() {
|
|
2825
|
-
for (const sandbox of this.sandboxes.values()) {
|
|
2826
|
-
sandbox.fs.close();
|
|
2587
|
+
const { timeout, signal } = callOptions ?? {};
|
|
2588
|
+
let timeoutId;
|
|
2589
|
+
let abortController;
|
|
2590
|
+
if (timeout !== undefined || signal !== undefined) {
|
|
2591
|
+
abortController = new AbortController;
|
|
2592
|
+
if (timeout !== undefined) {
|
|
2593
|
+
timeoutId = setTimeout(() => {
|
|
2594
|
+
abortController.abort(new Error(`Build timed out after ${timeout}ms`));
|
|
2595
|
+
}, timeout);
|
|
2596
|
+
}
|
|
2597
|
+
if (signal !== undefined) {
|
|
2598
|
+
if (signal.aborted) {
|
|
2599
|
+
abortController.abort(signal.reason);
|
|
2600
|
+
} else {
|
|
2601
|
+
signal.addEventListener("abort", () => {
|
|
2602
|
+
abortController.abort(signal.reason);
|
|
2603
|
+
}, { once: true });
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2827
2606
|
}
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2607
|
+
let result;
|
|
2608
|
+
let error = null;
|
|
2609
|
+
try {
|
|
2610
|
+
const buildPromise = options.build(sandbox, prompt);
|
|
2611
|
+
if (abortController) {
|
|
2612
|
+
const abortPromise = new Promise((_, reject) => {
|
|
2613
|
+
abortController.signal.addEventListener("abort", () => {
|
|
2614
|
+
const err = abortController.signal.reason instanceof Error ? abortController.signal.reason : new Error("Build aborted");
|
|
2615
|
+
err.name = "AbortError";
|
|
2616
|
+
reject(err);
|
|
2617
|
+
}, { once: true });
|
|
2618
|
+
if (abortController.signal.aborted) {
|
|
2619
|
+
const err = abortController.signal.reason instanceof Error ? abortController.signal.reason : new Error("Build aborted");
|
|
2620
|
+
err.name = "AbortError";
|
|
2621
|
+
reject(err);
|
|
2622
|
+
}
|
|
2623
|
+
});
|
|
2624
|
+
result = await Promise.race([buildPromise, abortPromise]);
|
|
2625
|
+
} else {
|
|
2626
|
+
result = await buildPromise;
|
|
2627
|
+
}
|
|
2628
|
+
} catch (err) {
|
|
2629
|
+
error = err instanceof Error ? err : new Error(String(err));
|
|
2630
|
+
} finally {
|
|
2631
|
+
if (timeoutId !== undefined) {
|
|
2632
|
+
clearTimeout(timeoutId);
|
|
2633
|
+
}
|
|
2634
|
+
unsubscribe();
|
|
2635
|
+
if (callOptions?.validate) {
|
|
2636
|
+
sandbox.clearValidation();
|
|
2637
|
+
}
|
|
2835
2638
|
}
|
|
2836
|
-
|
|
2837
|
-
}
|
|
2838
|
-
getDirtySandboxes() {
|
|
2839
|
-
return Array.from(this.sandboxes.entries()).filter(([_, sandbox]) => sandbox.isDirty()).map(([id]) => id);
|
|
2840
|
-
}
|
|
2841
|
-
getStats() {
|
|
2639
|
+
const buildOutput = captured.output;
|
|
2842
2640
|
return {
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2641
|
+
result,
|
|
2642
|
+
error,
|
|
2643
|
+
bundle: buildOutput?.bundle ?? null,
|
|
2644
|
+
module: buildOutput?.module ?? null,
|
|
2645
|
+
sandbox
|
|
2847
2646
|
};
|
|
2848
|
-
}
|
|
2849
|
-
getResources() {
|
|
2850
|
-
return this.resources;
|
|
2851
|
-
}
|
|
2852
|
-
}
|
|
2853
|
-
async function createSandboxManager(options = {}) {
|
|
2854
|
-
const manager = new SandboxManager(options);
|
|
2855
|
-
await manager.initialize();
|
|
2856
|
-
return manager;
|
|
2647
|
+
};
|
|
2857
2648
|
}
|
|
2858
2649
|
|
|
2859
2650
|
// src/index.ts
|
|
@@ -2890,22 +2681,20 @@ export {
|
|
|
2890
2681
|
createUninstallCommand,
|
|
2891
2682
|
createTscCommand,
|
|
2892
2683
|
createSharedResources,
|
|
2893
|
-
createSandboxManager,
|
|
2894
2684
|
createSandbox,
|
|
2895
2685
|
createRunCommand,
|
|
2896
2686
|
createListCommand,
|
|
2897
2687
|
createInstallCommand,
|
|
2898
|
-
|
|
2899
|
-
createInMemoryFs,
|
|
2688
|
+
createFilesystem,
|
|
2900
2689
|
createDefaultCommands,
|
|
2690
|
+
createBuilder,
|
|
2901
2691
|
createBuildCommand,
|
|
2902
2692
|
clearSharedModules,
|
|
2903
2693
|
clearDefaultResources,
|
|
2904
2694
|
bundleToUrl,
|
|
2905
2695
|
bundleAndImport,
|
|
2906
2696
|
bundle,
|
|
2907
|
-
SandboxManager,
|
|
2908
2697
|
ModuleLoadError,
|
|
2909
|
-
|
|
2698
|
+
Filesystem,
|
|
2910
2699
|
ExportNotFoundError
|
|
2911
2700
|
};
|