sandlot 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +616 -0
  2. package/dist/bundler.d.ts +148 -0
  3. package/dist/bundler.d.ts.map +1 -0
  4. package/dist/commands.d.ts +179 -0
  5. package/dist/commands.d.ts.map +1 -0
  6. package/dist/fs.d.ts +125 -0
  7. package/dist/fs.d.ts.map +1 -0
  8. package/dist/index.d.ts +16 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +2920 -0
  11. package/dist/internal.d.ts +74 -0
  12. package/dist/internal.d.ts.map +1 -0
  13. package/dist/internal.js +1897 -0
  14. package/dist/loader.d.ts +164 -0
  15. package/dist/loader.d.ts.map +1 -0
  16. package/dist/packages.d.ts +199 -0
  17. package/dist/packages.d.ts.map +1 -0
  18. package/dist/react.d.ts +159 -0
  19. package/dist/react.d.ts.map +1 -0
  20. package/dist/react.js +149 -0
  21. package/dist/sandbox-manager.d.ts +249 -0
  22. package/dist/sandbox-manager.d.ts.map +1 -0
  23. package/dist/sandbox.d.ts +193 -0
  24. package/dist/sandbox.d.ts.map +1 -0
  25. package/dist/shared-modules.d.ts +129 -0
  26. package/dist/shared-modules.d.ts.map +1 -0
  27. package/dist/shared-resources.d.ts +105 -0
  28. package/dist/shared-resources.d.ts.map +1 -0
  29. package/dist/ts-libs.d.ts +98 -0
  30. package/dist/ts-libs.d.ts.map +1 -0
  31. package/dist/typechecker.d.ts +127 -0
  32. package/dist/typechecker.d.ts.map +1 -0
  33. package/package.json +64 -0
  34. package/src/bundler.ts +513 -0
  35. package/src/commands.ts +733 -0
  36. package/src/fs.ts +935 -0
  37. package/src/index.ts +149 -0
  38. package/src/internal.ts +116 -0
  39. package/src/loader.ts +229 -0
  40. package/src/packages.ts +936 -0
  41. package/src/react.tsx +331 -0
  42. package/src/sandbox-manager.ts +490 -0
  43. package/src/sandbox.ts +402 -0
  44. package/src/shared-modules.ts +210 -0
  45. package/src/shared-resources.ts +169 -0
  46. package/src/ts-libs.ts +320 -0
  47. package/src/typechecker.ts +635 -0
package/dist/index.js ADDED
@@ -0,0 +1,2920 @@
1
+ // src/sandbox.ts
2
+ import { Bash } from "just-bash/browser";
3
+
4
+ // src/fs.ts
5
+ var DEFAULT_FILE_MODE = 420;
6
+ var DEFAULT_DIR_MODE = 493;
7
+ var DEFAULT_SYMLINK_MODE = 511;
8
+ var DEFAULT_MAX_SIZE_BYTES = 50 * 1024 * 1024;
9
+
10
+ class IndexedDbFs {
11
+ entries;
12
+ db = null;
13
+ dbName;
14
+ maxSizeBytes;
15
+ dirty = false;
16
+ constructor(entries, db, dbName, maxSizeBytes) {
17
+ this.entries = entries;
18
+ this.db = db;
19
+ this.dbName = dbName;
20
+ this.maxSizeBytes = maxSizeBytes;
21
+ }
22
+ static async create(options = {}) {
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 = {}) {
51
+ const maxSizeBytes = options.maxSizeBytes ?? DEFAULT_MAX_SIZE_BYTES;
52
+ const entries = new Map;
53
+ entries.set("/", {
54
+ type: "directory",
55
+ mode: DEFAULT_DIR_MODE,
56
+ mtime: new Date
57
+ });
58
+ if (options.initialFiles) {
59
+ for (const [path, value] of Object.entries(options.initialFiles)) {
60
+ const normalizedPath = IndexedDbFs.normalizePath(path);
61
+ const init = IndexedDbFs.parseFileInit(value);
62
+ IndexedDbFs.ensureParentDirs(entries, normalizedPath);
63
+ entries.set(normalizedPath, {
64
+ type: "file",
65
+ content: init.content,
66
+ mode: init.mode ?? DEFAULT_FILE_MODE,
67
+ mtime: init.mtime ?? new Date
68
+ });
69
+ }
70
+ }
71
+ return new IndexedDbFs(entries, null, "", maxSizeBytes);
72
+ }
73
+ async save() {
74
+ if (!this.db || !this.dirty) {
75
+ return false;
76
+ }
77
+ const tx = this.db.transaction("entries", "readwrite");
78
+ const store = tx.objectStore("entries");
79
+ await this.promisifyRequest(store.clear());
80
+ for (const [path, entry] of this.entries) {
81
+ store.put({ path, entry: this.serializeEntry(entry) });
82
+ }
83
+ await this.promisifyTransaction(tx);
84
+ this.dirty = false;
85
+ return true;
86
+ }
87
+ async reload() {
88
+ if (!this.db) {
89
+ return;
90
+ }
91
+ this.entries = await IndexedDbFs.loadEntries(this.db);
92
+ this.dirty = false;
93
+ }
94
+ isDirty() {
95
+ return this.dirty;
96
+ }
97
+ getSize() {
98
+ let size = 0;
99
+ for (const [path, entry] of this.entries) {
100
+ size += path.length * 2;
101
+ if (entry.type === "file") {
102
+ if (typeof entry.content === "string") {
103
+ size += entry.content.length * 2;
104
+ } else {
105
+ size += entry.content.byteLength;
106
+ }
107
+ }
108
+ }
109
+ return size;
110
+ }
111
+ close() {
112
+ if (this.db) {
113
+ this.db.close();
114
+ this.db = null;
115
+ }
116
+ }
117
+ async readFile(path, options) {
118
+ const normalizedPath = this.normalizePath(path);
119
+ const entry = this.resolveSymlinks(normalizedPath);
120
+ if (!entry) {
121
+ throw new Error(`ENOENT: no such file or directory, open '${path}'`);
122
+ }
123
+ if (entry.type !== "file") {
124
+ throw new Error(`EISDIR: illegal operation on a directory, read '${path}'`);
125
+ }
126
+ const content = entry.content;
127
+ if (typeof content === "string") {
128
+ return content;
129
+ }
130
+ const encoding = this.getEncoding(options) ?? "utf8";
131
+ return this.decodeBuffer(content, encoding);
132
+ }
133
+ async readFileBuffer(path) {
134
+ const normalizedPath = this.normalizePath(path);
135
+ const entry = this.resolveSymlinks(normalizedPath);
136
+ if (!entry) {
137
+ throw new Error(`ENOENT: no such file or directory, open '${path}'`);
138
+ }
139
+ if (entry.type !== "file") {
140
+ throw new Error(`EISDIR: illegal operation on a directory, read '${path}'`);
141
+ }
142
+ const content = entry.content;
143
+ if (content instanceof Uint8Array) {
144
+ return content;
145
+ }
146
+ return new TextEncoder().encode(content);
147
+ }
148
+ async writeFile(path, content, options) {
149
+ const normalizedPath = this.normalizePath(path);
150
+ this.checkSizeLimit(content);
151
+ this.ensureParentDirs(normalizedPath);
152
+ const existing = this.entries.get(normalizedPath);
153
+ if (existing && existing.type === "directory") {
154
+ throw new Error(`EISDIR: illegal operation on a directory, open '${path}'`);
155
+ }
156
+ this.entries.set(normalizedPath, {
157
+ type: "file",
158
+ content,
159
+ mode: existing?.mode ?? DEFAULT_FILE_MODE,
160
+ mtime: new Date
161
+ });
162
+ this.dirty = true;
163
+ }
164
+ async appendFile(path, content, options) {
165
+ const normalizedPath = this.normalizePath(path);
166
+ let existing = "";
167
+ try {
168
+ existing = await this.readFile(normalizedPath);
169
+ } catch {}
170
+ const newContent = typeof existing === "string" && typeof content === "string" ? existing + content : this.concatBuffers(typeof existing === "string" ? new TextEncoder().encode(existing) : existing, typeof content === "string" ? new TextEncoder().encode(content) : content);
171
+ await this.writeFile(normalizedPath, newContent, options);
172
+ }
173
+ async exists(path) {
174
+ const normalizedPath = this.normalizePath(path);
175
+ return this.entries.has(normalizedPath);
176
+ }
177
+ async stat(path) {
178
+ const normalizedPath = this.normalizePath(path);
179
+ const entry = this.resolveSymlinks(normalizedPath);
180
+ if (!entry) {
181
+ throw new Error(`ENOENT: no such file or directory, stat '${path}'`);
182
+ }
183
+ return this.entryToStat(entry);
184
+ }
185
+ async lstat(path) {
186
+ const normalizedPath = this.normalizePath(path);
187
+ const entry = this.entries.get(normalizedPath);
188
+ if (!entry) {
189
+ throw new Error(`ENOENT: no such file or directory, lstat '${path}'`);
190
+ }
191
+ return this.entryToStat(entry);
192
+ }
193
+ async mkdir(path, options) {
194
+ const normalizedPath = this.normalizePath(path);
195
+ if (this.entries.has(normalizedPath)) {
196
+ if (options?.recursive) {
197
+ return;
198
+ }
199
+ throw new Error(`EEXIST: file already exists, mkdir '${path}'`);
200
+ }
201
+ if (options?.recursive) {
202
+ this.ensureParentDirs(normalizedPath);
203
+ } else {
204
+ const parent = this.getParentPath(normalizedPath);
205
+ if (parent && !this.entries.has(parent)) {
206
+ throw new Error(`ENOENT: no such file or directory, mkdir '${path}'`);
207
+ }
208
+ }
209
+ this.entries.set(normalizedPath, {
210
+ type: "directory",
211
+ mode: DEFAULT_DIR_MODE,
212
+ mtime: new Date
213
+ });
214
+ this.dirty = true;
215
+ }
216
+ async readdir(path) {
217
+ const normalizedPath = this.normalizePath(path);
218
+ const entry = this.resolveSymlinks(normalizedPath);
219
+ if (!entry) {
220
+ throw new Error(`ENOENT: no such file or directory, scandir '${path}'`);
221
+ }
222
+ if (entry.type !== "directory") {
223
+ throw new Error(`ENOTDIR: not a directory, scandir '${path}'`);
224
+ }
225
+ const prefix = normalizedPath === "/" ? "/" : normalizedPath + "/";
226
+ const names = [];
227
+ for (const entryPath of this.entries.keys()) {
228
+ if (entryPath === normalizedPath)
229
+ continue;
230
+ if (!entryPath.startsWith(prefix))
231
+ continue;
232
+ const relative = entryPath.slice(prefix.length);
233
+ if (!relative.includes("/")) {
234
+ names.push(relative);
235
+ }
236
+ }
237
+ return names.sort();
238
+ }
239
+ async readdirWithFileTypes(path) {
240
+ const normalizedPath = this.normalizePath(path);
241
+ const entry = this.resolveSymlinks(normalizedPath);
242
+ if (!entry) {
243
+ throw new Error(`ENOENT: no such file or directory, scandir '${path}'`);
244
+ }
245
+ if (entry.type !== "directory") {
246
+ throw new Error(`ENOTDIR: not a directory, scandir '${path}'`);
247
+ }
248
+ const prefix = normalizedPath === "/" ? "/" : normalizedPath + "/";
249
+ const dirents = [];
250
+ for (const [entryPath, e] of this.entries) {
251
+ if (entryPath === normalizedPath)
252
+ continue;
253
+ if (!entryPath.startsWith(prefix))
254
+ continue;
255
+ const relative = entryPath.slice(prefix.length);
256
+ if (!relative.includes("/")) {
257
+ dirents.push({
258
+ name: relative,
259
+ isFile: e.type === "file",
260
+ isDirectory: e.type === "directory",
261
+ isSymbolicLink: e.type === "symlink"
262
+ });
263
+ }
264
+ }
265
+ return dirents.sort((a, b) => a.name.localeCompare(b.name));
266
+ }
267
+ async rm(path, options) {
268
+ const normalizedPath = this.normalizePath(path);
269
+ const entry = this.entries.get(normalizedPath);
270
+ if (!entry) {
271
+ if (options?.force)
272
+ return;
273
+ throw new Error(`ENOENT: no such file or directory, rm '${path}'`);
274
+ }
275
+ if (entry.type === "directory") {
276
+ const children = await this.readdir(normalizedPath);
277
+ if (children.length > 0 && !options?.recursive) {
278
+ throw new Error(`ENOTEMPTY: directory not empty, rm '${path}'`);
279
+ }
280
+ if (options?.recursive) {
281
+ const prefix = normalizedPath === "/" ? "/" : normalizedPath + "/";
282
+ for (const entryPath of [...this.entries.keys()]) {
283
+ if (entryPath.startsWith(prefix)) {
284
+ this.entries.delete(entryPath);
285
+ }
286
+ }
287
+ }
288
+ }
289
+ this.entries.delete(normalizedPath);
290
+ this.dirty = true;
291
+ }
292
+ async cp(src, dest, options) {
293
+ const srcPath = this.normalizePath(src);
294
+ const destPath = this.normalizePath(dest);
295
+ const entry = this.entries.get(srcPath);
296
+ if (!entry) {
297
+ throw new Error(`ENOENT: no such file or directory, cp '${src}'`);
298
+ }
299
+ if (entry.type === "directory") {
300
+ if (!options?.recursive) {
301
+ throw new Error(`EISDIR: cp called on directory without recursive '${src}'`);
302
+ }
303
+ this.ensureParentDirs(destPath);
304
+ this.entries.set(destPath, { ...entry, mtime: new Date });
305
+ const prefix = srcPath === "/" ? "/" : srcPath + "/";
306
+ for (const [entryPath, e] of this.entries) {
307
+ if (entryPath.startsWith(prefix)) {
308
+ const relative = entryPath.slice(srcPath.length);
309
+ const newPath = destPath + relative;
310
+ this.entries.set(newPath, this.cloneEntry(e));
311
+ }
312
+ }
313
+ } else {
314
+ this.ensureParentDirs(destPath);
315
+ this.entries.set(destPath, this.cloneEntry(entry));
316
+ }
317
+ this.dirty = true;
318
+ }
319
+ async mv(src, dest) {
320
+ const srcPath = this.normalizePath(src);
321
+ const destPath = this.normalizePath(dest);
322
+ const entry = this.entries.get(srcPath);
323
+ if (!entry) {
324
+ throw new Error(`ENOENT: no such file or directory, mv '${src}'`);
325
+ }
326
+ this.ensureParentDirs(destPath);
327
+ if (entry.type === "directory") {
328
+ const prefix = srcPath === "/" ? "/" : srcPath + "/";
329
+ const toMove = [];
330
+ for (const [entryPath, e] of this.entries) {
331
+ if (entryPath === srcPath || entryPath.startsWith(prefix)) {
332
+ const relative = entryPath.slice(srcPath.length);
333
+ toMove.push([destPath + relative, e]);
334
+ this.entries.delete(entryPath);
335
+ }
336
+ }
337
+ for (const [newPath, e] of toMove) {
338
+ this.entries.set(newPath, e);
339
+ }
340
+ } else {
341
+ this.entries.delete(srcPath);
342
+ this.entries.set(destPath, entry);
343
+ }
344
+ this.dirty = true;
345
+ }
346
+ resolvePath(base, path) {
347
+ if (path.startsWith("/")) {
348
+ return this.normalizePath(path);
349
+ }
350
+ const baseParts = base.split("/").filter(Boolean);
351
+ const pathParts = path.split("/").filter(Boolean);
352
+ for (const part of pathParts) {
353
+ if (part === ".") {
354
+ continue;
355
+ } else if (part === "..") {
356
+ baseParts.pop();
357
+ } else {
358
+ baseParts.push(part);
359
+ }
360
+ }
361
+ return "/" + baseParts.join("/");
362
+ }
363
+ getAllPaths() {
364
+ return [...this.entries.keys()].sort();
365
+ }
366
+ async chmod(path, mode) {
367
+ const normalizedPath = this.normalizePath(path);
368
+ const entry = this.entries.get(normalizedPath);
369
+ if (!entry) {
370
+ throw new Error(`ENOENT: no such file or directory, chmod '${path}'`);
371
+ }
372
+ entry.mode = mode;
373
+ entry.mtime = new Date;
374
+ this.dirty = true;
375
+ }
376
+ async symlink(target, linkPath) {
377
+ const normalizedLinkPath = this.normalizePath(linkPath);
378
+ if (this.entries.has(normalizedLinkPath)) {
379
+ throw new Error(`EEXIST: file already exists, symlink '${linkPath}'`);
380
+ }
381
+ this.ensureParentDirs(normalizedLinkPath);
382
+ this.entries.set(normalizedLinkPath, {
383
+ type: "symlink",
384
+ target,
385
+ mode: DEFAULT_SYMLINK_MODE,
386
+ mtime: new Date
387
+ });
388
+ this.dirty = true;
389
+ }
390
+ async link(existingPath, newPath) {
391
+ const srcPath = this.normalizePath(existingPath);
392
+ const destPath = this.normalizePath(newPath);
393
+ const entry = this.entries.get(srcPath);
394
+ if (!entry) {
395
+ throw new Error(`ENOENT: no such file or directory, link '${existingPath}'`);
396
+ }
397
+ if (entry.type !== "file") {
398
+ throw new Error(`EPERM: operation not permitted, link '${existingPath}'`);
399
+ }
400
+ if (this.entries.has(destPath)) {
401
+ throw new Error(`EEXIST: file already exists, link '${newPath}'`);
402
+ }
403
+ this.ensureParentDirs(destPath);
404
+ this.entries.set(destPath, {
405
+ type: "file",
406
+ content: entry.content,
407
+ mode: entry.mode,
408
+ mtime: new Date
409
+ });
410
+ this.dirty = true;
411
+ }
412
+ async readlink(path) {
413
+ const normalizedPath = this.normalizePath(path);
414
+ const entry = this.entries.get(normalizedPath);
415
+ if (!entry) {
416
+ throw new Error(`ENOENT: no such file or directory, readlink '${path}'`);
417
+ }
418
+ if (entry.type !== "symlink") {
419
+ throw new Error(`EINVAL: invalid argument, readlink '${path}'`);
420
+ }
421
+ return entry.target;
422
+ }
423
+ async realpath(path) {
424
+ const normalizedPath = this.normalizePath(path);
425
+ const parts = normalizedPath.split("/").filter(Boolean);
426
+ let resolved = "/";
427
+ for (const part of parts) {
428
+ resolved = resolved === "/" ? `/${part}` : `${resolved}/${part}`;
429
+ const entry = this.entries.get(resolved);
430
+ if (!entry) {
431
+ throw new Error(`ENOENT: no such file or directory, realpath '${path}'`);
432
+ }
433
+ if (entry.type === "symlink") {
434
+ const target = entry.target;
435
+ if (target.startsWith("/")) {
436
+ resolved = this.normalizePath(target);
437
+ } else {
438
+ const parent = this.getParentPath(resolved) ?? "/";
439
+ resolved = this.resolvePath(parent, target);
440
+ }
441
+ const targetEntry = this.entries.get(resolved);
442
+ if (!targetEntry) {
443
+ throw new Error(`ENOENT: no such file or directory, realpath '${path}'`);
444
+ }
445
+ }
446
+ }
447
+ return resolved;
448
+ }
449
+ async utimes(path, atime, mtime) {
450
+ const normalizedPath = this.normalizePath(path);
451
+ const entry = this.entries.get(normalizedPath);
452
+ if (!entry) {
453
+ throw new Error(`ENOENT: no such file or directory, utimes '${path}'`);
454
+ }
455
+ entry.mtime = mtime;
456
+ this.dirty = true;
457
+ }
458
+ normalizePath(path) {
459
+ return IndexedDbFs.normalizePath(path);
460
+ }
461
+ static normalizePath(path) {
462
+ if (!path || path === ".")
463
+ return "/";
464
+ if (!path.startsWith("/")) {
465
+ path = "/" + path;
466
+ }
467
+ const parts = path.split("/").filter(Boolean);
468
+ const normalized = [];
469
+ for (const part of parts) {
470
+ if (part === ".")
471
+ continue;
472
+ if (part === "..") {
473
+ normalized.pop();
474
+ } else {
475
+ normalized.push(part);
476
+ }
477
+ }
478
+ return "/" + normalized.join("/");
479
+ }
480
+ getParentPath(path) {
481
+ if (path === "/")
482
+ return null;
483
+ const lastSlash = path.lastIndexOf("/");
484
+ return lastSlash === 0 ? "/" : path.slice(0, lastSlash);
485
+ }
486
+ ensureParentDirs(path) {
487
+ IndexedDbFs.ensureParentDirs(this.entries, path);
488
+ this.dirty = true;
489
+ }
490
+ static ensureParentDirs(entries, path) {
491
+ const parts = path.split("/").filter(Boolean);
492
+ let current = "";
493
+ for (let i = 0;i < parts.length - 1; i++) {
494
+ current += "/" + parts[i];
495
+ if (!entries.has(current)) {
496
+ entries.set(current, {
497
+ type: "directory",
498
+ mode: DEFAULT_DIR_MODE,
499
+ mtime: new Date
500
+ });
501
+ }
502
+ }
503
+ }
504
+ resolveSymlinks(path, maxDepth = 10) {
505
+ let current = path;
506
+ let depth = 0;
507
+ while (depth < maxDepth) {
508
+ const entry = this.entries.get(current);
509
+ if (!entry)
510
+ return null;
511
+ if (entry.type !== "symlink")
512
+ return entry;
513
+ const target = entry.target;
514
+ current = target.startsWith("/") ? this.normalizePath(target) : this.resolvePath(this.getParentPath(current) ?? "/", target);
515
+ depth++;
516
+ }
517
+ throw new Error(`ELOOP: too many levels of symbolic links, stat '${path}'`);
518
+ }
519
+ entryToStat(entry) {
520
+ return {
521
+ isFile: entry.type === "file",
522
+ isDirectory: entry.type === "directory",
523
+ isSymbolicLink: entry.type === "symlink",
524
+ mode: entry.mode,
525
+ size: entry.type === "file" ? this.getContentSize(entry.content) : 0,
526
+ mtime: entry.mtime
527
+ };
528
+ }
529
+ getContentSize(content) {
530
+ if (typeof content === "string") {
531
+ return new TextEncoder().encode(content).byteLength;
532
+ }
533
+ return content.byteLength;
534
+ }
535
+ cloneEntry(entry) {
536
+ if (entry.type === "file") {
537
+ return {
538
+ type: "file",
539
+ content: entry.content instanceof Uint8Array ? new Uint8Array(entry.content) : entry.content,
540
+ mode: entry.mode,
541
+ mtime: new Date
542
+ };
543
+ }
544
+ return { ...entry, mtime: new Date };
545
+ }
546
+ checkSizeLimit(content) {
547
+ const currentSize = this.getSize();
548
+ const newSize = typeof content === "string" ? content.length * 2 : content.byteLength;
549
+ if (currentSize + newSize > this.maxSizeBytes) {
550
+ throw new Error(`ENOSPC: filesystem size limit exceeded (${this.maxSizeBytes} bytes)`);
551
+ }
552
+ }
553
+ getEncoding(options) {
554
+ if (!options)
555
+ return null;
556
+ if (typeof options === "string")
557
+ return options;
558
+ return options.encoding ?? null;
559
+ }
560
+ decodeBuffer(buffer, encoding) {
561
+ if (encoding === "utf8" || encoding === "utf-8") {
562
+ return new TextDecoder("utf-8").decode(buffer);
563
+ }
564
+ if (encoding === "base64") {
565
+ let binary = "";
566
+ for (let i = 0;i < buffer.byteLength; i++) {
567
+ binary += String.fromCharCode(buffer[i]);
568
+ }
569
+ return btoa(binary);
570
+ }
571
+ if (encoding === "hex") {
572
+ return Array.from(buffer).map((b) => b.toString(16).padStart(2, "0")).join("");
573
+ }
574
+ return new TextDecoder("utf-8").decode(buffer);
575
+ }
576
+ concatBuffers(a, b) {
577
+ const result = new Uint8Array(a.byteLength + b.byteLength);
578
+ result.set(a, 0);
579
+ result.set(b, a.byteLength);
580
+ return result;
581
+ }
582
+ static parseFileInit(value) {
583
+ if (typeof value === "string" || value instanceof Uint8Array) {
584
+ return { content: value };
585
+ }
586
+ return value;
587
+ }
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
+ }
657
+ function createIndexedDbFs(initialFiles) {
658
+ return IndexedDbFs.createInMemory({ initialFiles });
659
+ }
660
+
661
+ // src/bundler.ts
662
+ import * as esbuild from "esbuild-wasm";
663
+
664
+ // src/packages.ts
665
+ var ESM_CDN_BASE = "https://esm.sh";
666
+ var KNOWN_SUBPATHS = {
667
+ react: ["jsx-runtime", "jsx-dev-runtime"],
668
+ "react-dom": ["client", "server"]
669
+ };
670
+
671
+ class InMemoryTypesCache {
672
+ cache = new Map;
673
+ key(name, version) {
674
+ return `${name}@${version}`;
675
+ }
676
+ get(name, version) {
677
+ return this.cache.get(this.key(name, version)) ?? null;
678
+ }
679
+ set(name, version, types) {
680
+ this.cache.set(this.key(name, version), types);
681
+ }
682
+ has(name, version) {
683
+ return this.cache.has(this.key(name, version));
684
+ }
685
+ delete(name, version) {
686
+ return this.cache.delete(this.key(name, version));
687
+ }
688
+ clear() {
689
+ this.cache.clear();
690
+ }
691
+ get size() {
692
+ return this.cache.size;
693
+ }
694
+ }
695
+ var PACKAGE_JSON_PATH = "/package.json";
696
+ function parsePackageSpec(spec) {
697
+ if (spec.startsWith("@")) {
698
+ const slashIndex = spec.indexOf("/");
699
+ if (slashIndex === -1) {
700
+ return { name: spec };
701
+ }
702
+ const afterSlash = spec.slice(slashIndex + 1);
703
+ const atIndex2 = afterSlash.indexOf("@");
704
+ if (atIndex2 === -1) {
705
+ return { name: spec };
706
+ }
707
+ return {
708
+ name: spec.slice(0, slashIndex + 1 + atIndex2),
709
+ version: afterSlash.slice(atIndex2 + 1)
710
+ };
711
+ }
712
+ const atIndex = spec.indexOf("@");
713
+ if (atIndex === -1) {
714
+ return { name: spec };
715
+ }
716
+ return {
717
+ name: spec.slice(0, atIndex),
718
+ version: spec.slice(atIndex + 1)
719
+ };
720
+ }
721
+ function isTypesPackage(name) {
722
+ return name.startsWith("@types/");
723
+ }
724
+ function extractVersionFromUrl(url, packageName) {
725
+ const exactRegex = new RegExp(`${escapeRegExp(packageName)}@([^/]+)`);
726
+ const exactMatch = url.match(exactRegex);
727
+ if (exactMatch?.[1]) {
728
+ return exactMatch[1];
729
+ }
730
+ if (packageName.startsWith("@")) {
731
+ const scopedParts = packageName.split("/");
732
+ if (scopedParts.length === 2 && scopedParts[1]) {
733
+ const partialRegex = new RegExp(`${escapeRegExp(scopedParts[1])}@([^/]+)`);
734
+ const partialMatch = url.match(partialRegex);
735
+ if (partialMatch?.[1]) {
736
+ return partialMatch[1];
737
+ }
738
+ }
739
+ }
740
+ const genericMatch = url.match(/@(\d+\.\d+\.\d+[^/]*)/);
741
+ if (genericMatch?.[1]) {
742
+ return genericMatch[1];
743
+ }
744
+ return null;
745
+ }
746
+ async function fetchVersionFromNpm(name) {
747
+ const registryUrl = `https://registry.npmjs.org/${name}/latest`;
748
+ const response = await fetch(registryUrl);
749
+ if (!response.ok) {
750
+ throw new Error(`Failed to fetch version from npm: ${response.status}`);
751
+ }
752
+ const data = await response.json();
753
+ return data.version;
754
+ }
755
+ function extractVersionFromHeaders(headers, packageName) {
756
+ const esmId = headers.get("x-esm-id");
757
+ if (esmId) {
758
+ const version = extractVersionFromUrl(esmId, packageName);
759
+ if (version)
760
+ return version;
761
+ }
762
+ const typesHeader = headers.get("X-TypeScript-Types");
763
+ if (typesHeader) {
764
+ const versionMatch = typesHeader.match(/@(\d+\.\d+\.\d+[^/]*)/);
765
+ if (versionMatch?.[1]) {
766
+ return versionMatch[1];
767
+ }
768
+ }
769
+ return null;
770
+ }
771
+ async function fetchPackageInfo(name, version, subpath) {
772
+ let url = version ? `${ESM_CDN_BASE}/${name}@${version}` : `${ESM_CDN_BASE}/${name}`;
773
+ if (subpath) {
774
+ url += `/${subpath}`;
775
+ }
776
+ const response = await fetch(url, { method: "HEAD" });
777
+ if (!response.ok) {
778
+ throw new Error(`Package not found: ${name}${version ? `@${version}` : ""}${subpath ? `/${subpath}` : ""}`);
779
+ }
780
+ const resolvedUrl = response.url;
781
+ let resolvedVersion = extractVersionFromUrl(resolvedUrl, name);
782
+ if (!resolvedVersion) {
783
+ resolvedVersion = extractVersionFromHeaders(response.headers, name);
784
+ }
785
+ if (!resolvedVersion && version && version !== "latest") {
786
+ resolvedVersion = version;
787
+ }
788
+ if (!resolvedVersion) {
789
+ try {
790
+ resolvedVersion = await fetchVersionFromNpm(name);
791
+ } catch (err) {
792
+ console.warn(`Could not resolve version for ${name}:`, err);
793
+ resolvedVersion = "latest";
794
+ }
795
+ }
796
+ const typesUrl = response.headers.get("X-TypeScript-Types") ?? undefined;
797
+ return {
798
+ version: resolvedVersion,
799
+ typesUrl: typesUrl ? new URL(typesUrl, resolvedUrl).href : undefined
800
+ };
801
+ }
802
+ function escapeRegExp(string) {
803
+ return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
804
+ }
805
+ async function fetchTypeDefinitions(typesUrl, packageName) {
806
+ const types = new Map;
807
+ try {
808
+ const response = await fetch(typesUrl);
809
+ if (!response.ok) {
810
+ throw new Error(`Failed to fetch types: ${response.status}`);
811
+ }
812
+ const content = await response.text();
813
+ const typePath = `/node_modules/${packageName}/index.d.ts`;
814
+ types.set(typePath, content);
815
+ const refs = parseTypeReferences(content);
816
+ await fetchReferencedTypes(refs, typesUrl, packageName, types);
817
+ } catch (err) {
818
+ console.warn(`Failed to fetch types for ${packageName}:`, err);
819
+ throw err;
820
+ }
821
+ return types;
822
+ }
823
+ function parseTypeReferences(content) {
824
+ const paths = [];
825
+ const types = [];
826
+ const pathRegex = /\/\/\/\s*<reference\s+path="([^"]+)"\s*\/>/g;
827
+ let match;
828
+ while ((match = pathRegex.exec(content)) !== null) {
829
+ if (match[1])
830
+ paths.push(match[1]);
831
+ }
832
+ const typesRegex = /\/\/\/\s*<reference\s+types="([^"]+)"\s*\/>/g;
833
+ while ((match = typesRegex.exec(content)) !== null) {
834
+ if (match[1])
835
+ types.push(match[1]);
836
+ }
837
+ return { paths, types };
838
+ }
839
+ async function fetchReferencedTypes(refs, baseUrl, packageName, collected, visited = new Set) {
840
+ for (const pathRef of refs.paths) {
841
+ const refUrl = new URL(pathRef, baseUrl).href;
842
+ if (visited.has(refUrl))
843
+ continue;
844
+ visited.add(refUrl);
845
+ try {
846
+ const response = await fetch(refUrl);
847
+ if (!response.ok)
848
+ continue;
849
+ const content = await response.text();
850
+ const fileName = pathRef.split("/").pop() ?? "types.d.ts";
851
+ const typePath = `/node_modules/${packageName}/${fileName}`;
852
+ collected.set(typePath, content);
853
+ const nestedRefs = parseTypeReferences(content);
854
+ await fetchReferencedTypes(nestedRefs, refUrl, packageName, collected, visited);
855
+ } catch {}
856
+ }
857
+ }
858
+ async function fetchSubpathTypes(packageName, subpath, version) {
859
+ const types = new Map;
860
+ try {
861
+ const info = await fetchPackageInfo(packageName, version, subpath);
862
+ if (!info.typesUrl) {
863
+ return types;
864
+ }
865
+ const response = await fetch(info.typesUrl);
866
+ if (!response.ok) {
867
+ return types;
868
+ }
869
+ const content = await response.text();
870
+ const typePath = `/node_modules/${packageName}/${subpath}.d.ts`;
871
+ types.set(typePath, content);
872
+ const indexTypePath = `/node_modules/${packageName}/${subpath}/index.d.ts`;
873
+ types.set(indexTypePath, content);
874
+ const refs = parseTypeReferences(content);
875
+ await fetchReferencedTypes(refs, info.typesUrl, packageName, types);
876
+ } catch (err) {
877
+ console.warn(`Failed to fetch types for ${packageName}/${subpath}:`, err);
878
+ }
879
+ return types;
880
+ }
881
+ async function fetchTypesPackageContent(name, version) {
882
+ const url = version ? `${ESM_CDN_BASE}/${name}@${version}` : `${ESM_CDN_BASE}/${name}`;
883
+ const headResponse = await fetch(url, { method: "HEAD" });
884
+ if (!headResponse.ok) {
885
+ throw new Error(`Package not found: ${name}${version ? `@${version}` : ""}`);
886
+ }
887
+ const resolvedVersion = extractVersionFromUrl(headResponse.url, name) ?? version ?? "latest";
888
+ const indexUrl = `${ESM_CDN_BASE}/${name}@${resolvedVersion}/index.d.ts`;
889
+ const response = await fetch(indexUrl);
890
+ if (!response.ok) {
891
+ throw new Error(`Failed to fetch types from ${name}: ${response.status}`);
892
+ }
893
+ const content = await response.text();
894
+ const types = new Map;
895
+ const typePath = `/node_modules/${name}/index.d.ts`;
896
+ types.set(typePath, content);
897
+ const refs = parseTypeReferences(content);
898
+ for (const pathRef of refs.paths) {
899
+ try {
900
+ const refUrl = new URL(pathRef, indexUrl).href;
901
+ const refResponse = await fetch(refUrl);
902
+ if (refResponse.ok) {
903
+ const refContent = await refResponse.text();
904
+ const fileName = pathRef.startsWith("./") ? pathRef.slice(2) : pathRef;
905
+ const refTypePath = `/node_modules/${name}/${fileName}`;
906
+ types.set(refTypePath, refContent);
907
+ }
908
+ } catch {}
909
+ }
910
+ return { version: resolvedVersion, types };
911
+ }
912
+ async function getPackageManifest(fs) {
913
+ try {
914
+ if (await fs.exists(PACKAGE_JSON_PATH)) {
915
+ const content = await fs.readFile(PACKAGE_JSON_PATH);
916
+ const parsed = JSON.parse(content);
917
+ return {
918
+ dependencies: parsed.dependencies ?? {}
919
+ };
920
+ }
921
+ } catch {}
922
+ return { dependencies: {} };
923
+ }
924
+ async function savePackageManifest(fs, manifest) {
925
+ let existing = {};
926
+ try {
927
+ if (await fs.exists(PACKAGE_JSON_PATH)) {
928
+ const content = await fs.readFile(PACKAGE_JSON_PATH);
929
+ existing = JSON.parse(content);
930
+ }
931
+ } catch {}
932
+ const updated = {
933
+ ...existing,
934
+ dependencies: manifest.dependencies
935
+ };
936
+ await fs.writeFile(PACKAGE_JSON_PATH, JSON.stringify(updated, null, 2));
937
+ }
938
+ async function installPackage(fs, packageSpec, options) {
939
+ const { name, version } = parsePackageSpec(packageSpec);
940
+ const { cache } = options ?? {};
941
+ if (isTypesPackage(name)) {
942
+ return installTypesPackage(fs, name, version, cache);
943
+ }
944
+ const info = await fetchPackageInfo(name, version);
945
+ const packageDir = `/node_modules/${name}`;
946
+ await ensureDir(fs, packageDir);
947
+ const packageJsonPath = `${packageDir}/package.json`;
948
+ const packageJson = {
949
+ name,
950
+ version: info.version,
951
+ types: "./index.d.ts",
952
+ main: "./index.js"
953
+ };
954
+ await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
955
+ let typeFiles = null;
956
+ let fromCache = false;
957
+ if (cache) {
958
+ typeFiles = cache.get(name, info.version);
959
+ if (typeFiles) {
960
+ fromCache = true;
961
+ }
962
+ }
963
+ let typesError;
964
+ if (!typeFiles) {
965
+ typeFiles = new Map;
966
+ if (info.typesUrl) {
967
+ try {
968
+ const mainTypes = await fetchTypeDefinitions(info.typesUrl, name);
969
+ for (const [path, content] of mainTypes) {
970
+ typeFiles.set(path, content);
971
+ }
972
+ } catch (err) {
973
+ typesError = err instanceof Error ? err.message : String(err);
974
+ }
975
+ } else {
976
+ typesError = "No TypeScript types available from esm.sh";
977
+ }
978
+ const knownSubpaths = KNOWN_SUBPATHS[name];
979
+ if (knownSubpaths && knownSubpaths.length > 0) {
980
+ const subpathResults = await Promise.allSettled(knownSubpaths.map((subpath) => fetchSubpathTypes(name, subpath, info.version)));
981
+ for (const result of subpathResults) {
982
+ if (result.status === "fulfilled") {
983
+ for (const [path, content] of result.value) {
984
+ typeFiles.set(path, content);
985
+ }
986
+ }
987
+ }
988
+ }
989
+ if (cache && typeFiles.size > 0) {
990
+ cache.set(name, info.version, typeFiles);
991
+ }
992
+ }
993
+ for (const [path, content] of typeFiles) {
994
+ const dir = path.substring(0, path.lastIndexOf("/"));
995
+ await ensureDir(fs, dir);
996
+ await fs.writeFile(path, content);
997
+ }
998
+ const manifest = await getPackageManifest(fs);
999
+ manifest.dependencies[name] = info.version;
1000
+ await savePackageManifest(fs, manifest);
1001
+ return {
1002
+ name,
1003
+ version: info.version,
1004
+ typesInstalled: typeFiles.size > 0,
1005
+ typeFilesCount: typeFiles.size,
1006
+ typesError,
1007
+ fromCache
1008
+ };
1009
+ }
1010
+ async function installTypesPackage(fs, name, version, cache) {
1011
+ const url = version ? `${ESM_CDN_BASE}/${name}@${version}` : `${ESM_CDN_BASE}/${name}`;
1012
+ const headResponse = await fetch(url, { method: "HEAD" });
1013
+ if (!headResponse.ok) {
1014
+ throw new Error(`Package not found: ${name}${version ? `@${version}` : ""}`);
1015
+ }
1016
+ const resolvedVersion = extractVersionFromUrl(headResponse.url, name) ?? version ?? "latest";
1017
+ let types = null;
1018
+ let fromCache = false;
1019
+ if (cache) {
1020
+ types = cache.get(name, resolvedVersion);
1021
+ if (types) {
1022
+ fromCache = true;
1023
+ }
1024
+ }
1025
+ if (!types) {
1026
+ const result = await fetchTypesPackageContent(name, version);
1027
+ types = result.types;
1028
+ if (cache && types.size > 0) {
1029
+ cache.set(name, resolvedVersion, types);
1030
+ }
1031
+ }
1032
+ const packageDir = `/node_modules/${name}`;
1033
+ await ensureDir(fs, packageDir);
1034
+ const packageJsonPath = `${packageDir}/package.json`;
1035
+ const packageJson = {
1036
+ name,
1037
+ version: resolvedVersion,
1038
+ types: "./index.d.ts"
1039
+ };
1040
+ await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
1041
+ for (const [path, content] of types) {
1042
+ const dir = path.substring(0, path.lastIndexOf("/"));
1043
+ await ensureDir(fs, dir);
1044
+ await fs.writeFile(path, content);
1045
+ }
1046
+ const manifest = await getPackageManifest(fs);
1047
+ manifest.dependencies[name] = resolvedVersion;
1048
+ await savePackageManifest(fs, manifest);
1049
+ return {
1050
+ name,
1051
+ version: resolvedVersion,
1052
+ typesInstalled: types.size > 0,
1053
+ typeFilesCount: types.size,
1054
+ fromCache
1055
+ };
1056
+ }
1057
+ async function ensureDir(fs, path) {
1058
+ if (path === "/" || path === "")
1059
+ return;
1060
+ if (await fs.exists(path)) {
1061
+ const stat = await fs.stat(path);
1062
+ if (stat.isDirectory)
1063
+ return;
1064
+ }
1065
+ const parent = path.substring(0, path.lastIndexOf("/")) || "/";
1066
+ await ensureDir(fs, parent);
1067
+ await fs.mkdir(path);
1068
+ }
1069
+ async function uninstallPackage(fs, packageName) {
1070
+ const manifest = await getPackageManifest(fs);
1071
+ if (!(packageName in manifest.dependencies)) {
1072
+ return false;
1073
+ }
1074
+ delete manifest.dependencies[packageName];
1075
+ await savePackageManifest(fs, manifest);
1076
+ const typesPath = `/node_modules/${packageName}`;
1077
+ if (await fs.exists(typesPath)) {
1078
+ await removePath(fs, typesPath);
1079
+ }
1080
+ return true;
1081
+ }
1082
+ async function removePath(fs, path) {
1083
+ if (!await fs.exists(path))
1084
+ return;
1085
+ await fs.rm(path, { recursive: true, force: true });
1086
+ }
1087
+ function resolveToEsmUrl(importPath, installedPackages) {
1088
+ const { packageName, subpath } = parseImportPath(importPath);
1089
+ const version = installedPackages[packageName];
1090
+ if (!version) {
1091
+ return null;
1092
+ }
1093
+ const baseUrl = `${ESM_CDN_BASE}/${packageName}@${version}`;
1094
+ return subpath ? `${baseUrl}/${subpath}` : baseUrl;
1095
+ }
1096
+ function parseImportPath(importPath) {
1097
+ if (importPath.startsWith("@")) {
1098
+ const parts = importPath.split("/");
1099
+ if (parts.length >= 2) {
1100
+ const packageName = `${parts[0]}/${parts[1]}`;
1101
+ const subpath = parts.slice(2).join("/") || undefined;
1102
+ return { packageName, subpath };
1103
+ }
1104
+ return { packageName: importPath };
1105
+ }
1106
+ const slashIndex = importPath.indexOf("/");
1107
+ if (slashIndex === -1) {
1108
+ return { packageName: importPath };
1109
+ }
1110
+ return {
1111
+ packageName: importPath.slice(0, slashIndex),
1112
+ subpath: importPath.slice(slashIndex + 1)
1113
+ };
1114
+ }
1115
+ async function listPackages(fs) {
1116
+ const manifest = await getPackageManifest(fs);
1117
+ return Object.entries(manifest.dependencies).map(([name, version]) => ({
1118
+ name,
1119
+ version
1120
+ }));
1121
+ }
1122
+
1123
+ // src/shared-modules.ts
1124
+ var GLOBAL_KEY = "__sandlot_shared_modules__";
1125
+
1126
+ class SharedModuleRegistry {
1127
+ modules = new Map;
1128
+ constructor() {
1129
+ globalThis[GLOBAL_KEY] = this;
1130
+ }
1131
+ register(moduleId, module) {
1132
+ this.modules.set(moduleId, module);
1133
+ return this;
1134
+ }
1135
+ registerAll(modules) {
1136
+ for (const [id, mod] of Object.entries(modules)) {
1137
+ this.modules.set(id, mod);
1138
+ }
1139
+ return this;
1140
+ }
1141
+ unregister(moduleId) {
1142
+ return this.modules.delete(moduleId);
1143
+ }
1144
+ get(moduleId) {
1145
+ const mod = this.modules.get(moduleId);
1146
+ if (mod === undefined && !this.modules.has(moduleId)) {
1147
+ const available = this.list();
1148
+ throw new Error(`Shared module "${moduleId}" not registered. ` + `Available: ${available.length > 0 ? available.join(", ") : "(none)"}. ` + `Call registerSharedModules({ '${moduleId}': ... }) in your host application.`);
1149
+ }
1150
+ return mod;
1151
+ }
1152
+ has(moduleId) {
1153
+ return this.modules.has(moduleId);
1154
+ }
1155
+ list() {
1156
+ return [...this.modules.keys()];
1157
+ }
1158
+ clear() {
1159
+ this.modules.clear();
1160
+ }
1161
+ get size() {
1162
+ return this.modules.size;
1163
+ }
1164
+ }
1165
+ var defaultRegistry = null;
1166
+ function getSharedModuleRegistry() {
1167
+ if (!defaultRegistry) {
1168
+ defaultRegistry = new SharedModuleRegistry;
1169
+ }
1170
+ return defaultRegistry;
1171
+ }
1172
+ function hasSharedModuleRegistry() {
1173
+ return GLOBAL_KEY in globalThis;
1174
+ }
1175
+ function registerSharedModules(modules) {
1176
+ getSharedModuleRegistry().registerAll(modules);
1177
+ }
1178
+ function unregisterSharedModule(moduleId) {
1179
+ return getSharedModuleRegistry().unregister(moduleId);
1180
+ }
1181
+ function clearSharedModules() {
1182
+ getSharedModuleRegistry().clear();
1183
+ }
1184
+ function getSharedModuleRuntimeCode(moduleId) {
1185
+ return `
1186
+ (function() {
1187
+ var registry = globalThis["${GLOBAL_KEY}"];
1188
+ if (!registry) {
1189
+ throw new Error(
1190
+ 'Sandlot SharedModuleRegistry not found. ' +
1191
+ 'Call registerSharedModules() in your host application before loading dynamic modules.'
1192
+ );
1193
+ }
1194
+ return registry.get(${JSON.stringify(moduleId)});
1195
+ })()
1196
+ `.trim();
1197
+ }
1198
+
1199
+ // src/bundler.ts
1200
+ var initialized = false;
1201
+ var initPromise = null;
1202
+ function getWasmUrl() {
1203
+ const version = "0.27.2";
1204
+ return `https://unpkg.com/esbuild-wasm@${version}/esbuild.wasm`;
1205
+ }
1206
+ async function initBundler() {
1207
+ if (initialized)
1208
+ return;
1209
+ if (initPromise) {
1210
+ await initPromise;
1211
+ return;
1212
+ }
1213
+ initPromise = esbuild.initialize({
1214
+ wasmURL: getWasmUrl()
1215
+ });
1216
+ await initPromise;
1217
+ initialized = true;
1218
+ }
1219
+ function isBareImport(path) {
1220
+ return !path.startsWith(".") && !path.startsWith("/");
1221
+ }
1222
+ function getLoader(path) {
1223
+ const ext = path.split(".").pop()?.toLowerCase();
1224
+ switch (ext) {
1225
+ case "ts":
1226
+ return "ts";
1227
+ case "tsx":
1228
+ return "tsx";
1229
+ case "jsx":
1230
+ return "jsx";
1231
+ case "js":
1232
+ case "mjs":
1233
+ return "js";
1234
+ case "json":
1235
+ return "json";
1236
+ case "css":
1237
+ return "css";
1238
+ case "txt":
1239
+ return "text";
1240
+ default:
1241
+ return "js";
1242
+ }
1243
+ }
1244
+ function matchesSharedModule(importPath, sharedModuleIds) {
1245
+ if (sharedModuleIds.has(importPath)) {
1246
+ return importPath;
1247
+ }
1248
+ for (const moduleId of sharedModuleIds) {
1249
+ if (importPath === moduleId || importPath.startsWith(moduleId + "/")) {
1250
+ return importPath;
1251
+ }
1252
+ }
1253
+ return null;
1254
+ }
1255
+ function createVfsPlugin(options) {
1256
+ const {
1257
+ fs,
1258
+ entryPoint,
1259
+ npmImports,
1260
+ installedPackages,
1261
+ includedFiles,
1262
+ sharedModuleIds
1263
+ } = options;
1264
+ return {
1265
+ name: "virtual-fs",
1266
+ setup(build2) {
1267
+ build2.onResolve({ filter: /.*/ }, async (args) => {
1268
+ if (args.kind === "entry-point") {
1269
+ return { path: entryPoint, namespace: "vfs" };
1270
+ }
1271
+ if (isBareImport(args.path)) {
1272
+ const sharedMatch = matchesSharedModule(args.path, sharedModuleIds);
1273
+ if (sharedMatch) {
1274
+ return {
1275
+ path: sharedMatch,
1276
+ namespace: "sandlot-shared"
1277
+ };
1278
+ }
1279
+ switch (npmImports) {
1280
+ case "cdn": {
1281
+ const esmUrl = resolveToEsmUrl(args.path, installedPackages);
1282
+ if (esmUrl) {
1283
+ return { path: esmUrl, external: true };
1284
+ }
1285
+ return { path: args.path, external: true };
1286
+ }
1287
+ case "external":
1288
+ return { path: args.path, external: true };
1289
+ case "bundle": {
1290
+ const resolved2 = fs.resolvePath(args.resolveDir, `node_modules/${args.path}`);
1291
+ const exists = await fs.exists(resolved2);
1292
+ if (exists) {
1293
+ return { path: resolved2, namespace: "vfs" };
1294
+ }
1295
+ return { path: args.path, external: true };
1296
+ }
1297
+ }
1298
+ }
1299
+ const resolved = fs.resolvePath(args.resolveDir, args.path);
1300
+ const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".json"];
1301
+ const hasExtension = extensions.some((ext) => resolved.endsWith(ext));
1302
+ if (hasExtension) {
1303
+ const exists = await fs.exists(resolved);
1304
+ if (exists) {
1305
+ return { path: resolved, namespace: "vfs" };
1306
+ }
1307
+ return { errors: [{ text: `File not found: ${resolved}` }] };
1308
+ }
1309
+ for (const ext of extensions) {
1310
+ const withExt = resolved + ext;
1311
+ if (await fs.exists(withExt)) {
1312
+ return { path: withExt, namespace: "vfs" };
1313
+ }
1314
+ }
1315
+ for (const ext of extensions) {
1316
+ const indexPath = `${resolved}/index${ext}`;
1317
+ if (await fs.exists(indexPath)) {
1318
+ return { path: indexPath, namespace: "vfs" };
1319
+ }
1320
+ }
1321
+ return { errors: [{ text: `Cannot resolve: ${args.path} from ${args.resolveDir}` }] };
1322
+ });
1323
+ build2.onLoad({ filter: /.*/, namespace: "sandlot-shared" }, (args) => {
1324
+ const contents = `export default ${getSharedModuleRuntimeCode(args.path)};
1325
+ export * from ${JSON.stringify(args.path)};
1326
+ // Re-export all named exports by importing from registry
1327
+ const __mod__ = ${getSharedModuleRuntimeCode(args.path)};
1328
+ for (const __k__ in __mod__) {
1329
+ if (__k__ !== 'default') Object.defineProperty(exports, __k__, {
1330
+ enumerable: true,
1331
+ get: function() { return __mod__[__k__]; }
1332
+ });
1333
+ }`;
1334
+ const esmContents = `
1335
+ const __sandlot_mod__ = ${getSharedModuleRuntimeCode(args.path)};
1336
+ export default __sandlot_mod__.default ?? __sandlot_mod__;
1337
+ ${generateNamedExports(args.path)}
1338
+ `;
1339
+ return {
1340
+ contents: esmContents.trim(),
1341
+ loader: "js"
1342
+ };
1343
+ });
1344
+ build2.onLoad({ filter: /.*/, namespace: "vfs" }, async (args) => {
1345
+ try {
1346
+ const contents = await fs.readFile(args.path);
1347
+ includedFiles.add(args.path);
1348
+ return {
1349
+ contents,
1350
+ loader: getLoader(args.path),
1351
+ resolveDir: args.path.substring(0, args.path.lastIndexOf("/"))
1352
+ };
1353
+ } catch (err) {
1354
+ return {
1355
+ errors: [{ text: `Failed to read ${args.path}: ${err}` }]
1356
+ };
1357
+ }
1358
+ });
1359
+ }
1360
+ };
1361
+ }
1362
+ function generateNamedExports(moduleId) {
1363
+ const knownExports = {
1364
+ react: [
1365
+ "useState",
1366
+ "useEffect",
1367
+ "useContext",
1368
+ "useReducer",
1369
+ "useCallback",
1370
+ "useMemo",
1371
+ "useRef",
1372
+ "useImperativeHandle",
1373
+ "useLayoutEffect",
1374
+ "useDebugValue",
1375
+ "useDeferredValue",
1376
+ "useTransition",
1377
+ "useId",
1378
+ "useSyncExternalStore",
1379
+ "useInsertionEffect",
1380
+ "useOptimistic",
1381
+ "useActionState",
1382
+ "createElement",
1383
+ "cloneElement",
1384
+ "createContext",
1385
+ "forwardRef",
1386
+ "lazy",
1387
+ "memo",
1388
+ "startTransition",
1389
+ "Children",
1390
+ "Component",
1391
+ "PureComponent",
1392
+ "Fragment",
1393
+ "Profiler",
1394
+ "StrictMode",
1395
+ "Suspense",
1396
+ "version",
1397
+ "isValidElement"
1398
+ ],
1399
+ "react-dom": ["createPortal", "flushSync", "version"],
1400
+ "react-dom/client": ["createRoot", "hydrateRoot"],
1401
+ "react-dom/server": ["renderToString", "renderToStaticMarkup", "renderToPipeableStream"]
1402
+ };
1403
+ const exports = knownExports[moduleId];
1404
+ if (!exports) {
1405
+ return `
1406
+ // Dynamic re-export for unknown module
1407
+ export const __moduleProxy__ = __sandlot_mod__;
1408
+ `;
1409
+ }
1410
+ return exports.map((name) => `export const ${name} = __sandlot_mod__.${name};`).join(`
1411
+ `);
1412
+ }
1413
+ async function bundle(options) {
1414
+ await initBundler();
1415
+ const {
1416
+ fs,
1417
+ entryPoint,
1418
+ external = [],
1419
+ npmImports = "cdn",
1420
+ sharedModules = [],
1421
+ format = "esm",
1422
+ minify = false,
1423
+ sourcemap = false,
1424
+ globalName,
1425
+ target = ["es2020"]
1426
+ } = options;
1427
+ const normalizedEntry = entryPoint.startsWith("/") ? entryPoint : `/${entryPoint}`;
1428
+ if (!await fs.exists(normalizedEntry)) {
1429
+ throw new Error(`Entry point not found: ${normalizedEntry}`);
1430
+ }
1431
+ const manifest = await getPackageManifest(fs);
1432
+ const installedPackages = manifest.dependencies;
1433
+ const sharedModuleIds = new Set(sharedModules);
1434
+ const includedFiles = new Set;
1435
+ const plugin = createVfsPlugin({
1436
+ fs,
1437
+ entryPoint: normalizedEntry,
1438
+ npmImports,
1439
+ installedPackages,
1440
+ includedFiles,
1441
+ sharedModuleIds
1442
+ });
1443
+ const result = await esbuild.build({
1444
+ entryPoints: [normalizedEntry],
1445
+ bundle: true,
1446
+ write: false,
1447
+ format,
1448
+ minify,
1449
+ sourcemap: sourcemap ? "inline" : false,
1450
+ globalName,
1451
+ target,
1452
+ external,
1453
+ plugins: [plugin]
1454
+ });
1455
+ const code = result.outputFiles?.[0]?.text ?? "";
1456
+ return {
1457
+ code,
1458
+ warnings: result.warnings,
1459
+ includedFiles: Array.from(includedFiles)
1460
+ };
1461
+ }
1462
+ async function bundleToUrl(options) {
1463
+ const result = await bundle(options);
1464
+ const blob = new Blob([result.code], { type: "application/javascript" });
1465
+ return URL.createObjectURL(blob);
1466
+ }
1467
+ async function bundleAndImport(options) {
1468
+ const url = await bundleToUrl(options);
1469
+ try {
1470
+ return await import(url);
1471
+ } finally {
1472
+ URL.revokeObjectURL(url);
1473
+ }
1474
+ }
1475
+
1476
+ // src/commands.ts
1477
+ import { defineCommand } from "just-bash/browser";
1478
+
1479
+ // src/typechecker.ts
1480
+ import ts from "typescript";
1481
+ function categoryToString(category) {
1482
+ switch (category) {
1483
+ case ts.DiagnosticCategory.Error:
1484
+ return "error";
1485
+ case ts.DiagnosticCategory.Warning:
1486
+ return "warning";
1487
+ case ts.DiagnosticCategory.Suggestion:
1488
+ return "suggestion";
1489
+ case ts.DiagnosticCategory.Message:
1490
+ return "message";
1491
+ default:
1492
+ return "error";
1493
+ }
1494
+ }
1495
+ function convertDiagnostic(diag) {
1496
+ let file = null;
1497
+ let line = null;
1498
+ let column = null;
1499
+ if (diag.file && diag.start !== undefined) {
1500
+ file = diag.file.fileName;
1501
+ const pos = diag.file.getLineAndCharacterOfPosition(diag.start);
1502
+ line = pos.line + 1;
1503
+ column = pos.character + 1;
1504
+ }
1505
+ return {
1506
+ file,
1507
+ line,
1508
+ column,
1509
+ code: diag.code,
1510
+ category: categoryToString(diag.category),
1511
+ message: ts.flattenDiagnosticMessageText(diag.messageText, `
1512
+ `)
1513
+ };
1514
+ }
1515
+ function normalizePath(path) {
1516
+ if (!path.startsWith("/")) {
1517
+ return "/" + path;
1518
+ }
1519
+ return path;
1520
+ }
1521
+ async function preloadFiles(fs) {
1522
+ const cache = new Map;
1523
+ const allPaths = fs.getAllPaths();
1524
+ for (const path of allPaths) {
1525
+ if (path.endsWith(".ts") || path.endsWith(".tsx") || path.endsWith(".js") || path.endsWith(".jsx") || path.endsWith(".json") || path.endsWith(".d.ts")) {
1526
+ try {
1527
+ const stat = await fs.stat(path);
1528
+ if (stat.isFile) {
1529
+ const content = await fs.readFile(path);
1530
+ cache.set(path, content);
1531
+ }
1532
+ } catch {}
1533
+ }
1534
+ }
1535
+ return cache;
1536
+ }
1537
+ function parseTsConfig(configText, _configPath) {
1538
+ try {
1539
+ const config = JSON.parse(configText);
1540
+ const compilerOptions = config.compilerOptions || {};
1541
+ const options = {
1542
+ ...getDefaultCompilerOptions()
1543
+ };
1544
+ if (compilerOptions.target) {
1545
+ const targetMap = {
1546
+ es5: ts.ScriptTarget.ES5,
1547
+ es6: ts.ScriptTarget.ES2015,
1548
+ es2015: ts.ScriptTarget.ES2015,
1549
+ es2016: ts.ScriptTarget.ES2016,
1550
+ es2017: ts.ScriptTarget.ES2017,
1551
+ es2018: ts.ScriptTarget.ES2018,
1552
+ es2019: ts.ScriptTarget.ES2019,
1553
+ es2020: ts.ScriptTarget.ES2020,
1554
+ es2021: ts.ScriptTarget.ES2021,
1555
+ es2022: ts.ScriptTarget.ES2022,
1556
+ esnext: ts.ScriptTarget.ESNext
1557
+ };
1558
+ options.target = targetMap[compilerOptions.target.toLowerCase()] ?? ts.ScriptTarget.ES2020;
1559
+ }
1560
+ if (compilerOptions.module) {
1561
+ const moduleMap = {
1562
+ commonjs: ts.ModuleKind.CommonJS,
1563
+ amd: ts.ModuleKind.AMD,
1564
+ umd: ts.ModuleKind.UMD,
1565
+ system: ts.ModuleKind.System,
1566
+ es6: ts.ModuleKind.ES2015,
1567
+ es2015: ts.ModuleKind.ES2015,
1568
+ es2020: ts.ModuleKind.ES2020,
1569
+ es2022: ts.ModuleKind.ES2022,
1570
+ esnext: ts.ModuleKind.ESNext,
1571
+ node16: ts.ModuleKind.Node16,
1572
+ nodenext: ts.ModuleKind.NodeNext
1573
+ };
1574
+ options.module = moduleMap[compilerOptions.module.toLowerCase()] ?? ts.ModuleKind.ESNext;
1575
+ }
1576
+ if (compilerOptions.moduleResolution) {
1577
+ const resolutionMap = {
1578
+ classic: ts.ModuleResolutionKind.Classic,
1579
+ node: ts.ModuleResolutionKind.NodeJs,
1580
+ node10: ts.ModuleResolutionKind.NodeJs,
1581
+ node16: ts.ModuleResolutionKind.Node16,
1582
+ nodenext: ts.ModuleResolutionKind.NodeNext,
1583
+ bundler: ts.ModuleResolutionKind.Bundler
1584
+ };
1585
+ options.moduleResolution = resolutionMap[compilerOptions.moduleResolution.toLowerCase()] ?? ts.ModuleResolutionKind.Bundler;
1586
+ }
1587
+ if (compilerOptions.jsx) {
1588
+ const jsxMap = {
1589
+ preserve: ts.JsxEmit.Preserve,
1590
+ react: ts.JsxEmit.React,
1591
+ "react-jsx": ts.JsxEmit.ReactJSX,
1592
+ "react-jsxdev": ts.JsxEmit.ReactJSXDev,
1593
+ "react-native": ts.JsxEmit.ReactNative
1594
+ };
1595
+ options.jsx = jsxMap[compilerOptions.jsx.toLowerCase()] ?? ts.JsxEmit.ReactJSX;
1596
+ }
1597
+ if (compilerOptions.strict !== undefined)
1598
+ options.strict = compilerOptions.strict;
1599
+ if (compilerOptions.esModuleInterop !== undefined)
1600
+ options.esModuleInterop = compilerOptions.esModuleInterop;
1601
+ if (compilerOptions.skipLibCheck !== undefined)
1602
+ options.skipLibCheck = compilerOptions.skipLibCheck;
1603
+ if (compilerOptions.allowJs !== undefined)
1604
+ options.allowJs = compilerOptions.allowJs;
1605
+ if (compilerOptions.resolveJsonModule !== undefined)
1606
+ options.resolveJsonModule = compilerOptions.resolveJsonModule;
1607
+ if (compilerOptions.noImplicitAny !== undefined)
1608
+ options.noImplicitAny = compilerOptions.noImplicitAny;
1609
+ if (compilerOptions.strictNullChecks !== undefined)
1610
+ options.strictNullChecks = compilerOptions.strictNullChecks;
1611
+ if (Array.isArray(compilerOptions.lib)) {
1612
+ options.lib = compilerOptions.lib.map((lib) => lib.toLowerCase().startsWith("lib.") ? lib : `lib.${lib.toLowerCase()}.d.ts`);
1613
+ }
1614
+ options.noEmit = true;
1615
+ return options;
1616
+ } catch (err) {
1617
+ console.warn("Error parsing tsconfig.json:", err);
1618
+ return getDefaultCompilerOptions();
1619
+ }
1620
+ }
1621
+ function getDefaultCompilerOptions() {
1622
+ return {
1623
+ target: ts.ScriptTarget.ES2020,
1624
+ module: ts.ModuleKind.ESNext,
1625
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
1626
+ esModuleInterop: true,
1627
+ strict: true,
1628
+ skipLibCheck: true,
1629
+ noEmit: true,
1630
+ jsx: ts.JsxEmit.ReactJSX,
1631
+ allowJs: true,
1632
+ resolveJsonModule: true,
1633
+ noLib: false,
1634
+ lib: [
1635
+ "lib.es2020.d.ts",
1636
+ "lib.dom.d.ts",
1637
+ "lib.dom.iterable.d.ts"
1638
+ ]
1639
+ };
1640
+ }
1641
+ var LIB_PATH_PREFIX = "/node_modules/typescript/lib/";
1642
+ function createVfsCompilerHost(fileCache, libFiles, _options) {
1643
+ function getLibContent(fileName) {
1644
+ const libMatch = fileName.match(/lib\.([^/]+)\.d\.ts$/);
1645
+ if (libMatch && libMatch[1]) {
1646
+ return libFiles.get(libMatch[1]);
1647
+ }
1648
+ return;
1649
+ }
1650
+ return {
1651
+ getSourceFile(fileName, languageVersion, onError) {
1652
+ const normalizedPath = normalizePath(fileName);
1653
+ const content = fileCache.get(normalizedPath);
1654
+ if (content !== undefined) {
1655
+ return ts.createSourceFile(normalizedPath, content, languageVersion, true);
1656
+ }
1657
+ const altContent = fileCache.get(fileName);
1658
+ if (altContent !== undefined) {
1659
+ return ts.createSourceFile(fileName, altContent, languageVersion, true);
1660
+ }
1661
+ const libContent = getLibContent(fileName);
1662
+ if (libContent !== undefined) {
1663
+ return ts.createSourceFile(fileName, libContent, languageVersion, true);
1664
+ }
1665
+ if (onError) {
1666
+ onError(`File not found: ${fileName}`);
1667
+ }
1668
+ return;
1669
+ },
1670
+ getDefaultLibFileName(options) {
1671
+ return LIB_PATH_PREFIX + ts.getDefaultLibFileName(options);
1672
+ },
1673
+ writeFile() {},
1674
+ getCurrentDirectory() {
1675
+ return "/";
1676
+ },
1677
+ getCanonicalFileName(fileName) {
1678
+ return fileName;
1679
+ },
1680
+ useCaseSensitiveFileNames() {
1681
+ return true;
1682
+ },
1683
+ getNewLine() {
1684
+ return `
1685
+ `;
1686
+ },
1687
+ fileExists(fileName) {
1688
+ const normalizedPath = normalizePath(fileName);
1689
+ if (fileCache.has(normalizedPath) || fileCache.has(fileName)) {
1690
+ return true;
1691
+ }
1692
+ return getLibContent(fileName) !== undefined;
1693
+ },
1694
+ readFile(fileName) {
1695
+ const normalizedPath = normalizePath(fileName);
1696
+ const content = fileCache.get(normalizedPath) ?? fileCache.get(fileName);
1697
+ if (content !== undefined) {
1698
+ return content;
1699
+ }
1700
+ return getLibContent(fileName);
1701
+ },
1702
+ directoryExists(directoryName) {
1703
+ const normalizedDir = normalizePath(directoryName);
1704
+ for (const path of fileCache.keys()) {
1705
+ if (path.startsWith(normalizedDir + "/")) {
1706
+ return true;
1707
+ }
1708
+ }
1709
+ if (normalizedDir === "/node_modules/typescript/lib" || normalizedDir === "/node_modules/typescript") {
1710
+ return libFiles.size > 0;
1711
+ }
1712
+ if (normalizedDir === "/node_modules") {
1713
+ return libFiles.size > 0 || Array.from(fileCache.keys()).some((p) => p.startsWith("/node_modules/"));
1714
+ }
1715
+ if (normalizedDir === "/node_modules/@types") {
1716
+ return Array.from(fileCache.keys()).some((p) => p.startsWith("/node_modules/@types/"));
1717
+ }
1718
+ return false;
1719
+ },
1720
+ getDirectories(path) {
1721
+ const normalizedPath = normalizePath(path);
1722
+ const prefix = normalizedPath === "/" ? "/" : normalizedPath + "/";
1723
+ const dirs = new Set;
1724
+ for (const filePath of fileCache.keys()) {
1725
+ if (filePath.startsWith(prefix)) {
1726
+ const relative = filePath.slice(prefix.length);
1727
+ const firstSlash = relative.indexOf("/");
1728
+ if (firstSlash > 0) {
1729
+ dirs.add(relative.slice(0, firstSlash));
1730
+ }
1731
+ }
1732
+ }
1733
+ if (normalizedPath === "/") {
1734
+ const hasNodeModules = libFiles.size > 0 || Array.from(fileCache.keys()).some((p) => p.startsWith("/node_modules/"));
1735
+ if (hasNodeModules) {
1736
+ dirs.add("node_modules");
1737
+ }
1738
+ }
1739
+ return Array.from(dirs);
1740
+ },
1741
+ realpath(path) {
1742
+ return path;
1743
+ },
1744
+ getEnvironmentVariable() {
1745
+ return;
1746
+ }
1747
+ };
1748
+ }
1749
+ async function typecheck(options) {
1750
+ const {
1751
+ fs,
1752
+ entryPoint,
1753
+ tsconfigPath = "/tsconfig.json",
1754
+ libFiles = new Map
1755
+ } = options;
1756
+ const normalizedEntry = normalizePath(entryPoint);
1757
+ if (!await fs.exists(normalizedEntry)) {
1758
+ throw new Error(`Entry point not found: ${normalizedEntry}`);
1759
+ }
1760
+ const fileCache = await preloadFiles(fs);
1761
+ let compilerOptions = getDefaultCompilerOptions();
1762
+ const tsconfigContent = fileCache.get(normalizePath(tsconfigPath));
1763
+ if (tsconfigContent) {
1764
+ compilerOptions = {
1765
+ ...parseTsConfig(tsconfigContent, tsconfigPath),
1766
+ noEmit: true
1767
+ };
1768
+ }
1769
+ const host = createVfsCompilerHost(fileCache, libFiles, compilerOptions);
1770
+ const program = ts.createProgram([normalizedEntry], compilerOptions, host);
1771
+ const allDiagnostics = [
1772
+ ...program.getSyntacticDiagnostics(),
1773
+ ...program.getSemanticDiagnostics(),
1774
+ ...program.getDeclarationDiagnostics()
1775
+ ];
1776
+ const diagnostics = allDiagnostics.map(convertDiagnostic);
1777
+ const checkedFiles = program.getSourceFiles().map((sf) => sf.fileName).filter((f) => !f.includes("node_modules/typescript/lib"));
1778
+ const hasErrors = diagnostics.some((d) => d.category === "error");
1779
+ return {
1780
+ diagnostics,
1781
+ hasErrors,
1782
+ checkedFiles
1783
+ };
1784
+ }
1785
+ function formatDiagnostics(diagnostics) {
1786
+ return diagnostics.map((d) => {
1787
+ const location = d.file ? `${d.file}${d.line ? `:${d.line}` : ""}${d.column ? `:${d.column}` : ""}` : "(global)";
1788
+ return `${location} - ${d.category} TS${d.code}: ${d.message}`;
1789
+ }).join(`
1790
+ `);
1791
+ }
1792
+ function formatDiagnosticsForAgent(diagnostics) {
1793
+ if (diagnostics.length === 0) {
1794
+ return "No type errors found.";
1795
+ }
1796
+ const errorCount = diagnostics.filter((d) => d.category === "error").length;
1797
+ const warningCount = diagnostics.filter((d) => d.category === "warning").length;
1798
+ const summary = errorCount > 0 || warningCount > 0 ? `Found ${errorCount} error(s) and ${warningCount} warning(s):
1799
+
1800
+ ` : "";
1801
+ const formatted = diagnostics.map((d) => {
1802
+ const location = d.file ? `${d.file}${d.line ? ` at line ${d.line}` : ""}` : "Global";
1803
+ const prefix = d.category === "error" ? "Error" : d.category === "warning" ? "Warning" : "Info";
1804
+ return `${prefix} in ${location}: ${d.message} (TS${d.code})`;
1805
+ }).join(`
1806
+
1807
+ `);
1808
+ return summary + formatted;
1809
+ }
1810
+
1811
+ // src/loader.ts
1812
+ class ModuleLoadError extends Error {
1813
+ constructor(message, cause) {
1814
+ super(message, { cause });
1815
+ this.name = "ModuleLoadError";
1816
+ }
1817
+ }
1818
+
1819
+ class ExportNotFoundError extends Error {
1820
+ exportName;
1821
+ availableExports;
1822
+ constructor(exportName, availableExports) {
1823
+ super(`Export "${exportName}" not found. Available exports: ${availableExports.length > 0 ? availableExports.join(", ") : "(none)"}`);
1824
+ this.exportName = exportName;
1825
+ this.availableExports = availableExports;
1826
+ this.name = "ExportNotFoundError";
1827
+ }
1828
+ }
1829
+ function createModuleUrl(result) {
1830
+ const blob = new Blob([result.code], { type: "application/javascript" });
1831
+ return URL.createObjectURL(blob);
1832
+ }
1833
+ function revokeModuleUrl(url) {
1834
+ URL.revokeObjectURL(url);
1835
+ }
1836
+ async function loadModule(result) {
1837
+ const url = createModuleUrl(result);
1838
+ try {
1839
+ const module = await import(url);
1840
+ return module;
1841
+ } catch (err) {
1842
+ throw new ModuleLoadError(`Failed to load module: ${err instanceof Error ? err.message : String(err)}`, err);
1843
+ } finally {
1844
+ revokeModuleUrl(url);
1845
+ }
1846
+ }
1847
+ async function loadExport(result, exportName = "default") {
1848
+ const module = await loadModule(result);
1849
+ if (!(exportName in module)) {
1850
+ const availableExports = Object.keys(module).filter((key) => !key.startsWith("__"));
1851
+ throw new ExportNotFoundError(exportName, availableExports);
1852
+ }
1853
+ return module[exportName];
1854
+ }
1855
+ async function loadDefault(result) {
1856
+ return loadExport(result, "default");
1857
+ }
1858
+ async function getExportNames(result) {
1859
+ const module = await loadModule(result);
1860
+ return Object.keys(module).filter((key) => !key.startsWith("__"));
1861
+ }
1862
+ async function hasExport(result, exportName) {
1863
+ const module = await loadModule(result);
1864
+ return exportName in module;
1865
+ }
1866
+
1867
+ // src/commands.ts
1868
+ function formatEsbuildMessages(messages) {
1869
+ if (messages.length === 0)
1870
+ return "";
1871
+ return messages.map((msg) => {
1872
+ if (msg.location) {
1873
+ const { file, line, column } = msg.location;
1874
+ const loc = file ? `${file}${line ? `:${line}` : ""}${column ? `:${column}` : ""}` : "";
1875
+ return loc ? `${loc}: ${msg.text}` : msg.text;
1876
+ }
1877
+ return msg.text;
1878
+ }).join(`
1879
+ `);
1880
+ }
1881
+ function createTscCommand(deps) {
1882
+ const { fs, libFiles, tsconfigPath } = deps;
1883
+ return defineCommand("tsc", async (args, _ctx) => {
1884
+ const entryPoint = args[0];
1885
+ if (!entryPoint) {
1886
+ return {
1887
+ stdout: "",
1888
+ stderr: `Usage: tsc <entry-point>
1889
+
1890
+ Example: tsc /src/index.ts
1891
+ `,
1892
+ exitCode: 1
1893
+ };
1894
+ }
1895
+ try {
1896
+ if (!await fs.exists(entryPoint)) {
1897
+ return {
1898
+ stdout: "",
1899
+ stderr: `Error: Entry point not found: ${entryPoint}
1900
+ `,
1901
+ exitCode: 1
1902
+ };
1903
+ }
1904
+ const result = await typecheck({
1905
+ fs,
1906
+ entryPoint,
1907
+ tsconfigPath,
1908
+ libFiles
1909
+ });
1910
+ if (result.hasErrors) {
1911
+ const formatted = formatDiagnosticsForAgent(result.diagnostics);
1912
+ return {
1913
+ stdout: "",
1914
+ stderr: formatted + `
1915
+ `,
1916
+ exitCode: 1
1917
+ };
1918
+ }
1919
+ const checkedCount = result.checkedFiles.length;
1920
+ const warningCount = result.diagnostics.filter((d) => d.category === "warning").length;
1921
+ let output = `Type check passed. Checked ${checkedCount} file(s).
1922
+ `;
1923
+ if (warningCount > 0) {
1924
+ output += `
1925
+ Warnings:
1926
+ ${formatDiagnosticsForAgent(result.diagnostics.filter((d) => d.category === "warning"))}
1927
+ `;
1928
+ }
1929
+ return {
1930
+ stdout: output,
1931
+ stderr: "",
1932
+ exitCode: 0
1933
+ };
1934
+ } catch (err) {
1935
+ return {
1936
+ stdout: "",
1937
+ stderr: `Type check failed: ${err instanceof Error ? err.message : String(err)}
1938
+ `,
1939
+ exitCode: 1
1940
+ };
1941
+ }
1942
+ });
1943
+ }
1944
+ function createBuildCommand(deps) {
1945
+ const { fs, libFiles, tsconfigPath, onBuild, sharedModules } = deps;
1946
+ return defineCommand("build", async (args, _ctx) => {
1947
+ let entryPoint = null;
1948
+ let skipTypecheck = false;
1949
+ let minify = false;
1950
+ let format = "esm";
1951
+ for (let i = 0;i < args.length; i++) {
1952
+ const arg = args[i];
1953
+ if (arg === "--skip-typecheck" || arg === "-s") {
1954
+ skipTypecheck = true;
1955
+ } else if (arg === "--minify" || arg === "-m") {
1956
+ minify = true;
1957
+ } else if ((arg === "--format" || arg === "-f") && args[i + 1]) {
1958
+ const f = args[++i].toLowerCase();
1959
+ if (f === "esm" || f === "iife" || f === "cjs") {
1960
+ format = f;
1961
+ }
1962
+ } else if (!arg.startsWith("-")) {
1963
+ entryPoint = arg;
1964
+ }
1965
+ }
1966
+ if (!entryPoint) {
1967
+ return {
1968
+ stdout: "",
1969
+ stderr: `Usage: build <entry-point> [options]
1970
+
1971
+ Options:
1972
+ --skip-typecheck, -s Skip type checking
1973
+ --minify, -m Minify output
1974
+ --format, -f <fmt> Output format (esm|iife|cjs)
1975
+
1976
+ Example: build /src/index.ts
1977
+ `,
1978
+ exitCode: 1
1979
+ };
1980
+ }
1981
+ try {
1982
+ if (!await fs.exists(entryPoint)) {
1983
+ return {
1984
+ stdout: "",
1985
+ stderr: `Error: Entry point not found: ${entryPoint}
1986
+ `,
1987
+ exitCode: 1
1988
+ };
1989
+ }
1990
+ let typecheckResult = null;
1991
+ if (!skipTypecheck) {
1992
+ typecheckResult = await typecheck({
1993
+ fs,
1994
+ entryPoint,
1995
+ tsconfigPath,
1996
+ libFiles
1997
+ });
1998
+ if (typecheckResult.hasErrors) {
1999
+ const formatted = formatDiagnosticsForAgent(typecheckResult.diagnostics);
2000
+ return {
2001
+ stdout: "",
2002
+ stderr: `Build failed: Type errors found.
2003
+
2004
+ ${formatted}
2005
+ `,
2006
+ exitCode: 1
2007
+ };
2008
+ }
2009
+ }
2010
+ const bundleResult = await bundle({
2011
+ fs,
2012
+ entryPoint,
2013
+ format,
2014
+ minify,
2015
+ sharedModules
2016
+ });
2017
+ if (onBuild) {
2018
+ await onBuild(bundleResult);
2019
+ }
2020
+ let output = `Build successful!
2021
+ `;
2022
+ output += `Entry: ${entryPoint}
2023
+ `;
2024
+ output += `Format: ${format}
2025
+ `;
2026
+ output += `Size: ${(bundleResult.code.length / 1024).toFixed(2)} KB
2027
+ `;
2028
+ if (typecheckResult) {
2029
+ output += `Type checked: ${typecheckResult.checkedFiles.length} file(s)
2030
+ `;
2031
+ }
2032
+ output += `Bundled: ${bundleResult.includedFiles.length} file(s)
2033
+ `;
2034
+ if (bundleResult.warnings.length > 0) {
2035
+ output += `
2036
+ Build warnings:
2037
+ ${formatEsbuildMessages(bundleResult.warnings)}
2038
+ `;
2039
+ }
2040
+ if (typecheckResult) {
2041
+ const warnings = typecheckResult.diagnostics.filter((d) => d.category === "warning");
2042
+ if (warnings.length > 0) {
2043
+ output += `
2044
+ Type warnings:
2045
+ ${formatDiagnosticsForAgent(warnings)}
2046
+ `;
2047
+ }
2048
+ }
2049
+ return {
2050
+ stdout: output,
2051
+ stderr: "",
2052
+ exitCode: 0
2053
+ };
2054
+ } catch (err) {
2055
+ const errorMessage = err instanceof Error ? err.message : String(err);
2056
+ return {
2057
+ stdout: "",
2058
+ stderr: `Build failed: ${errorMessage}
2059
+ `,
2060
+ exitCode: 1
2061
+ };
2062
+ }
2063
+ });
2064
+ }
2065
+ function createInstallCommand(deps) {
2066
+ const { fs, typesCache } = deps;
2067
+ return defineCommand("install", async (args, _ctx) => {
2068
+ if (args.length === 0) {
2069
+ return {
2070
+ stdout: "",
2071
+ stderr: `Usage: install <package>[@version] [...packages]
2072
+
2073
+ Examples:
2074
+ install react
2075
+ install lodash@4.17.21
2076
+ install @tanstack/react-query@5
2077
+ `,
2078
+ exitCode: 1
2079
+ };
2080
+ }
2081
+ const results = [];
2082
+ let hasError = false;
2083
+ for (const packageSpec of args) {
2084
+ try {
2085
+ const result = await installPackage(fs, packageSpec, { cache: typesCache });
2086
+ let status = `+ ${result.name}@${result.version}`;
2087
+ if (result.typesInstalled) {
2088
+ status += ` (${result.typeFilesCount} type file${result.typeFilesCount !== 1 ? "s" : ""})`;
2089
+ if (result.fromCache) {
2090
+ status += " [cached]";
2091
+ }
2092
+ } else if (result.typesError) {
2093
+ status += ` (no types: ${result.typesError})`;
2094
+ }
2095
+ results.push(status);
2096
+ } catch (err) {
2097
+ hasError = true;
2098
+ const message = err instanceof Error ? err.message : String(err);
2099
+ results.push(`x ${packageSpec}: ${message}`);
2100
+ }
2101
+ }
2102
+ const output = results.join(`
2103
+ `) + `
2104
+ `;
2105
+ if (hasError) {
2106
+ return {
2107
+ stdout: "",
2108
+ stderr: output,
2109
+ exitCode: 1
2110
+ };
2111
+ }
2112
+ return {
2113
+ stdout: output,
2114
+ stderr: "",
2115
+ exitCode: 0
2116
+ };
2117
+ });
2118
+ }
2119
+ function createUninstallCommand(deps) {
2120
+ const { fs } = deps;
2121
+ return defineCommand("uninstall", async (args, _ctx) => {
2122
+ if (args.length === 0) {
2123
+ return {
2124
+ stdout: "",
2125
+ stderr: `Usage: uninstall <package> [...packages]
2126
+ `,
2127
+ exitCode: 1
2128
+ };
2129
+ }
2130
+ const results = [];
2131
+ let hasError = false;
2132
+ for (const packageName of args) {
2133
+ try {
2134
+ const removed = await uninstallPackage(fs, packageName);
2135
+ if (removed) {
2136
+ results.push(`- ${packageName}`);
2137
+ } else {
2138
+ results.push(`x ${packageName}: not installed`);
2139
+ hasError = true;
2140
+ }
2141
+ } catch (err) {
2142
+ hasError = true;
2143
+ const message = err instanceof Error ? err.message : String(err);
2144
+ results.push(`x ${packageName}: ${message}`);
2145
+ }
2146
+ }
2147
+ const output = results.join(`
2148
+ `) + `
2149
+ `;
2150
+ if (hasError) {
2151
+ return {
2152
+ stdout: "",
2153
+ stderr: output,
2154
+ exitCode: 1
2155
+ };
2156
+ }
2157
+ return {
2158
+ stdout: output,
2159
+ stderr: "",
2160
+ exitCode: 0
2161
+ };
2162
+ });
2163
+ }
2164
+ function createListCommand(deps) {
2165
+ const { fs } = deps;
2166
+ return defineCommand("list", async (_args, _ctx) => {
2167
+ try {
2168
+ const packages = await listPackages(fs);
2169
+ if (packages.length === 0) {
2170
+ return {
2171
+ stdout: `No packages installed.
2172
+ `,
2173
+ stderr: "",
2174
+ exitCode: 0
2175
+ };
2176
+ }
2177
+ const output = packages.map((pkg) => `${pkg.name}@${pkg.version}`).join(`
2178
+ `) + `
2179
+ `;
2180
+ return {
2181
+ stdout: output,
2182
+ stderr: "",
2183
+ exitCode: 0
2184
+ };
2185
+ } catch (err) {
2186
+ const message = err instanceof Error ? err.message : String(err);
2187
+ return {
2188
+ stdout: "",
2189
+ stderr: `Failed to list packages: ${message}
2190
+ `,
2191
+ exitCode: 1
2192
+ };
2193
+ }
2194
+ });
2195
+ }
2196
+ function createRunCommand(deps) {
2197
+ const { fs, libFiles, tsconfigPath, runOptions = {}, sharedModules } = deps;
2198
+ return defineCommand("run", async (args, _ctx) => {
2199
+ let entryPoint = null;
2200
+ let skipTypecheck = runOptions.skipTypecheck ?? false;
2201
+ let timeout = runOptions.timeout ?? 30000;
2202
+ const scriptArgs = [];
2203
+ let collectingArgs = false;
2204
+ for (let i = 0;i < args.length; i++) {
2205
+ const arg = args[i];
2206
+ if (collectingArgs) {
2207
+ scriptArgs.push(arg);
2208
+ continue;
2209
+ }
2210
+ if (arg === "--") {
2211
+ collectingArgs = true;
2212
+ } else if (arg === "--skip-typecheck" || arg === "-s") {
2213
+ skipTypecheck = true;
2214
+ } else if ((arg === "--timeout" || arg === "-t") && args[i + 1]) {
2215
+ timeout = parseInt(args[++i], 10);
2216
+ if (isNaN(timeout))
2217
+ timeout = 30000;
2218
+ } else if (!arg.startsWith("-")) {
2219
+ entryPoint = arg;
2220
+ }
2221
+ }
2222
+ if (!entryPoint) {
2223
+ return {
2224
+ stdout: "",
2225
+ stderr: `Usage: run <entry-point> [options] [-- args...]
2226
+
2227
+ Options:
2228
+ --skip-typecheck, -s Skip type checking
2229
+ --timeout, -t <ms> Execution timeout (default: 30000)
2230
+
2231
+ Example: run /src/index.ts
2232
+ `,
2233
+ exitCode: 1
2234
+ };
2235
+ }
2236
+ const logs = [];
2237
+ const originalConsole = {
2238
+ log: console.log,
2239
+ warn: console.warn,
2240
+ error: console.error,
2241
+ info: console.info,
2242
+ debug: console.debug
2243
+ };
2244
+ const formatArgs = (...a) => a.map((v) => typeof v === "object" ? JSON.stringify(v) : String(v)).join(" ");
2245
+ const captureLog = (...a) => {
2246
+ logs.push(formatArgs(...a));
2247
+ originalConsole.log.apply(console, a);
2248
+ };
2249
+ const captureWarn = (...a) => {
2250
+ logs.push(`[warn] ${formatArgs(...a)}`);
2251
+ originalConsole.warn.apply(console, a);
2252
+ };
2253
+ const captureError = (...a) => {
2254
+ logs.push(`[error] ${formatArgs(...a)}`);
2255
+ originalConsole.error.apply(console, a);
2256
+ };
2257
+ const captureInfo = (...a) => {
2258
+ logs.push(`[info] ${formatArgs(...a)}`);
2259
+ originalConsole.info.apply(console, a);
2260
+ };
2261
+ const captureDebug = (...a) => {
2262
+ logs.push(`[debug] ${formatArgs(...a)}`);
2263
+ originalConsole.debug.apply(console, a);
2264
+ };
2265
+ const restoreConsole = () => {
2266
+ console.log = originalConsole.log;
2267
+ console.warn = originalConsole.warn;
2268
+ console.error = originalConsole.error;
2269
+ console.info = originalConsole.info;
2270
+ console.debug = originalConsole.debug;
2271
+ };
2272
+ try {
2273
+ if (!await fs.exists(entryPoint)) {
2274
+ return {
2275
+ stdout: "",
2276
+ stderr: `Error: Entry point not found: ${entryPoint}
2277
+ `,
2278
+ exitCode: 1
2279
+ };
2280
+ }
2281
+ if (!skipTypecheck) {
2282
+ const typecheckResult = await typecheck({
2283
+ fs,
2284
+ entryPoint,
2285
+ tsconfigPath,
2286
+ libFiles
2287
+ });
2288
+ if (typecheckResult.hasErrors) {
2289
+ const formatted = formatDiagnosticsForAgent(typecheckResult.diagnostics);
2290
+ return {
2291
+ stdout: "",
2292
+ stderr: `Type errors:
2293
+ ${formatted}
2294
+ `,
2295
+ exitCode: 1
2296
+ };
2297
+ }
2298
+ }
2299
+ const bundleResult = await bundle({
2300
+ fs,
2301
+ entryPoint,
2302
+ format: "esm",
2303
+ sharedModules
2304
+ });
2305
+ console.log = captureLog;
2306
+ console.warn = captureWarn;
2307
+ console.error = captureError;
2308
+ console.info = captureInfo;
2309
+ console.debug = captureDebug;
2310
+ const context = {
2311
+ fs,
2312
+ env: { ...runOptions.env },
2313
+ args: scriptArgs,
2314
+ log: captureLog,
2315
+ error: captureError
2316
+ };
2317
+ const startTime = performance.now();
2318
+ let returnValue;
2319
+ const executeCode = async () => {
2320
+ const module = await loadModule(bundleResult);
2321
+ if (typeof module.main === "function") {
2322
+ returnValue = await module.main(context);
2323
+ }
2324
+ };
2325
+ if (timeout > 0) {
2326
+ const timeoutPromise = new Promise((_, reject) => {
2327
+ setTimeout(() => reject(new Error(`Execution timed out after ${timeout}ms`)), timeout);
2328
+ });
2329
+ await Promise.race([executeCode(), timeoutPromise]);
2330
+ } else {
2331
+ await executeCode();
2332
+ }
2333
+ const executionTimeMs = performance.now() - startTime;
2334
+ restoreConsole();
2335
+ let output = "";
2336
+ if (logs.length > 0) {
2337
+ output = logs.join(`
2338
+ `) + `
2339
+ `;
2340
+ }
2341
+ if (returnValue !== undefined) {
2342
+ const returnStr = typeof returnValue === "object" ? JSON.stringify(returnValue, null, 2) : String(returnValue);
2343
+ output += `[return] ${returnStr}
2344
+ `;
2345
+ }
2346
+ output += `
2347
+ Execution completed in ${executionTimeMs.toFixed(2)}ms
2348
+ `;
2349
+ return {
2350
+ stdout: output,
2351
+ stderr: "",
2352
+ exitCode: 0
2353
+ };
2354
+ } catch (err) {
2355
+ restoreConsole();
2356
+ const errorMessage = err instanceof Error ? err.message : String(err);
2357
+ const errorStack = err instanceof Error && err.stack ? `
2358
+ ${err.stack}` : "";
2359
+ let output = "";
2360
+ if (logs.length > 0) {
2361
+ output = logs.join(`
2362
+ `) + `
2363
+
2364
+ `;
2365
+ }
2366
+ return {
2367
+ stdout: output,
2368
+ stderr: `Runtime error: ${errorMessage}${errorStack}
2369
+ `,
2370
+ exitCode: 1
2371
+ };
2372
+ }
2373
+ });
2374
+ }
2375
+ function createDefaultCommands(deps) {
2376
+ return [
2377
+ createTscCommand(deps),
2378
+ createBuildCommand(deps),
2379
+ createRunCommand(deps),
2380
+ createInstallCommand(deps),
2381
+ createUninstallCommand(deps),
2382
+ createListCommand(deps)
2383
+ ];
2384
+ }
2385
+
2386
+ // src/ts-libs.ts
2387
+ var TS_VERSION = "5.9.3";
2388
+ var CDN_BASE = `https://cdn.jsdelivr.net/npm/typescript@${TS_VERSION}/lib`;
2389
+ var DB_NAME = "ts-lib-cache";
2390
+ var DB_VERSION = 1;
2391
+ var STORE_NAME = "libs";
2392
+ function getDefaultBrowserLibs() {
2393
+ return ["es2020", "dom", "dom.iterable"];
2394
+ }
2395
+ function parseLibReferences(content) {
2396
+ const refs = [];
2397
+ const regex = /\/\/\/\s*<reference\s+lib="([^"]+)"\s*\/>/g;
2398
+ let match;
2399
+ while ((match = regex.exec(content)) !== null) {
2400
+ if (match[1]) {
2401
+ refs.push(match[1]);
2402
+ }
2403
+ }
2404
+ return refs;
2405
+ }
2406
+ function libNameToFileName(name) {
2407
+ return `lib.${name}.d.ts`;
2408
+ }
2409
+ function extractLibName(filePath) {
2410
+ const match = filePath.match(/lib\.([^/]+)\.d\.ts$/);
2411
+ return match?.[1] ?? null;
2412
+ }
2413
+ async function fetchLibFile(name) {
2414
+ const fileName = libNameToFileName(name);
2415
+ const url = `${CDN_BASE}/${fileName}`;
2416
+ const response = await fetch(url);
2417
+ if (!response.ok) {
2418
+ throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
2419
+ }
2420
+ return response.text();
2421
+ }
2422
+ async function fetchAllLibs(libs) {
2423
+ const result = new Map;
2424
+ const pending = new Set(libs);
2425
+ const fetched = new Set;
2426
+ while (pending.size > 0) {
2427
+ const batch = Array.from(pending);
2428
+ pending.clear();
2429
+ const results = await Promise.all(batch.map(async (name) => {
2430
+ if (fetched.has(name)) {
2431
+ return { name, content: null };
2432
+ }
2433
+ fetched.add(name);
2434
+ try {
2435
+ const content = await fetchLibFile(name);
2436
+ return { name, content };
2437
+ } catch (err) {
2438
+ console.warn(`Failed to fetch lib.${name}.d.ts:`, err);
2439
+ return { name, content: null };
2440
+ }
2441
+ }));
2442
+ for (const { name, content } of results) {
2443
+ if (content === null)
2444
+ continue;
2445
+ result.set(name, content);
2446
+ const refs = parseLibReferences(content);
2447
+ for (const ref of refs) {
2448
+ if (!fetched.has(ref) && !pending.has(ref)) {
2449
+ pending.add(ref);
2450
+ }
2451
+ }
2452
+ }
2453
+ }
2454
+ return result;
2455
+ }
2456
+ async function openDatabase() {
2457
+ return new Promise((resolve, reject) => {
2458
+ const request = indexedDB.open(DB_NAME, DB_VERSION);
2459
+ request.onerror = () => reject(request.error);
2460
+ request.onsuccess = () => resolve(request.result);
2461
+ request.onupgradeneeded = (event) => {
2462
+ const db = event.target.result;
2463
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
2464
+ db.createObjectStore(STORE_NAME);
2465
+ }
2466
+ };
2467
+ });
2468
+ }
2469
+ function promisifyRequest(request) {
2470
+ return new Promise((resolve, reject) => {
2471
+ request.onsuccess = () => resolve(request.result);
2472
+ request.onerror = () => reject(request.error);
2473
+ });
2474
+ }
2475
+ function getCacheKey() {
2476
+ return `libs-${TS_VERSION}`;
2477
+ }
2478
+
2479
+ class LibCache {
2480
+ db;
2481
+ constructor(db) {
2482
+ this.db = db;
2483
+ }
2484
+ static async create() {
2485
+ const db = await openDatabase();
2486
+ return new LibCache(db);
2487
+ }
2488
+ async getOrFetch(libs) {
2489
+ const cached = await this.get();
2490
+ if (cached) {
2491
+ const missing = libs.filter((lib) => !cached.has(lib));
2492
+ if (missing.length === 0) {
2493
+ return cached;
2494
+ }
2495
+ console.log(`Cache missing libs: ${missing.join(", ")}, fetching all...`);
2496
+ }
2497
+ console.log(`Fetching TypeScript libs from CDN: ${libs.join(", ")}...`);
2498
+ const fetched = await fetchAllLibs(libs);
2499
+ console.log(`Fetched ${fetched.size} lib files`);
2500
+ await this.set(fetched);
2501
+ return fetched;
2502
+ }
2503
+ async get() {
2504
+ const tx = this.db.transaction(STORE_NAME, "readonly");
2505
+ const store = tx.objectStore(STORE_NAME);
2506
+ const key = getCacheKey();
2507
+ const cached = await promisifyRequest(store.get(key));
2508
+ if (!cached) {
2509
+ return null;
2510
+ }
2511
+ if (cached.version !== TS_VERSION) {
2512
+ console.log(`Cache version mismatch: ${cached.version} vs ${TS_VERSION}`);
2513
+ return null;
2514
+ }
2515
+ return new Map(Object.entries(cached.libs));
2516
+ }
2517
+ async set(libs) {
2518
+ const tx = this.db.transaction(STORE_NAME, "readwrite");
2519
+ const store = tx.objectStore(STORE_NAME);
2520
+ const key = getCacheKey();
2521
+ const cached = {
2522
+ version: TS_VERSION,
2523
+ timestamp: Date.now(),
2524
+ libs: Object.fromEntries(libs)
2525
+ };
2526
+ await promisifyRequest(store.put(cached, key));
2527
+ }
2528
+ async clear() {
2529
+ const tx = this.db.transaction(STORE_NAME, "readwrite");
2530
+ const store = tx.objectStore(STORE_NAME);
2531
+ await promisifyRequest(store.clear());
2532
+ }
2533
+ close() {
2534
+ this.db.close();
2535
+ }
2536
+ }
2537
+ async function fetchAndCacheLibs(libs = getDefaultBrowserLibs()) {
2538
+ const cache = await LibCache.create();
2539
+ try {
2540
+ return await cache.getOrFetch(libs);
2541
+ } finally {
2542
+ cache.close();
2543
+ }
2544
+ }
2545
+
2546
+ // src/shared-resources.ts
2547
+ async function createSharedResources(options = {}) {
2548
+ const { libs = getDefaultBrowserLibs(), skipLibs = false, skipBundler = false } = options;
2549
+ const libsPromise = skipLibs ? Promise.resolve(new Map) : fetchAndCacheLibs(libs);
2550
+ const bundlerPromise = skipBundler ? Promise.resolve() : initBundler();
2551
+ const typesCache = new InMemoryTypesCache;
2552
+ const [libFiles] = await Promise.all([libsPromise, bundlerPromise]);
2553
+ return {
2554
+ libFiles,
2555
+ bundlerReady: Promise.resolve(),
2556
+ typesCache
2557
+ };
2558
+ }
2559
+ var defaultResourcesInstance = null;
2560
+ var defaultResourcesPromise = null;
2561
+ async function getDefaultResources() {
2562
+ if (defaultResourcesInstance) {
2563
+ return defaultResourcesInstance;
2564
+ }
2565
+ if (!defaultResourcesPromise) {
2566
+ defaultResourcesPromise = createSharedResources().then((resources) => {
2567
+ defaultResourcesInstance = resources;
2568
+ return resources;
2569
+ });
2570
+ }
2571
+ return defaultResourcesPromise;
2572
+ }
2573
+ function clearDefaultResources() {
2574
+ defaultResourcesInstance = null;
2575
+ defaultResourcesPromise = null;
2576
+ }
2577
+ function hasDefaultResources() {
2578
+ return defaultResourcesInstance !== null;
2579
+ }
2580
+
2581
+ // src/sandbox.ts
2582
+ class BuildEmitter {
2583
+ listeners = new Set;
2584
+ lastResult = null;
2585
+ emit = async (result) => {
2586
+ this.lastResult = result;
2587
+ const promises = [];
2588
+ for (const listener of this.listeners) {
2589
+ const ret = listener(result);
2590
+ if (ret instanceof Promise) {
2591
+ promises.push(ret);
2592
+ }
2593
+ }
2594
+ await Promise.all(promises);
2595
+ };
2596
+ on(callback) {
2597
+ this.listeners.add(callback);
2598
+ return () => {
2599
+ this.listeners.delete(callback);
2600
+ };
2601
+ }
2602
+ waitFor() {
2603
+ if (this.lastResult) {
2604
+ const result = this.lastResult;
2605
+ this.lastResult = null;
2606
+ return Promise.resolve(result);
2607
+ }
2608
+ return new Promise((resolve) => {
2609
+ const unsub = this.on((result) => {
2610
+ unsub();
2611
+ this.lastResult = null;
2612
+ resolve(result);
2613
+ });
2614
+ });
2615
+ }
2616
+ }
2617
+ async function createSandbox(options = {}) {
2618
+ const {
2619
+ fsOptions = {},
2620
+ tsconfigPath = "/tsconfig.json",
2621
+ resources: providedResources,
2622
+ onBuild: onBuildCallback,
2623
+ customCommands = [],
2624
+ sharedModules
2625
+ } = options;
2626
+ const fsPromise = IndexedDbFs.create(fsOptions);
2627
+ const resourcesPromise = providedResources ? Promise.resolve(providedResources) : getDefaultResources();
2628
+ const bundlerPromise = initBundler();
2629
+ const [fs, resources] = await Promise.all([fsPromise, resourcesPromise, bundlerPromise]);
2630
+ const libFiles = resources.libFiles;
2631
+ const typesCache = resources.typesCache;
2632
+ const buildEmitter = new BuildEmitter;
2633
+ if (onBuildCallback) {
2634
+ buildEmitter.on(onBuildCallback);
2635
+ }
2636
+ const commandDeps = {
2637
+ fs,
2638
+ libFiles,
2639
+ tsconfigPath,
2640
+ onBuild: buildEmitter.emit,
2641
+ typesCache,
2642
+ sharedModules
2643
+ };
2644
+ const defaultCommands = createDefaultCommands(commandDeps);
2645
+ const bash = new Bash({
2646
+ fs,
2647
+ cwd: "/",
2648
+ customCommands: [...defaultCommands, ...customCommands]
2649
+ });
2650
+ return {
2651
+ fs,
2652
+ bash,
2653
+ isDirty: () => fs.isDirty(),
2654
+ save: () => fs.save(),
2655
+ close: () => fs.close(),
2656
+ onBuild: (callback) => buildEmitter.on(callback)
2657
+ };
2658
+ }
2659
+ async function createInMemorySandbox(options = {}) {
2660
+ const {
2661
+ initialFiles,
2662
+ tsconfigPath = "/tsconfig.json",
2663
+ resources: providedResources,
2664
+ onBuild: onBuildCallback,
2665
+ customCommands = [],
2666
+ sharedModules
2667
+ } = options;
2668
+ const resourcesPromise = providedResources ? Promise.resolve(providedResources) : getDefaultResources();
2669
+ const bundlerPromise = initBundler();
2670
+ const fs = IndexedDbFs.createInMemory({ initialFiles });
2671
+ const [resources] = await Promise.all([resourcesPromise, bundlerPromise]);
2672
+ const libFiles = resources.libFiles;
2673
+ const typesCache = resources.typesCache;
2674
+ const buildEmitter = new BuildEmitter;
2675
+ if (onBuildCallback) {
2676
+ buildEmitter.on(onBuildCallback);
2677
+ }
2678
+ const commandDeps = {
2679
+ fs,
2680
+ libFiles,
2681
+ tsconfigPath,
2682
+ onBuild: buildEmitter.emit,
2683
+ typesCache,
2684
+ sharedModules
2685
+ };
2686
+ const defaultCommands = createDefaultCommands(commandDeps);
2687
+ const bash = new Bash({
2688
+ fs,
2689
+ cwd: "/",
2690
+ customCommands: [...defaultCommands, ...customCommands]
2691
+ });
2692
+ return {
2693
+ fs,
2694
+ bash,
2695
+ isDirty: () => fs.isDirty(),
2696
+ save: () => Promise.resolve(false),
2697
+ close: () => {},
2698
+ onBuild: (callback) => buildEmitter.on(callback)
2699
+ };
2700
+ }
2701
+ // src/sandbox-manager.ts
2702
+ import { Bash as Bash2 } from "just-bash/browser";
2703
+ class BuildEmitter2 {
2704
+ listeners = new Set;
2705
+ lastResult = null;
2706
+ emit = async (result) => {
2707
+ this.lastResult = result;
2708
+ const promises = [];
2709
+ for (const listener of this.listeners) {
2710
+ const ret = listener(result);
2711
+ if (ret instanceof Promise) {
2712
+ promises.push(ret);
2713
+ }
2714
+ }
2715
+ await Promise.all(promises);
2716
+ };
2717
+ on(callback) {
2718
+ this.listeners.add(callback);
2719
+ return () => {
2720
+ this.listeners.delete(callback);
2721
+ };
2722
+ }
2723
+ waitFor() {
2724
+ if (this.lastResult) {
2725
+ const result = this.lastResult;
2726
+ this.lastResult = null;
2727
+ return Promise.resolve(result);
2728
+ }
2729
+ return new Promise((resolve) => {
2730
+ const unsub = this.on((result) => {
2731
+ unsub();
2732
+ this.lastResult = null;
2733
+ resolve(result);
2734
+ });
2735
+ });
2736
+ }
2737
+ }
2738
+
2739
+ class SandboxManager {
2740
+ resources = null;
2741
+ sandboxes = new Map;
2742
+ initialized = false;
2743
+ initPromise = null;
2744
+ nextId = 1;
2745
+ options;
2746
+ constructor(options = {}) {
2747
+ this.options = {
2748
+ libs: options.libs ?? getDefaultBrowserLibs(),
2749
+ ...options
2750
+ };
2751
+ }
2752
+ async initialize() {
2753
+ if (this.initialized)
2754
+ return;
2755
+ if (this.initPromise) {
2756
+ await this.initPromise;
2757
+ return;
2758
+ }
2759
+ this.initPromise = this.doInitialize();
2760
+ await this.initPromise;
2761
+ this.initialized = true;
2762
+ }
2763
+ async doInitialize() {
2764
+ this.resources = await createSharedResources({
2765
+ libs: this.options.libs,
2766
+ skipLibs: this.options.skipLibs,
2767
+ skipBundler: this.options.skipBundler
2768
+ });
2769
+ }
2770
+ async createSandbox(options = {}) {
2771
+ await this.initialize();
2772
+ const {
2773
+ id = `sandbox-${this.nextId++}`,
2774
+ fsOptions = {},
2775
+ initialFiles,
2776
+ tsconfigPath = "/tsconfig.json",
2777
+ onBuild,
2778
+ customCommands = [],
2779
+ inMemory = true,
2780
+ sharedModules = this.options.sharedModules
2781
+ } = options;
2782
+ let fs;
2783
+ if (inMemory) {
2784
+ fs = IndexedDbFs.createInMemory({
2785
+ initialFiles,
2786
+ maxSizeBytes: fsOptions.maxSizeBytes
2787
+ });
2788
+ } else {
2789
+ fs = await IndexedDbFs.create({
2790
+ dbName: fsOptions.dbName ?? id,
2791
+ initialFiles,
2792
+ maxSizeBytes: fsOptions.maxSizeBytes
2793
+ });
2794
+ }
2795
+ const buildEmitter = new BuildEmitter2;
2796
+ if (onBuild) {
2797
+ buildEmitter.on(onBuild);
2798
+ }
2799
+ const commandDeps = {
2800
+ fs,
2801
+ libFiles: this.resources.libFiles,
2802
+ tsconfigPath,
2803
+ onBuild: buildEmitter.emit,
2804
+ typesCache: this.resources.typesCache,
2805
+ sharedModules
2806
+ };
2807
+ const defaultCommands = createDefaultCommands(commandDeps);
2808
+ const bash = new Bash2({
2809
+ fs,
2810
+ cwd: "/",
2811
+ customCommands: [...defaultCommands, ...customCommands]
2812
+ });
2813
+ const sandbox = {
2814
+ id,
2815
+ fs,
2816
+ bash,
2817
+ isDirty: () => fs.isDirty(),
2818
+ save: () => fs.save(),
2819
+ close: () => {
2820
+ fs.close();
2821
+ this.sandboxes.delete(id);
2822
+ },
2823
+ onBuild: (callback) => buildEmitter.on(callback)
2824
+ };
2825
+ this.sandboxes.set(id, sandbox);
2826
+ return sandbox;
2827
+ }
2828
+ getSandbox(id) {
2829
+ return this.sandboxes.get(id);
2830
+ }
2831
+ getAllSandboxes() {
2832
+ return Array.from(this.sandboxes.values());
2833
+ }
2834
+ closeSandbox(id) {
2835
+ const sandbox = this.sandboxes.get(id);
2836
+ if (sandbox) {
2837
+ sandbox.close();
2838
+ return true;
2839
+ }
2840
+ return false;
2841
+ }
2842
+ destroyAll() {
2843
+ for (const sandbox of this.sandboxes.values()) {
2844
+ sandbox.fs.close();
2845
+ }
2846
+ this.sandboxes.clear();
2847
+ }
2848
+ async saveAll() {
2849
+ const results = new Map;
2850
+ for (const [id, sandbox] of this.sandboxes) {
2851
+ const saved = await sandbox.save();
2852
+ results.set(id, saved);
2853
+ }
2854
+ return results;
2855
+ }
2856
+ getDirtySandboxes() {
2857
+ return Array.from(this.sandboxes.entries()).filter(([_, sandbox]) => sandbox.isDirty()).map(([id]) => id);
2858
+ }
2859
+ getStats() {
2860
+ return {
2861
+ initialized: this.initialized,
2862
+ activeSandboxes: this.sandboxes.size,
2863
+ libFilesCount: this.resources?.libFiles.size ?? 0,
2864
+ sandboxIds: Array.from(this.sandboxes.keys())
2865
+ };
2866
+ }
2867
+ getResources() {
2868
+ return this.resources;
2869
+ }
2870
+ getLibFiles() {
2871
+ return this.resources?.libFiles ?? new Map;
2872
+ }
2873
+ }
2874
+ async function createSandboxManager(options = {}) {
2875
+ const manager = new SandboxManager(options);
2876
+ await manager.initialize();
2877
+ return manager;
2878
+ }
2879
+ export {
2880
+ unregisterSharedModule,
2881
+ uninstallPackage,
2882
+ typecheck,
2883
+ registerSharedModules,
2884
+ loadModule,
2885
+ loadExport,
2886
+ loadDefault,
2887
+ listPackages,
2888
+ installPackage,
2889
+ initBundler,
2890
+ hasExport,
2891
+ hasDefaultResources,
2892
+ getPackageManifest,
2893
+ getExportNames,
2894
+ getDefaultResources,
2895
+ getDefaultBrowserLibs,
2896
+ formatDiagnosticsForAgent,
2897
+ formatDiagnostics,
2898
+ fetchAndCacheLibs,
2899
+ createUninstallCommand,
2900
+ createTscCommand,
2901
+ createSharedResources,
2902
+ createSandboxManager,
2903
+ createSandbox,
2904
+ createRunCommand,
2905
+ createListCommand,
2906
+ createInstallCommand,
2907
+ createIndexedDbFs,
2908
+ createInMemorySandbox,
2909
+ createDefaultCommands,
2910
+ createBuildCommand,
2911
+ clearSharedModules,
2912
+ clearDefaultResources,
2913
+ bundleToUrl,
2914
+ bundleAndImport,
2915
+ bundle,
2916
+ SandboxManager,
2917
+ ModuleLoadError,
2918
+ IndexedDbFs,
2919
+ ExportNotFoundError
2920
+ };