sandlot 0.1.4 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/dist/browser/bundler.d.ts +68 -0
  2. package/dist/browser/bundler.d.ts.map +1 -0
  3. package/dist/browser/executor.d.ts +46 -0
  4. package/dist/browser/executor.d.ts.map +1 -0
  5. package/dist/browser/index.d.ts +9 -0
  6. package/dist/browser/index.d.ts.map +1 -0
  7. package/dist/browser/index.js +2690 -0
  8. package/dist/browser/preset.d.ts +63 -0
  9. package/dist/browser/preset.d.ts.map +1 -0
  10. package/dist/commands/index.d.ts +20 -11
  11. package/dist/commands/index.d.ts.map +1 -1
  12. package/dist/commands/types.d.ts +37 -130
  13. package/dist/commands/types.d.ts.map +1 -1
  14. package/dist/core/bundler-utils.d.ts +142 -0
  15. package/dist/core/bundler-utils.d.ts.map +1 -0
  16. package/dist/core/esm-types-resolver.d.ts +125 -0
  17. package/dist/core/esm-types-resolver.d.ts.map +1 -0
  18. package/dist/core/executor.d.ts +35 -0
  19. package/dist/core/executor.d.ts.map +1 -0
  20. package/dist/{fs.d.ts → core/fs.d.ts} +27 -29
  21. package/dist/core/fs.d.ts.map +1 -0
  22. package/dist/core/sandbox.d.ts +30 -0
  23. package/dist/core/sandbox.d.ts.map +1 -0
  24. package/dist/core/sandlot.d.ts +30 -0
  25. package/dist/core/sandlot.d.ts.map +1 -0
  26. package/dist/core/shared-module-registry.d.ts +46 -0
  27. package/dist/core/shared-module-registry.d.ts.map +1 -0
  28. package/dist/core/typechecker.d.ts +60 -0
  29. package/dist/core/typechecker.d.ts.map +1 -0
  30. package/dist/index.d.ts +11 -16
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +1398 -2010
  33. package/dist/node/bundler.d.ts +48 -0
  34. package/dist/node/bundler.d.ts.map +1 -0
  35. package/dist/node/executor.d.ts +48 -0
  36. package/dist/node/executor.d.ts.map +1 -0
  37. package/dist/node/index.d.ts +9 -0
  38. package/dist/node/index.d.ts.map +1 -0
  39. package/dist/node/index.js +2644 -0
  40. package/dist/node/preset.d.ts +62 -0
  41. package/dist/node/preset.d.ts.map +1 -0
  42. package/dist/types.d.ts +528 -0
  43. package/dist/types.d.ts.map +1 -0
  44. package/package.json +16 -6
  45. package/src/browser/bundler.ts +294 -0
  46. package/src/browser/executor.ts +71 -0
  47. package/src/browser/index.ts +57 -0
  48. package/src/browser/preset.ts +179 -0
  49. package/src/commands/index.ts +498 -37
  50. package/src/commands/types.ts +117 -145
  51. package/src/core/bundler-utils.ts +630 -0
  52. package/src/core/esm-types-resolver.ts +432 -0
  53. package/src/core/executor.ts +161 -0
  54. package/src/{fs.ts → core/fs.ts} +59 -37
  55. package/src/core/sandbox.ts +624 -0
  56. package/src/core/sandlot.ts +77 -0
  57. package/src/core/shared-module-registry.ts +138 -0
  58. package/src/core/typechecker.ts +609 -0
  59. package/src/index.ts +106 -139
  60. package/src/node/bundler.ts +194 -0
  61. package/src/node/executor.ts +87 -0
  62. package/src/node/index.ts +39 -0
  63. package/src/node/preset.ts +178 -0
  64. package/src/types.ts +672 -0
  65. package/README.md +0 -243
  66. package/dist/build-emitter.d.ts +0 -47
  67. package/dist/build-emitter.d.ts.map +0 -1
  68. package/dist/builder.d.ts +0 -370
  69. package/dist/builder.d.ts.map +0 -1
  70. package/dist/bundler.d.ts +0 -152
  71. package/dist/bundler.d.ts.map +0 -1
  72. package/dist/commands/compile.d.ts +0 -13
  73. package/dist/commands/compile.d.ts.map +0 -1
  74. package/dist/commands/packages.d.ts +0 -17
  75. package/dist/commands/packages.d.ts.map +0 -1
  76. package/dist/commands/run.d.ts +0 -40
  77. package/dist/commands/run.d.ts.map +0 -1
  78. package/dist/commands.d.ts +0 -179
  79. package/dist/commands.d.ts.map +0 -1
  80. package/dist/fs.d.ts.map +0 -1
  81. package/dist/internal.d.ts +0 -79
  82. package/dist/internal.d.ts.map +0 -1
  83. package/dist/internal.js +0 -1942
  84. package/dist/loader.d.ts +0 -164
  85. package/dist/loader.d.ts.map +0 -1
  86. package/dist/packages.d.ts +0 -199
  87. package/dist/packages.d.ts.map +0 -1
  88. package/dist/runner.d.ts +0 -314
  89. package/dist/runner.d.ts.map +0 -1
  90. package/dist/sandbox-manager.d.ts +0 -261
  91. package/dist/sandbox-manager.d.ts.map +0 -1
  92. package/dist/sandbox.d.ts +0 -267
  93. package/dist/sandbox.d.ts.map +0 -1
  94. package/dist/shared-modules.d.ts +0 -148
  95. package/dist/shared-modules.d.ts.map +0 -1
  96. package/dist/shared-resources.d.ts +0 -102
  97. package/dist/shared-resources.d.ts.map +0 -1
  98. package/dist/ts-libs.d.ts +0 -85
  99. package/dist/ts-libs.d.ts.map +0 -1
  100. package/dist/typechecker.d.ts +0 -127
  101. package/dist/typechecker.d.ts.map +0 -1
  102. package/src/build-emitter.ts +0 -64
  103. package/src/builder.ts +0 -498
  104. package/src/bundler.ts +0 -575
  105. package/src/commands/compile.ts +0 -236
  106. package/src/commands/packages.ts +0 -154
  107. package/src/commands/run.ts +0 -245
  108. package/src/internal.ts +0 -119
  109. package/src/loader.ts +0 -229
  110. package/src/packages.ts +0 -936
  111. package/src/sandbox.ts +0 -398
  112. package/src/shared-modules.ts +0 -280
  113. package/src/shared-resources.ts +0 -166
  114. package/src/ts-libs.ts +0 -218
  115. package/src/typechecker.ts +0 -635
package/dist/index.js CHANGED
@@ -6,10 +6,101 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
6
6
  throw Error('Dynamic require of "' + x + '" is not supported');
7
7
  });
8
8
 
9
- // src/sandbox.ts
9
+ // src/core/shared-module-registry.ts
10
+ var instanceCounter = 0;
11
+ function generateInstanceId() {
12
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
13
+ return crypto.randomUUID().slice(0, 8);
14
+ }
15
+ return `${Date.now().toString(36)}_${(++instanceCounter).toString(36)}`;
16
+ }
17
+
18
+ class SharedModuleRegistry {
19
+ modules;
20
+ exportNamesMap;
21
+ _registryKey;
22
+ constructor(modules) {
23
+ this._registryKey = `__sandlot_${generateInstanceId()}__`;
24
+ this.modules = new Map(Object.entries(modules));
25
+ this.exportNamesMap = new Map;
26
+ for (const [id, mod] of this.modules) {
27
+ this.exportNamesMap.set(id, this.introspectExports(mod));
28
+ }
29
+ }
30
+ get registryKey() {
31
+ return this._registryKey;
32
+ }
33
+ exposeGlobally() {
34
+ globalThis[this._registryKey] = this;
35
+ return this;
36
+ }
37
+ removeFromGlobal() {
38
+ if (globalThis[this._registryKey] === this) {
39
+ delete globalThis[this._registryKey];
40
+ }
41
+ }
42
+ get(moduleId) {
43
+ const mod = this.modules.get(moduleId);
44
+ if (mod === undefined && !this.modules.has(moduleId)) {
45
+ throw new Error(`Shared module "${moduleId}" not registered.`);
46
+ }
47
+ return mod;
48
+ }
49
+ has(moduleId) {
50
+ return this.modules.has(moduleId);
51
+ }
52
+ getExportNames(moduleId) {
53
+ return this.exportNamesMap.get(moduleId) ?? [];
54
+ }
55
+ list() {
56
+ return [...this.modules.keys()];
57
+ }
58
+ introspectExports(module) {
59
+ if (module === null || module === undefined) {
60
+ return [];
61
+ }
62
+ if (typeof module !== "object" && typeof module !== "function") {
63
+ return [];
64
+ }
65
+ const exports = [];
66
+ for (const key of Object.keys(module)) {
67
+ if (this.isValidIdentifier(key)) {
68
+ exports.push(key);
69
+ }
70
+ }
71
+ return exports;
72
+ }
73
+ isValidIdentifier(name) {
74
+ if (name.length === 0)
75
+ return false;
76
+ if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name))
77
+ return false;
78
+ const reserved = [
79
+ "default",
80
+ "class",
81
+ "function",
82
+ "var",
83
+ "let",
84
+ "const",
85
+ "import",
86
+ "export"
87
+ ];
88
+ return !reserved.includes(name);
89
+ }
90
+ }
91
+ function createSharedModuleRegistry(modules) {
92
+ if (!modules || Object.keys(modules).length === 0) {
93
+ return null;
94
+ }
95
+ const registry = new SharedModuleRegistry(modules);
96
+ registry.exposeGlobally();
97
+ return registry;
98
+ }
99
+
100
+ // src/core/sandbox.ts
10
101
  import { Bash } from "just-bash/browser";
11
102
 
12
- // src/fs.ts
103
+ // src/core/fs.ts
13
104
  var DEFAULT_FILE_MODE = 420;
14
105
  var DEFAULT_DIR_MODE = 493;
15
106
  var DEFAULT_SYMLINK_MODE = 511;
@@ -73,7 +164,7 @@ class Filesystem {
73
164
  }
74
165
  return size;
75
166
  }
76
- async readFile(path, options) {
167
+ readFile(path, options) {
77
168
  const normalizedPath = this.normalizePath(path);
78
169
  const entry = this.resolveSymlinks(normalizedPath);
79
170
  if (!entry) {
@@ -89,7 +180,7 @@ class Filesystem {
89
180
  const encoding = this.getEncoding(options) ?? "utf8";
90
181
  return this.decodeBuffer(content, encoding);
91
182
  }
92
- async readFileBuffer(path) {
183
+ readFileBuffer(path) {
93
184
  const normalizedPath = this.normalizePath(path);
94
185
  const entry = this.resolveSymlinks(normalizedPath);
95
186
  if (!entry) {
@@ -104,7 +195,7 @@ class Filesystem {
104
195
  }
105
196
  return new TextEncoder().encode(content);
106
197
  }
107
- async writeFile(path, content, _options) {
198
+ writeFile(path, content, _options) {
108
199
  const normalizedPath = this.normalizePath(path);
109
200
  this.checkSizeLimit(content);
110
201
  this.ensureParentDirs(normalizedPath);
@@ -119,20 +210,20 @@ class Filesystem {
119
210
  mtime: new Date
120
211
  });
121
212
  }
122
- async appendFile(path, content, options) {
213
+ appendFile(path, content, options) {
123
214
  const normalizedPath = this.normalizePath(path);
124
215
  let existing = "";
125
216
  try {
126
- existing = await this.readFile(normalizedPath);
217
+ existing = this.readFile(normalizedPath);
127
218
  } catch {}
128
219
  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);
129
- await this.writeFile(normalizedPath, newContent, options);
220
+ this.writeFile(normalizedPath, newContent, options);
130
221
  }
131
- async exists(path) {
222
+ exists(path) {
132
223
  const normalizedPath = this.normalizePath(path);
133
224
  return this.entries.has(normalizedPath);
134
225
  }
135
- async stat(path) {
226
+ stat(path) {
136
227
  const normalizedPath = this.normalizePath(path);
137
228
  const entry = this.resolveSymlinks(normalizedPath);
138
229
  if (!entry) {
@@ -140,7 +231,7 @@ class Filesystem {
140
231
  }
141
232
  return this.entryToStat(entry);
142
233
  }
143
- async lstat(path) {
234
+ lstat(path) {
144
235
  const normalizedPath = this.normalizePath(path);
145
236
  const entry = this.entries.get(normalizedPath);
146
237
  if (!entry) {
@@ -148,7 +239,7 @@ class Filesystem {
148
239
  }
149
240
  return this.entryToStat(entry);
150
241
  }
151
- async mkdir(path, options) {
242
+ mkdir(path, options) {
152
243
  const normalizedPath = this.normalizePath(path);
153
244
  if (this.entries.has(normalizedPath)) {
154
245
  if (options?.recursive) {
@@ -170,7 +261,7 @@ class Filesystem {
170
261
  mtime: new Date
171
262
  });
172
263
  }
173
- async readdir(path) {
264
+ readdir(path) {
174
265
  const normalizedPath = this.normalizePath(path);
175
266
  const entry = this.resolveSymlinks(normalizedPath);
176
267
  if (!entry) {
@@ -193,7 +284,7 @@ class Filesystem {
193
284
  }
194
285
  return names.sort();
195
286
  }
196
- async readdirWithFileTypes(path) {
287
+ readdirWithFileTypes(path) {
197
288
  const normalizedPath = this.normalizePath(path);
198
289
  const entry = this.resolveSymlinks(normalizedPath);
199
290
  if (!entry) {
@@ -221,7 +312,7 @@ class Filesystem {
221
312
  }
222
313
  return dirents.sort((a, b) => a.name.localeCompare(b.name));
223
314
  }
224
- async rm(path, options) {
315
+ rm(path, options) {
225
316
  const normalizedPath = this.normalizePath(path);
226
317
  const entry = this.entries.get(normalizedPath);
227
318
  if (!entry) {
@@ -230,7 +321,7 @@ class Filesystem {
230
321
  throw new Error(`ENOENT: no such file or directory, rm '${path}'`);
231
322
  }
232
323
  if (entry.type === "directory") {
233
- const children = await this.readdir(normalizedPath);
324
+ const children = this.readdir(normalizedPath);
234
325
  if (children.length > 0 && !options?.recursive) {
235
326
  throw new Error(`ENOTEMPTY: directory not empty, rm '${path}'`);
236
327
  }
@@ -245,7 +336,7 @@ class Filesystem {
245
336
  }
246
337
  this.entries.delete(normalizedPath);
247
338
  }
248
- async cp(src, dest, options) {
339
+ cp(src, dest, options) {
249
340
  const srcPath = this.normalizePath(src);
250
341
  const destPath = this.normalizePath(dest);
251
342
  const entry = this.entries.get(srcPath);
@@ -271,7 +362,7 @@ class Filesystem {
271
362
  this.entries.set(destPath, this.cloneEntry(entry));
272
363
  }
273
364
  }
274
- async mv(src, dest) {
365
+ mv(src, dest) {
275
366
  const srcPath = this.normalizePath(src);
276
367
  const destPath = this.normalizePath(dest);
277
368
  const entry = this.entries.get(srcPath);
@@ -317,7 +408,7 @@ class Filesystem {
317
408
  getAllPaths() {
318
409
  return [...this.entries.keys()].sort();
319
410
  }
320
- async chmod(path, mode) {
411
+ chmod(path, mode) {
321
412
  const normalizedPath = this.normalizePath(path);
322
413
  const entry = this.entries.get(normalizedPath);
323
414
  if (!entry) {
@@ -326,7 +417,7 @@ class Filesystem {
326
417
  entry.mode = mode;
327
418
  entry.mtime = new Date;
328
419
  }
329
- async symlink(target, linkPath) {
420
+ symlink(target, linkPath) {
330
421
  const normalizedLinkPath = this.normalizePath(linkPath);
331
422
  if (this.entries.has(normalizedLinkPath)) {
332
423
  throw new Error(`EEXIST: file already exists, symlink '${linkPath}'`);
@@ -339,7 +430,7 @@ class Filesystem {
339
430
  mtime: new Date
340
431
  });
341
432
  }
342
- async link(existingPath, newPath) {
433
+ link(existingPath, newPath) {
343
434
  const srcPath = this.normalizePath(existingPath);
344
435
  const destPath = this.normalizePath(newPath);
345
436
  const entry = this.entries.get(srcPath);
@@ -360,7 +451,7 @@ class Filesystem {
360
451
  mtime: new Date
361
452
  });
362
453
  }
363
- async readlink(path) {
454
+ readlink(path) {
364
455
  const normalizedPath = this.normalizePath(path);
365
456
  const entry = this.entries.get(normalizedPath);
366
457
  if (!entry) {
@@ -371,7 +462,7 @@ class Filesystem {
371
462
  }
372
463
  return entry.target;
373
464
  }
374
- async realpath(path) {
465
+ realpath(path) {
375
466
  const normalizedPath = this.normalizePath(path);
376
467
  const parts = normalizedPath.split("/").filter(Boolean);
377
468
  let resolved = "/";
@@ -397,7 +488,7 @@ class Filesystem {
397
488
  }
398
489
  return resolved;
399
490
  }
400
- async utimes(path, atime, mtime) {
491
+ utimes(path, atime, mtime) {
401
492
  const normalizedPath = this.normalizePath(path);
402
493
  const entry = this.entries.get(normalizedPath);
403
494
  if (!entry) {
@@ -541,39 +632,553 @@ class Filesystem {
541
632
  function createFilesystem(options) {
542
633
  return Filesystem.create(options);
543
634
  }
635
+ function wrapFilesystemForJustBash(fs) {
636
+ return {
637
+ readFile: async (path, options) => fs.readFile(path, options),
638
+ writeFile: async (path, content, options) => fs.writeFile(path, content, options),
639
+ appendFile: async (path, content, options) => fs.appendFile(path, content, options),
640
+ exists: async (path) => fs.exists(path),
641
+ stat: async (path) => fs.stat(path),
642
+ lstat: async (path) => fs.lstat(path),
643
+ mkdir: async (path, options) => fs.mkdir(path, options),
644
+ readdir: async (path) => fs.readdir(path),
645
+ readdirWithFileTypes: async (path) => fs.readdirWithFileTypes(path),
646
+ rm: async (path, options) => fs.rm(path, options),
647
+ cp: async (src, dest, options) => fs.cp(src, dest, options),
648
+ mv: async (src, dest) => fs.mv(src, dest),
649
+ chmod: async (path, mode) => fs.chmod(path, mode),
650
+ symlink: async (target, linkPath) => fs.symlink(target, linkPath),
651
+ link: async (existingPath, newPath) => fs.link(existingPath, newPath),
652
+ readlink: async (path) => fs.readlink(path),
653
+ realpath: async (path) => fs.realpath(path),
654
+ utimes: async (path, atime, mtime) => fs.utimes(path, atime, mtime),
655
+ readFileBuffer: async (path) => fs.readFileBuffer(path),
656
+ resolvePath: (base, path) => fs.resolvePath(base, path),
657
+ getAllPaths: () => fs.getAllPaths()
658
+ };
659
+ }
544
660
 
545
- // src/packages.ts
546
- var ESM_CDN_BASE = "https://esm.sh";
547
- var KNOWN_SUBPATHS = {
548
- react: ["jsx-runtime", "jsx-dev-runtime"],
549
- "react-dom": ["client", "server"]
550
- };
661
+ // src/commands/index.ts
662
+ import { defineCommand } from "just-bash/browser";
551
663
 
552
- class InMemoryTypesCache {
553
- cache = new Map;
554
- key(name, version) {
555
- return `${name}@${version}`;
664
+ // src/commands/types.ts
665
+ function formatSize(bytes) {
666
+ if (bytes < 1024)
667
+ return `${bytes} B`;
668
+ if (bytes < 1024 * 1024)
669
+ return `${(bytes / 1024).toFixed(2)} KB`;
670
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
671
+ }
672
+ function formatDiagnostics(diagnostics) {
673
+ if (diagnostics.length === 0)
674
+ return "";
675
+ return diagnostics.map((d) => {
676
+ const severity = d.severity.toUpperCase();
677
+ if (d.file) {
678
+ const loc = `${d.file}${d.line ? `:${d.line}` : ""}${d.column ? `:${d.column}` : ""}`;
679
+ return `${severity}: ${loc}: ${d.message}`;
680
+ }
681
+ return `${severity}: ${d.message}`;
682
+ }).join(`
683
+ `);
684
+ }
685
+ function formatBundleErrors(errors) {
686
+ if (errors.length === 0)
687
+ return "";
688
+ return errors.map((e) => {
689
+ let output = "";
690
+ if (e.location) {
691
+ const loc = `${e.location.file}:${e.location.line}${e.location.column ? `:${e.location.column}` : ""}`;
692
+ output += `ERROR: ${loc}: ${e.text}`;
693
+ if (e.location.lineText) {
694
+ output += `
695
+ ${e.location.line} | ${e.location.lineText}`;
696
+ if (e.location.column) {
697
+ const padding = " ".repeat(String(e.location.line).length + 3 + e.location.column - 1);
698
+ output += `
699
+ ${padding}^`;
700
+ }
701
+ }
702
+ } else {
703
+ output += `ERROR: ${e.text}`;
704
+ }
705
+ return output;
706
+ }).join(`
707
+
708
+ `);
709
+ }
710
+ function formatBuildFailure(failure, prefix = "Build failed") {
711
+ switch (failure.phase) {
712
+ case "entry":
713
+ return `${prefix}: ${failure.message}
714
+ `;
715
+ case "typecheck":
716
+ if (failure.diagnostics && failure.diagnostics.length > 0) {
717
+ const errors = failure.diagnostics.filter((d) => d.severity === "error");
718
+ if (errors.length > 0) {
719
+ return `${prefix}: Type check errors
720
+
721
+ ${formatDiagnostics(errors)}
722
+ `;
723
+ }
724
+ }
725
+ return `${prefix}: Type check errors
726
+ `;
727
+ case "bundle":
728
+ if (failure.bundleErrors && failure.bundleErrors.length > 0) {
729
+ return `${prefix}: Bundle errors
730
+
731
+ ${formatBundleErrors(failure.bundleErrors)}
732
+ `;
733
+ }
734
+ return `${prefix}: Bundle error
735
+ `;
736
+ default:
737
+ return `${prefix}: Unknown error
738
+ `;
739
+ }
740
+ }
741
+ // src/commands/index.ts
742
+ function createSandlotCommand(sandboxRef) {
743
+ return defineCommand("sandlot", async (args, ctx) => {
744
+ const subcommand = args[0];
745
+ if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
746
+ return showHelp();
747
+ }
748
+ switch (subcommand) {
749
+ case "build":
750
+ return handleBuild(sandboxRef, args.slice(1));
751
+ case "typecheck":
752
+ case "tsc":
753
+ return handleTypecheck(sandboxRef, args.slice(1));
754
+ case "install":
755
+ case "add":
756
+ case "i":
757
+ return handleInstall(sandboxRef, args.slice(1));
758
+ case "uninstall":
759
+ case "remove":
760
+ case "rm":
761
+ return handleUninstall(sandboxRef, args.slice(1));
762
+ case "run":
763
+ return handleRun(sandboxRef, args.slice(1));
764
+ default:
765
+ return {
766
+ stdout: "",
767
+ stderr: `Unknown command: sandlot ${subcommand}
768
+
769
+ Run 'sandlot help' for available commands.
770
+ `,
771
+ exitCode: 1
772
+ };
773
+ }
774
+ });
775
+ }
776
+ function showHelp() {
777
+ return {
778
+ stdout: `sandlot - In-browser TypeScript sandbox
779
+
780
+ Usage: sandlot <command> [options]
781
+
782
+ Commands:
783
+ build Build the project (typecheck, bundle)
784
+ run Build and execute code
785
+ typecheck Type check without building (alias: tsc)
786
+ install Install packages (aliases: add, i)
787
+ uninstall Remove packages (aliases: remove, rm)
788
+ help Show this help message
789
+
790
+ Run 'sandlot <command> --help' for command-specific options.
791
+
792
+ Examples:
793
+ sandlot build
794
+ sandlot run
795
+ sandlot run --skip-typecheck --timeout 5000
796
+ sandlot install react react-dom
797
+ sandlot typecheck
798
+ `,
799
+ stderr: "",
800
+ exitCode: 0
801
+ };
802
+ }
803
+ async function handleBuild(sandboxRef, args) {
804
+ let entryPoint;
805
+ let skipTypecheck = false;
806
+ let minify = false;
807
+ let format = "esm";
808
+ for (let i = 0;i < args.length; i++) {
809
+ const arg = args[i];
810
+ if (arg === "--skip-typecheck" || arg === "-s") {
811
+ skipTypecheck = true;
812
+ } else if (arg === "--minify" || arg === "-m") {
813
+ minify = true;
814
+ } else if ((arg === "--format" || arg === "-f") && args[i + 1]) {
815
+ const f = args[++i].toLowerCase();
816
+ if (f === "esm" || f === "iife" || f === "cjs") {
817
+ format = f;
818
+ }
819
+ } else if ((arg === "--entry" || arg === "-e") && args[i + 1]) {
820
+ entryPoint = args[++i];
821
+ } else if (arg === "--help" || arg === "-h") {
822
+ return {
823
+ stdout: `Usage: sandlot build [options]
824
+
825
+ Options:
826
+ --entry, -e <path> Entry point (default: from package.json main)
827
+ --skip-typecheck, -s Skip type checking
828
+ --minify, -m Minify output
829
+ --format, -f <fmt> Output format (esm|iife|cjs)
830
+ --help, -h Show this help message
831
+
832
+ Examples:
833
+ sandlot build
834
+ sandlot build --entry /src/main.ts
835
+ sandlot build --skip-typecheck --minify
836
+ `,
837
+ stderr: "",
838
+ exitCode: 0
839
+ };
840
+ } else if (arg && !arg.startsWith("-") && !entryPoint) {
841
+ entryPoint = arg;
842
+ }
843
+ }
844
+ const result = await sandboxRef.build({
845
+ entryPoint,
846
+ skipTypecheck,
847
+ minify,
848
+ format
849
+ });
850
+ if (!result.success) {
851
+ return {
852
+ stdout: "",
853
+ stderr: formatBuildFailure(result),
854
+ exitCode: 1
855
+ };
856
+ }
857
+ let output = `Build successful!
858
+ `;
859
+ output += `Size: ${formatSize(result.code.length)}
860
+ `;
861
+ output += `Files: ${result.includedFiles.length}
862
+ `;
863
+ if (result.warnings.length > 0) {
864
+ output += `
865
+ Warnings:
866
+ `;
867
+ for (const warning of result.warnings) {
868
+ if (warning.location) {
869
+ output += ` ${warning.location.file}:${warning.location.line}: ${warning.text}
870
+ `;
871
+ } else {
872
+ output += ` ${warning.text}
873
+ `;
874
+ }
875
+ }
876
+ }
877
+ return {
878
+ stdout: output,
879
+ stderr: "",
880
+ exitCode: 0
881
+ };
882
+ }
883
+ async function handleTypecheck(sandboxRef, args) {
884
+ let entryPoint;
885
+ for (let i = 0;i < args.length; i++) {
886
+ const arg = args[i];
887
+ if ((arg === "--entry" || arg === "-e") && args[i + 1]) {
888
+ entryPoint = args[++i];
889
+ } else if (arg === "--help" || arg === "-h") {
890
+ return {
891
+ stdout: `Usage: sandlot typecheck [options]
892
+
893
+ Options:
894
+ --entry, -e <path> Entry point (default: from package.json main)
895
+ --help, -h Show this help message
896
+
897
+ Aliases: sandlot tsc
898
+
899
+ Examples:
900
+ sandlot typecheck
901
+ sandlot typecheck --entry /src/main.ts
902
+ `,
903
+ stderr: "",
904
+ exitCode: 0
905
+ };
906
+ } else if (arg && !arg.startsWith("-") && !entryPoint) {
907
+ entryPoint = arg;
908
+ }
556
909
  }
557
- get(name, version) {
558
- return this.cache.get(this.key(name, version)) ?? null;
910
+ try {
911
+ const result = await sandboxRef.typecheck({ entryPoint });
912
+ if (!result.success) {
913
+ const errors = result.diagnostics.filter((d) => d.severity === "error");
914
+ const formatted = formatDiagnostics(errors);
915
+ return {
916
+ stdout: "",
917
+ stderr: `Type check failed
918
+
919
+ ${formatted}
920
+ `,
921
+ exitCode: 1
922
+ };
923
+ }
924
+ const warnings = result.diagnostics.filter((d) => d.severity === "warning");
925
+ let output = `Type check passed
926
+ `;
927
+ if (warnings.length > 0) {
928
+ output += `
929
+ Warnings:
930
+
931
+ ${formatDiagnostics(warnings)}
932
+ `;
933
+ }
934
+ return {
935
+ stdout: output,
936
+ stderr: "",
937
+ exitCode: 0
938
+ };
939
+ } catch (err) {
940
+ const message = err instanceof Error ? err.message : String(err);
941
+ return {
942
+ stdout: "",
943
+ stderr: `Type check error: ${message}
944
+ `,
945
+ exitCode: 1
946
+ };
559
947
  }
560
- set(name, version, types) {
561
- this.cache.set(this.key(name, version), types);
948
+ }
949
+ async function handleInstall(sandboxRef, args) {
950
+ if (args.includes("--help") || args.includes("-h")) {
951
+ return {
952
+ stdout: `Usage: sandlot install <package>[@version] [...packages]
953
+
954
+ Examples:
955
+ sandlot install react
956
+ sandlot install lodash@4.17.21
957
+ sandlot install @tanstack/react-query@5
958
+ sandlot install react react-dom
959
+
960
+ Aliases: sandlot add, sandlot i
961
+ `,
962
+ stderr: "",
963
+ exitCode: 0
964
+ };
562
965
  }
563
- has(name, version) {
564
- return this.cache.has(this.key(name, version));
966
+ const packages = args.filter((a) => !!a && !a.startsWith("-"));
967
+ if (packages.length === 0) {
968
+ return {
969
+ stdout: "",
970
+ stderr: `Usage: sandlot install <package>[@version] [...packages]
971
+
972
+ Run 'sandlot install --help' for more information.
973
+ `,
974
+ exitCode: 1
975
+ };
565
976
  }
566
- delete(name, version) {
567
- return this.cache.delete(this.key(name, version));
977
+ const results = [];
978
+ let hasError = false;
979
+ for (const packageSpec of packages) {
980
+ try {
981
+ const result = await sandboxRef.install(packageSpec);
982
+ let status = `+ ${result.name}@${result.version}`;
983
+ if (result.typesInstalled) {
984
+ status += ` (${result.typeFilesCount} type file${result.typeFilesCount !== 1 ? "s" : ""})`;
985
+ if (result.fromCache) {
986
+ status += " [cached]";
987
+ }
988
+ } else if (result.typesError) {
989
+ status += ` (no types: ${result.typesError})`;
990
+ }
991
+ results.push(status);
992
+ } catch (err) {
993
+ hasError = true;
994
+ const message = err instanceof Error ? err.message : String(err);
995
+ results.push(`x ${packageSpec}: ${message}`);
996
+ }
568
997
  }
569
- clear() {
570
- this.cache.clear();
998
+ const output = results.join(`
999
+ `) + `
1000
+ `;
1001
+ return hasError ? { stdout: "", stderr: output, exitCode: 1 } : { stdout: output, stderr: "", exitCode: 0 };
1002
+ }
1003
+ async function handleUninstall(sandboxRef, args) {
1004
+ if (args.includes("--help") || args.includes("-h")) {
1005
+ return {
1006
+ stdout: `Usage: sandlot uninstall <package> [...packages]
1007
+
1008
+ Examples:
1009
+ sandlot uninstall lodash
1010
+ sandlot uninstall react react-dom
1011
+
1012
+ Aliases: sandlot remove, sandlot rm
1013
+ `,
1014
+ stderr: "",
1015
+ exitCode: 0
1016
+ };
571
1017
  }
572
- get size() {
573
- return this.cache.size;
1018
+ const packages = args.filter((a) => !!a && !a.startsWith("-"));
1019
+ if (packages.length === 0) {
1020
+ return {
1021
+ stdout: "",
1022
+ stderr: `Usage: sandlot uninstall <package> [...packages]
1023
+
1024
+ Run 'sandlot uninstall --help' for more information.
1025
+ `,
1026
+ exitCode: 1
1027
+ };
1028
+ }
1029
+ const results = [];
1030
+ let hasError = false;
1031
+ for (const packageName of packages) {
1032
+ try {
1033
+ const result = await sandboxRef.uninstall(packageName);
1034
+ if (result.removed) {
1035
+ results.push(`- ${result.name}`);
1036
+ } else {
1037
+ results.push(`x ${packageName}: not installed`);
1038
+ hasError = true;
1039
+ }
1040
+ } catch (err) {
1041
+ hasError = true;
1042
+ const message = err instanceof Error ? err.message : String(err);
1043
+ results.push(`x ${packageName}: ${message}`);
1044
+ }
1045
+ }
1046
+ const output = results.join(`
1047
+ `) + `
1048
+ `;
1049
+ return hasError ? { stdout: "", stderr: output, exitCode: 1 } : { stdout: output, stderr: "", exitCode: 0 };
1050
+ }
1051
+ async function handleRun(sandboxRef, args) {
1052
+ let entryPoint;
1053
+ let skipTypecheck = false;
1054
+ let timeout = 30000;
1055
+ let entryExport = "main";
1056
+ for (let i = 0;i < args.length; i++) {
1057
+ const arg = args[i];
1058
+ if (arg === "--skip-typecheck" || arg === "-s") {
1059
+ skipTypecheck = true;
1060
+ } else if ((arg === "--timeout" || arg === "-t") && args[i + 1]) {
1061
+ const t = parseInt(args[++i], 10);
1062
+ if (!isNaN(t))
1063
+ timeout = t;
1064
+ } else if ((arg === "--entry" || arg === "-e") && args[i + 1]) {
1065
+ entryPoint = args[++i];
1066
+ } else if ((arg === "--export" || arg === "-x") && args[i + 1]) {
1067
+ const e = args[++i].toLowerCase();
1068
+ if (e === "main" || e === "default") {
1069
+ entryExport = e;
1070
+ }
1071
+ } else if (arg === "--help" || arg === "-h") {
1072
+ return {
1073
+ stdout: `Usage: sandlot run [options]
1074
+
1075
+ Options:
1076
+ --entry, -e <path> Entry point (default: from package.json main)
1077
+ --skip-typecheck, -s Skip type checking
1078
+ --timeout, -t <ms> Execution timeout (default: 30000, 0 = none)
1079
+ --export, -x <name> Export to call: main or default (default: main)
1080
+ --help, -h Show this help message
1081
+
1082
+ Examples:
1083
+ sandlot run
1084
+ sandlot run --entry /src/main.ts
1085
+ sandlot run --skip-typecheck --timeout 5000
1086
+ sandlot run --export default
1087
+ `,
1088
+ stderr: "",
1089
+ exitCode: 0
1090
+ };
1091
+ } else if (arg && !arg.startsWith("-") && !entryPoint) {
1092
+ entryPoint = arg;
1093
+ }
1094
+ }
1095
+ try {
1096
+ const result = await sandboxRef.run({
1097
+ entryPoint,
1098
+ skipTypecheck,
1099
+ timeout,
1100
+ entryExport
1101
+ });
1102
+ if (!result.success) {
1103
+ let stderr = "";
1104
+ if (result.buildFailure) {
1105
+ stderr = formatBuildFailure(result.buildFailure, "Run failed");
1106
+ } else {
1107
+ stderr = `Run failed: ${result.error ?? "Unknown error"}
1108
+ `;
1109
+ }
1110
+ let stdout = "";
1111
+ if (result.logs.length > 0) {
1112
+ stdout = result.logs.join(`
1113
+ `) + `
1114
+ `;
1115
+ }
1116
+ return {
1117
+ stdout,
1118
+ stderr,
1119
+ exitCode: 1
1120
+ };
1121
+ }
1122
+ let output = "";
1123
+ if (result.logs.length > 0) {
1124
+ output = result.logs.join(`
1125
+ `) + `
1126
+ `;
1127
+ }
1128
+ if (result.returnValue !== undefined) {
1129
+ const returnStr = typeof result.returnValue === "object" ? JSON.stringify(result.returnValue, null, 2) : String(result.returnValue);
1130
+ output += `[return] ${returnStr}
1131
+ `;
1132
+ }
1133
+ if (result.executionTimeMs !== undefined) {
1134
+ output += `
1135
+ Completed in ${result.executionTimeMs.toFixed(2)}ms
1136
+ `;
1137
+ }
1138
+ return {
1139
+ stdout: output,
1140
+ stderr: "",
1141
+ exitCode: 0
1142
+ };
1143
+ } catch (err) {
1144
+ const message = err instanceof Error ? err.message : String(err);
1145
+ return {
1146
+ stdout: "",
1147
+ stderr: `Run error: ${message}
1148
+ `,
1149
+ exitCode: 1
1150
+ };
574
1151
  }
575
1152
  }
1153
+ function createDefaultCommands(sandboxRef) {
1154
+ return [createSandlotCommand(sandboxRef)];
1155
+ }
1156
+
1157
+ // src/core/sandbox.ts
1158
+ var DEFAULT_ENTRY_POINT = "./index.ts";
1159
+ var TSCONFIG_PATH = "/tsconfig.json";
576
1160
  var PACKAGE_JSON_PATH = "/package.json";
1161
+ var DEFAULT_PACKAGE_JSON = {
1162
+ main: DEFAULT_ENTRY_POINT,
1163
+ dependencies: {}
1164
+ };
1165
+ var DEFAULT_TSCONFIG = {
1166
+ compilerOptions: {
1167
+ target: "ES2020",
1168
+ lib: ["ES2020", "DOM", "DOM.Iterable"],
1169
+ module: "ESNext",
1170
+ moduleResolution: "bundler",
1171
+ jsx: "react-jsx",
1172
+ strict: true,
1173
+ noEmit: true,
1174
+ esModuleInterop: true,
1175
+ skipLibCheck: true,
1176
+ resolveJsonModule: true,
1177
+ isolatedModules: true
1178
+ },
1179
+ include: ["**/*.ts", "**/*.tsx"],
1180
+ exclude: ["node_modules"]
1181
+ };
577
1182
  function parsePackageSpec(spec) {
578
1183
  if (spec.startsWith("@")) {
579
1184
  const slashIndex = spec.indexOf("/");
@@ -599,1755 +1204,361 @@ function parsePackageSpec(spec) {
599
1204
  version: spec.slice(atIndex + 1)
600
1205
  };
601
1206
  }
602
- function isTypesPackage(name) {
603
- return name.startsWith("@types/");
604
- }
605
- function extractVersionFromUrl(url, packageName) {
606
- const exactRegex = new RegExp(`${escapeRegExp(packageName)}@([^/]+)`);
607
- const exactMatch = url.match(exactRegex);
608
- if (exactMatch?.[1]) {
609
- return exactMatch[1];
610
- }
611
- if (packageName.startsWith("@")) {
612
- const scopedParts = packageName.split("/");
613
- if (scopedParts.length === 2 && scopedParts[1]) {
614
- const partialRegex = new RegExp(`${escapeRegExp(scopedParts[1])}@([^/]+)`);
615
- const partialMatch = url.match(partialRegex);
616
- if (partialMatch?.[1]) {
617
- return partialMatch[1];
618
- }
619
- }
620
- }
621
- const genericMatch = url.match(/@(\d+\.\d+\.\d+[^/]*)/);
622
- if (genericMatch?.[1]) {
623
- return genericMatch[1];
624
- }
625
- return null;
626
- }
627
- async function fetchVersionFromNpm(name) {
628
- const registryUrl = `https://registry.npmjs.org/${name}/latest`;
629
- const response = await fetch(registryUrl);
630
- if (!response.ok) {
631
- throw new Error(`Failed to fetch version from npm: ${response.status}`);
632
- }
633
- const data = await response.json();
634
- return data.version;
635
- }
636
- function extractVersionFromHeaders(headers, packageName) {
637
- const esmId = headers.get("x-esm-id");
638
- if (esmId) {
639
- const version = extractVersionFromUrl(esmId, packageName);
640
- if (version)
641
- return version;
642
- }
643
- const typesHeader = headers.get("X-TypeScript-Types");
644
- if (typesHeader) {
645
- const versionMatch = typesHeader.match(/@(\d+\.\d+\.\d+[^/]*)/);
646
- if (versionMatch?.[1]) {
647
- return versionMatch[1];
648
- }
649
- }
650
- return null;
651
- }
652
- async function fetchPackageInfo(name, version, subpath) {
653
- let url = version ? `${ESM_CDN_BASE}/${name}@${version}` : `${ESM_CDN_BASE}/${name}`;
654
- if (subpath) {
655
- url += `/${subpath}`;
656
- }
657
- const response = await fetch(url, { method: "HEAD" });
658
- if (!response.ok) {
659
- throw new Error(`Package not found: ${name}${version ? `@${version}` : ""}${subpath ? `/${subpath}` : ""}`);
660
- }
661
- const resolvedUrl = response.url;
662
- let resolvedVersion = extractVersionFromUrl(resolvedUrl, name);
663
- if (!resolvedVersion) {
664
- resolvedVersion = extractVersionFromHeaders(response.headers, name);
665
- }
666
- if (!resolvedVersion && version && version !== "latest") {
667
- resolvedVersion = version;
668
- }
669
- if (!resolvedVersion) {
670
- try {
671
- resolvedVersion = await fetchVersionFromNpm(name);
672
- } catch (err) {
673
- console.warn(`Could not resolve version for ${name}:`, err);
674
- resolvedVersion = "latest";
675
- }
676
- }
677
- const typesUrl = response.headers.get("X-TypeScript-Types") ?? undefined;
678
- return {
679
- version: resolvedVersion,
680
- typesUrl: typesUrl ? new URL(typesUrl, resolvedUrl).href : undefined
681
- };
682
- }
683
- function escapeRegExp(string) {
684
- return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
685
- }
686
- async function fetchTypeDefinitions(typesUrl, packageName) {
687
- const types = new Map;
1207
+ function readPackageJson(fs) {
688
1208
  try {
689
- const response = await fetch(typesUrl);
690
- if (!response.ok) {
691
- throw new Error(`Failed to fetch types: ${response.status}`);
1209
+ if (fs.exists(PACKAGE_JSON_PATH)) {
1210
+ const content = fs.readFile(PACKAGE_JSON_PATH);
1211
+ return JSON.parse(content);
692
1212
  }
693
- const content = await response.text();
694
- const typePath = `/node_modules/${packageName}/index.d.ts`;
695
- types.set(typePath, content);
696
- const refs = parseTypeReferences(content);
697
- await fetchReferencedTypes(refs, typesUrl, packageName, types);
698
- } catch (err) {
699
- console.warn(`Failed to fetch types for ${packageName}:`, err);
700
- throw err;
701
- }
702
- return types;
703
- }
704
- function parseTypeReferences(content) {
705
- const paths = [];
706
- const types = [];
707
- const pathRegex = /\/\/\/\s*<reference\s+path="([^"]+)"\s*\/>/g;
708
- let match;
709
- while ((match = pathRegex.exec(content)) !== null) {
710
- if (match[1])
711
- paths.push(match[1]);
712
- }
713
- const typesRegex = /\/\/\/\s*<reference\s+types="([^"]+)"\s*\/>/g;
714
- while ((match = typesRegex.exec(content)) !== null) {
715
- if (match[1])
716
- types.push(match[1]);
717
- }
718
- return { paths, types };
719
- }
720
- async function fetchReferencedTypes(refs, baseUrl, packageName, collected, visited = new Set) {
721
- for (const pathRef of refs.paths) {
722
- const refUrl = new URL(pathRef, baseUrl).href;
723
- if (visited.has(refUrl))
724
- continue;
725
- visited.add(refUrl);
726
- try {
727
- const response = await fetch(refUrl);
728
- if (!response.ok)
729
- continue;
730
- const content = await response.text();
731
- const fileName = pathRef.split("/").pop() ?? "types.d.ts";
732
- const typePath = `/node_modules/${packageName}/${fileName}`;
733
- collected.set(typePath, content);
734
- const nestedRefs = parseTypeReferences(content);
735
- await fetchReferencedTypes(nestedRefs, refUrl, packageName, collected, visited);
736
- } catch {}
737
- }
1213
+ } catch {}
1214
+ return {};
738
1215
  }
739
- async function fetchSubpathTypes(packageName, subpath, version) {
740
- const types = new Map;
741
- try {
742
- const info = await fetchPackageInfo(packageName, version, subpath);
743
- if (!info.typesUrl) {
744
- return types;
745
- }
746
- const response = await fetch(info.typesUrl);
747
- if (!response.ok) {
748
- return types;
749
- }
750
- const content = await response.text();
751
- const typePath = `/node_modules/${packageName}/${subpath}.d.ts`;
752
- types.set(typePath, content);
753
- const indexTypePath = `/node_modules/${packageName}/${subpath}/index.d.ts`;
754
- types.set(indexTypePath, content);
755
- const refs = parseTypeReferences(content);
756
- await fetchReferencedTypes(refs, info.typesUrl, packageName, types);
757
- } catch (err) {
758
- console.warn(`Failed to fetch types for ${packageName}/${subpath}:`, err);
1216
+ function getEntryPoint(fs) {
1217
+ const pkg = readPackageJson(fs);
1218
+ const main = pkg.main ?? DEFAULT_ENTRY_POINT;
1219
+ if (main.startsWith("/")) {
1220
+ return main;
759
1221
  }
760
- return types;
761
- }
762
- async function fetchTypesPackageContent(name, version) {
763
- const url = version ? `${ESM_CDN_BASE}/${name}@${version}` : `${ESM_CDN_BASE}/${name}`;
764
- const headResponse = await fetch(url, { method: "HEAD" });
765
- if (!headResponse.ok) {
766
- throw new Error(`Package not found: ${name}${version ? `@${version}` : ""}`);
767
- }
768
- const resolvedVersion = extractVersionFromUrl(headResponse.url, name) ?? version ?? "latest";
769
- const indexUrl = `${ESM_CDN_BASE}/${name}@${resolvedVersion}/index.d.ts`;
770
- const response = await fetch(indexUrl);
771
- if (!response.ok) {
772
- throw new Error(`Failed to fetch types from ${name}: ${response.status}`);
773
- }
774
- const content = await response.text();
775
- const types = new Map;
776
- const typePath = `/node_modules/${name}/index.d.ts`;
777
- types.set(typePath, content);
778
- const refs = parseTypeReferences(content);
779
- for (const pathRef of refs.paths) {
780
- try {
781
- const refUrl = new URL(pathRef, indexUrl).href;
782
- const refResponse = await fetch(refUrl);
783
- if (refResponse.ok) {
784
- const refContent = await refResponse.text();
785
- const fileName = pathRef.startsWith("./") ? pathRef.slice(2) : pathRef;
786
- const refTypePath = `/node_modules/${name}/${fileName}`;
787
- types.set(refTypePath, refContent);
788
- }
789
- } catch {}
1222
+ if (main.startsWith("./")) {
1223
+ return "/" + main.slice(2);
790
1224
  }
791
- return { version: resolvedVersion, types };
1225
+ return "/" + main;
792
1226
  }
793
- async function getPackageManifest(fs) {
794
- try {
795
- if (await fs.exists(PACKAGE_JSON_PATH)) {
796
- const content = await fs.readFile(PACKAGE_JSON_PATH);
797
- const parsed = JSON.parse(content);
798
- return {
799
- dependencies: parsed.dependencies ?? {}
800
- };
801
- }
802
- } catch {}
803
- return { dependencies: {} };
1227
+ function getInstalledPackages(fs) {
1228
+ const pkg = readPackageJson(fs);
1229
+ return pkg.dependencies ?? {};
804
1230
  }
805
- async function savePackageManifest(fs, manifest) {
1231
+ function saveInstalledPackages(fs, dependencies) {
806
1232
  let existing = {};
807
1233
  try {
808
- if (await fs.exists(PACKAGE_JSON_PATH)) {
809
- const content = await fs.readFile(PACKAGE_JSON_PATH);
1234
+ if (fs.exists(PACKAGE_JSON_PATH)) {
1235
+ const content = fs.readFile(PACKAGE_JSON_PATH);
810
1236
  existing = JSON.parse(content);
811
1237
  }
812
1238
  } catch {}
813
1239
  const updated = {
814
1240
  ...existing,
815
- dependencies: manifest.dependencies
816
- };
817
- await fs.writeFile(PACKAGE_JSON_PATH, JSON.stringify(updated, null, 2));
818
- }
819
- async function installPackage(fs, packageSpec, options) {
820
- const { name, version } = parsePackageSpec(packageSpec);
821
- const { cache } = options ?? {};
822
- if (isTypesPackage(name)) {
823
- return installTypesPackage(fs, name, version, cache);
824
- }
825
- const info = await fetchPackageInfo(name, version);
826
- const packageDir = `/node_modules/${name}`;
827
- await ensureDir(fs, packageDir);
828
- const packageJsonPath = `${packageDir}/package.json`;
829
- const packageJson = {
830
- name,
831
- version: info.version,
832
- types: "./index.d.ts",
833
- main: "./index.js"
834
- };
835
- await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
836
- let typeFiles = null;
837
- let fromCache = false;
838
- if (cache) {
839
- typeFiles = cache.get(name, info.version);
840
- if (typeFiles) {
841
- fromCache = true;
842
- }
843
- }
844
- let typesError;
845
- if (!typeFiles) {
846
- typeFiles = new Map;
847
- if (info.typesUrl) {
848
- try {
849
- const mainTypes = await fetchTypeDefinitions(info.typesUrl, name);
850
- for (const [path, content] of mainTypes) {
851
- typeFiles.set(path, content);
852
- }
853
- } catch (err) {
854
- typesError = err instanceof Error ? err.message : String(err);
855
- }
856
- } else {
857
- typesError = "No TypeScript types available from esm.sh";
858
- }
859
- const knownSubpaths = KNOWN_SUBPATHS[name];
860
- if (knownSubpaths && knownSubpaths.length > 0) {
861
- const subpathResults = await Promise.allSettled(knownSubpaths.map((subpath) => fetchSubpathTypes(name, subpath, info.version)));
862
- for (const result of subpathResults) {
863
- if (result.status === "fulfilled") {
864
- for (const [path, content] of result.value) {
865
- typeFiles.set(path, content);
866
- }
867
- }
868
- }
869
- }
870
- if (cache && typeFiles.size > 0) {
871
- cache.set(name, info.version, typeFiles);
872
- }
873
- }
874
- for (const [path, content] of typeFiles) {
875
- const dir = path.substring(0, path.lastIndexOf("/"));
876
- await ensureDir(fs, dir);
877
- await fs.writeFile(path, content);
878
- }
879
- const manifest = await getPackageManifest(fs);
880
- manifest.dependencies[name] = info.version;
881
- await savePackageManifest(fs, manifest);
882
- return {
883
- name,
884
- version: info.version,
885
- typesInstalled: typeFiles.size > 0,
886
- typeFilesCount: typeFiles.size,
887
- typesError,
888
- fromCache
889
- };
890
- }
891
- async function installTypesPackage(fs, name, version, cache) {
892
- const url = version ? `${ESM_CDN_BASE}/${name}@${version}` : `${ESM_CDN_BASE}/${name}`;
893
- const headResponse = await fetch(url, { method: "HEAD" });
894
- if (!headResponse.ok) {
895
- throw new Error(`Package not found: ${name}${version ? `@${version}` : ""}`);
896
- }
897
- const resolvedVersion = extractVersionFromUrl(headResponse.url, name) ?? version ?? "latest";
898
- let types = null;
899
- let fromCache = false;
900
- if (cache) {
901
- types = cache.get(name, resolvedVersion);
902
- if (types) {
903
- fromCache = true;
904
- }
905
- }
906
- if (!types) {
907
- const result = await fetchTypesPackageContent(name, version);
908
- types = result.types;
909
- if (cache && types.size > 0) {
910
- cache.set(name, resolvedVersion, types);
911
- }
912
- }
913
- const packageDir = `/node_modules/${name}`;
914
- await ensureDir(fs, packageDir);
915
- const packageJsonPath = `${packageDir}/package.json`;
916
- const packageJson = {
917
- name,
918
- version: resolvedVersion,
919
- types: "./index.d.ts"
920
- };
921
- await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
922
- for (const [path, content] of types) {
923
- const dir = path.substring(0, path.lastIndexOf("/"));
924
- await ensureDir(fs, dir);
925
- await fs.writeFile(path, content);
926
- }
927
- const manifest = await getPackageManifest(fs);
928
- manifest.dependencies[name] = resolvedVersion;
929
- await savePackageManifest(fs, manifest);
930
- return {
931
- name,
932
- version: resolvedVersion,
933
- typesInstalled: types.size > 0,
934
- typeFilesCount: types.size,
935
- fromCache
1241
+ dependencies
936
1242
  };
1243
+ fs.writeFile(PACKAGE_JSON_PATH, JSON.stringify(updated, null, 2));
937
1244
  }
938
- async function ensureDir(fs, path) {
1245
+ function ensureDir(fs, path) {
939
1246
  if (path === "/" || path === "")
940
1247
  return;
941
- if (await fs.exists(path)) {
942
- const stat = await fs.stat(path);
1248
+ if (fs.exists(path)) {
1249
+ const stat = fs.stat(path);
943
1250
  if (stat.isDirectory)
944
1251
  return;
945
1252
  }
946
1253
  const parent = path.substring(0, path.lastIndexOf("/")) || "/";
947
- await ensureDir(fs, parent);
948
- await fs.mkdir(path);
949
- }
950
- async function uninstallPackage(fs, packageName) {
951
- const manifest = await getPackageManifest(fs);
952
- if (!(packageName in manifest.dependencies)) {
953
- return false;
954
- }
955
- delete manifest.dependencies[packageName];
956
- await savePackageManifest(fs, manifest);
957
- const typesPath = `/node_modules/${packageName}`;
958
- if (await fs.exists(typesPath)) {
959
- await removePath(fs, typesPath);
960
- }
961
- return true;
962
- }
963
- async function removePath(fs, path) {
964
- if (!await fs.exists(path))
965
- return;
966
- await fs.rm(path, { recursive: true, force: true });
967
- }
968
- function resolveToEsmUrl(importPath, installedPackages) {
969
- const { packageName, subpath } = parseImportPath(importPath);
970
- const version = installedPackages[packageName];
971
- if (!version) {
972
- return null;
973
- }
974
- const baseUrl = `${ESM_CDN_BASE}/${packageName}@${version}`;
975
- return subpath ? `${baseUrl}/${subpath}` : baseUrl;
1254
+ ensureDir(fs, parent);
1255
+ fs.mkdir(path);
976
1256
  }
977
- function parseImportPath(importPath) {
978
- if (importPath.startsWith("@")) {
979
- const parts = importPath.split("/");
980
- if (parts.length >= 2) {
981
- const packageName = `${parts[0]}/${parts[1]}`;
982
- const subpath = parts.slice(2).join("/") || undefined;
983
- return { packageName, subpath };
1257
+ async function createSandboxImpl(fs, options, context) {
1258
+ const {
1259
+ bundler,
1260
+ typechecker,
1261
+ typesResolver,
1262
+ sharedModuleRegistry,
1263
+ executor
1264
+ } = context;
1265
+ let lastBuild = null;
1266
+ const onBuildCallbacks = new Set;
1267
+ if (options.onBuild) {
1268
+ onBuildCallbacks.add(options.onBuild);
1269
+ }
1270
+ if (options.initialFiles) {
1271
+ for (const [path, content] of Object.entries(options.initialFiles)) {
1272
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
1273
+ const dir = normalizedPath.substring(0, normalizedPath.lastIndexOf("/"));
1274
+ if (dir && dir !== "/") {
1275
+ ensureDir(fs, dir);
1276
+ }
1277
+ fs.writeFile(normalizedPath, content);
1278
+ }
1279
+ }
1280
+ if (!fs.exists(PACKAGE_JSON_PATH)) {
1281
+ fs.writeFile(PACKAGE_JSON_PATH, JSON.stringify(DEFAULT_PACKAGE_JSON, null, 2));
1282
+ }
1283
+ if (!fs.exists(TSCONFIG_PATH)) {
1284
+ fs.writeFile(TSCONFIG_PATH, JSON.stringify(DEFAULT_TSCONFIG, null, 2));
1285
+ }
1286
+ async function install(packageSpec) {
1287
+ const { name, version } = parsePackageSpec(packageSpec);
1288
+ let resolvedVersion = version ?? "latest";
1289
+ let typesInstalled = false;
1290
+ let typeFilesCount = 0;
1291
+ let typesError;
1292
+ const fromCache = false;
1293
+ if (typesResolver) {
1294
+ try {
1295
+ const typeFiles = await typesResolver.resolveTypes(name, version);
1296
+ const packageDir = `/node_modules/${name}`;
1297
+ ensureDir(fs, packageDir);
1298
+ let typesEntry = "index.d.ts";
1299
+ for (const [filePath, content] of Object.entries(typeFiles)) {
1300
+ const fullPath = filePath.startsWith("/") ? filePath : `${packageDir}/${filePath}`;
1301
+ const dir = fullPath.substring(0, fullPath.lastIndexOf("/"));
1302
+ ensureDir(fs, dir);
1303
+ fs.writeFile(fullPath, content);
1304
+ typeFilesCount++;
1305
+ const relativePath = fullPath.replace(`${packageDir}/`, "");
1306
+ if (relativePath === "index.d.ts") {
1307
+ typesEntry = "index.d.ts";
1308
+ } else if (typesEntry === "index.d.ts" && relativePath.endsWith(".d.ts") && !relativePath.includes("/")) {
1309
+ typesEntry = relativePath;
1310
+ }
1311
+ }
1312
+ typesInstalled = typeFilesCount > 0;
1313
+ if (typesInstalled) {
1314
+ const pkgJsonPath = `${packageDir}/package.json`;
1315
+ const pkgJson = {
1316
+ name,
1317
+ version: resolvedVersion,
1318
+ types: typesEntry,
1319
+ main: typesEntry.replace(/\.d\.ts$/, ".js")
1320
+ };
1321
+ fs.writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
1322
+ }
1323
+ if (!version) {
1324
+ resolvedVersion = "latest";
1325
+ }
1326
+ } catch (err) {
1327
+ typesError = err instanceof Error ? err.message : String(err);
1328
+ }
984
1329
  }
985
- return { packageName: importPath };
986
- }
987
- const slashIndex = importPath.indexOf("/");
988
- if (slashIndex === -1) {
989
- return { packageName: importPath };
990
- }
991
- return {
992
- packageName: importPath.slice(0, slashIndex),
993
- subpath: importPath.slice(slashIndex + 1)
994
- };
995
- }
996
- async function listPackages(fs) {
997
- const manifest = await getPackageManifest(fs);
998
- return Object.entries(manifest.dependencies).map(([name, version]) => ({
999
- name,
1000
- version
1001
- }));
1002
- }
1003
-
1004
- // src/shared-modules.ts
1005
- var GLOBAL_KEY = "__sandlot_shared_modules__";
1006
-
1007
- class SharedModuleRegistry {
1008
- modules = new Map;
1009
- exportNames = new Map;
1010
- constructor() {
1011
- globalThis[GLOBAL_KEY] = this;
1012
- }
1013
- register(moduleId, module) {
1014
- this.modules.set(moduleId, module);
1015
- this.exportNames.set(moduleId, introspectExports(module));
1016
- return this;
1330
+ const dependencies = getInstalledPackages(fs);
1331
+ dependencies[name] = resolvedVersion;
1332
+ saveInstalledPackages(fs, dependencies);
1333
+ return {
1334
+ name,
1335
+ version: resolvedVersion,
1336
+ typesInstalled,
1337
+ typeFilesCount,
1338
+ typesError,
1339
+ fromCache
1340
+ };
1017
1341
  }
1018
- registerAll(modules) {
1019
- for (const [id, mod] of Object.entries(modules)) {
1020
- this.register(id, mod);
1342
+ async function uninstall(packageName) {
1343
+ const dependencies = getInstalledPackages(fs);
1344
+ if (!(packageName in dependencies)) {
1345
+ return { name: packageName, removed: false };
1346
+ }
1347
+ delete dependencies[packageName];
1348
+ saveInstalledPackages(fs, dependencies);
1349
+ const typesPath = `/node_modules/${packageName}`;
1350
+ if (fs.exists(typesPath)) {
1351
+ fs.rm(typesPath, { recursive: true, force: true });
1352
+ }
1353
+ return { name: packageName, removed: true };
1354
+ }
1355
+ async function build(buildOptions) {
1356
+ const buildEntryPoint = buildOptions?.entryPoint ?? getEntryPoint(fs);
1357
+ const skipTypecheck = buildOptions?.skipTypecheck ?? false;
1358
+ const minify = buildOptions?.minify ?? false;
1359
+ const format = buildOptions?.format ?? "esm";
1360
+ if (!fs.exists(buildEntryPoint)) {
1361
+ return {
1362
+ success: false,
1363
+ phase: "entry",
1364
+ message: `Entry point not found: ${buildEntryPoint}`
1365
+ };
1021
1366
  }
1022
- return this;
1023
- }
1024
- unregister(moduleId) {
1025
- return this.modules.delete(moduleId);
1026
- }
1027
- get(moduleId) {
1028
- const mod = this.modules.get(moduleId);
1029
- if (mod === undefined && !this.modules.has(moduleId)) {
1030
- const available = this.list();
1031
- throw new Error(`Shared module "${moduleId}" not registered. ` + `Available: ${available.length > 0 ? available.join(", ") : "(none)"}. ` + `Call registerSharedModules({ '${moduleId}': ... }) in your host application.`);
1367
+ if (!skipTypecheck && typechecker) {
1368
+ const typecheckResult = await typechecker.typecheck({
1369
+ fs,
1370
+ entryPoint: buildEntryPoint,
1371
+ tsconfigPath: TSCONFIG_PATH
1372
+ });
1373
+ if (!typecheckResult.success) {
1374
+ return {
1375
+ success: false,
1376
+ phase: "typecheck",
1377
+ diagnostics: typecheckResult.diagnostics
1378
+ };
1379
+ }
1032
1380
  }
1033
- return mod;
1034
- }
1035
- has(moduleId) {
1036
- return this.modules.has(moduleId);
1037
- }
1038
- list() {
1039
- return [...this.modules.keys()];
1040
- }
1041
- getExportNames(moduleId) {
1042
- return this.exportNames.get(moduleId) ?? [];
1043
- }
1044
- clear() {
1045
- this.modules.clear();
1046
- this.exportNames.clear();
1047
- }
1048
- get size() {
1049
- return this.modules.size;
1050
- }
1051
- }
1052
- function introspectExports(module) {
1053
- if (module === null || module === undefined) {
1054
- return [];
1055
- }
1056
- if (typeof module !== "object" && typeof module !== "function") {
1057
- return [];
1058
- }
1059
- const exports = [];
1060
- for (const key of Object.keys(module)) {
1061
- if (isValidIdentifier(key)) {
1062
- exports.push(key);
1381
+ const installedPackages = getInstalledPackages(fs);
1382
+ const bundleResult = await bundler.bundle({
1383
+ fs,
1384
+ entryPoint: buildEntryPoint,
1385
+ installedPackages,
1386
+ sharedModules: sharedModuleRegistry?.list() ?? [],
1387
+ sharedModuleRegistry: sharedModuleRegistry ?? undefined,
1388
+ format,
1389
+ minify
1390
+ });
1391
+ if (!bundleResult.success) {
1392
+ return {
1393
+ success: false,
1394
+ phase: "bundle",
1395
+ bundleErrors: bundleResult.errors,
1396
+ bundleWarnings: bundleResult.warnings
1397
+ };
1063
1398
  }
1064
- }
1065
- return exports;
1066
- }
1067
- function isValidIdentifier(name) {
1068
- if (name.length === 0)
1069
- return false;
1070
- if (!/^[a-zA-Z_$]/.test(name))
1071
- return false;
1072
- if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name))
1073
- return false;
1074
- const reserved = ["default", "class", "function", "var", "let", "const", "import", "export"];
1075
- if (reserved.includes(name))
1076
- return false;
1077
- return true;
1078
- }
1079
- var defaultRegistry = null;
1080
- function getSharedModuleRegistry() {
1081
- if (!defaultRegistry) {
1082
- defaultRegistry = new SharedModuleRegistry;
1083
- }
1084
- return defaultRegistry;
1085
- }
1086
- function hasSharedModuleRegistry() {
1087
- return GLOBAL_KEY in globalThis;
1088
- }
1089
- function registerSharedModules(modules) {
1090
- getSharedModuleRegistry().registerAll(modules);
1091
- }
1092
- function unregisterSharedModule(moduleId) {
1093
- return getSharedModuleRegistry().unregister(moduleId);
1094
- }
1095
- function clearSharedModules() {
1096
- getSharedModuleRegistry().clear();
1097
- }
1098
- function getSharedModuleExports(moduleId) {
1099
- return getSharedModuleRegistry().getExportNames(moduleId);
1100
- }
1101
- function getSharedModuleRuntimeCode(moduleId) {
1102
- return `
1103
- (function() {
1104
- var registry = globalThis["${GLOBAL_KEY}"];
1105
- if (!registry) {
1106
- throw new Error(
1107
- 'Sandlot SharedModuleRegistry not found. ' +
1108
- 'Call registerSharedModules() in your host application before loading dynamic modules.'
1109
- );
1110
- }
1111
- return registry.get(${JSON.stringify(moduleId)});
1112
- })()
1113
- `.trim();
1114
- }
1115
-
1116
- // src/bundler.ts
1117
- var esbuild = null;
1118
- function isServerEnvironment() {
1119
- if (typeof globalThis !== "undefined" && "Bun" in globalThis) {
1120
- return true;
1121
- }
1122
- if (typeof globalThis !== "undefined" && "Deno" in globalThis) {
1123
- return true;
1124
- }
1125
- if (typeof process !== "undefined" && process.versions?.node) {
1126
- return true;
1127
- }
1128
- return false;
1129
- }
1130
- async function getEsbuild() {
1131
- if (esbuild)
1132
- return esbuild;
1133
- if (isServerEnvironment()) {
1134
- const mod = await import("esbuild");
1135
- esbuild = mod.default ?? mod;
1136
- } else {
1137
- const cdnUrl = `https://esm.sh/esbuild-wasm@${ESBUILD_VERSION}`;
1138
- const mod = await import(cdnUrl);
1139
- esbuild = mod.default ?? mod;
1140
- if (typeof esbuild?.initialize !== "function") {
1141
- console.error("esbuild-wasm module structure:", mod);
1142
- throw new Error("Failed to load esbuild-wasm: initialize function not found");
1143
- }
1144
- }
1145
- return esbuild;
1146
- }
1147
- var ESBUILD_VERSION = "0.27.2";
1148
- var initialized = false;
1149
- var initPromise = null;
1150
- function getWasmUrl() {
1151
- return `https://unpkg.com/esbuild-wasm@${ESBUILD_VERSION}/esbuild.wasm`;
1152
- }
1153
- function checkCrossOriginIsolation() {
1154
- if (typeof window === "undefined")
1155
- return;
1156
- if (!window.crossOriginIsolated) {
1157
- console.warn(`[sandlot] Cross-origin isolation is not enabled. esbuild-wasm may have reduced performance or fail on some browsers.
1158
- To enable, add these headers to your dev server:
1159
- Cross-Origin-Embedder-Policy: require-corp
1160
- Cross-Origin-Opener-Policy: same-origin
1161
- In Vite, add a plugin to configureServer. See sandlot README for details.`);
1162
- }
1163
- }
1164
- async function initBundler() {
1165
- if (initialized)
1166
- return;
1167
- if (initPromise) {
1168
- await initPromise;
1169
- return;
1170
- }
1171
- initPromise = (async () => {
1172
- const es = await getEsbuild();
1173
- if (!isServerEnvironment() && typeof es.initialize === "function") {
1174
- checkCrossOriginIsolation();
1175
- await es.initialize({
1176
- wasmURL: getWasmUrl()
1177
- });
1399
+ const output = {
1400
+ success: true,
1401
+ code: bundleResult.code,
1402
+ includedFiles: bundleResult.includedFiles,
1403
+ warnings: bundleResult.warnings
1404
+ };
1405
+ lastBuild = output;
1406
+ for (const callback of onBuildCallbacks) {
1407
+ try {
1408
+ await callback(output);
1409
+ } catch (err) {
1410
+ console.error("[sandlot] onBuild callback error:", err);
1411
+ }
1178
1412
  }
1179
- })();
1180
- await initPromise;
1181
- initialized = true;
1182
- }
1183
- function isBareImport(path) {
1184
- return !path.startsWith(".") && !path.startsWith("/");
1185
- }
1186
- function getLoader(path) {
1187
- const ext = path.split(".").pop()?.toLowerCase();
1188
- switch (ext) {
1189
- case "ts":
1190
- return "ts";
1191
- case "tsx":
1192
- return "tsx";
1193
- case "jsx":
1194
- return "jsx";
1195
- case "js":
1196
- case "mjs":
1197
- return "js";
1198
- case "json":
1199
- return "json";
1200
- case "css":
1201
- return "css";
1202
- case "txt":
1203
- return "text";
1204
- default:
1205
- return "js";
1206
- }
1207
- }
1208
- function matchesSharedModule(importPath, sharedModuleIds) {
1209
- if (sharedModuleIds.has(importPath)) {
1210
- return importPath;
1413
+ return output;
1211
1414
  }
1212
- for (const moduleId of sharedModuleIds) {
1213
- if (importPath === moduleId || importPath.startsWith(moduleId + "/")) {
1214
- return importPath;
1415
+ async function typecheck(typecheckOptions) {
1416
+ if (!typechecker) {
1417
+ return { success: true, diagnostics: [] };
1215
1418
  }
1216
- }
1217
- return null;
1218
- }
1219
- function createVfsPlugin(options) {
1220
- const {
1221
- fs,
1222
- entryPoint,
1223
- npmImports,
1224
- installedPackages,
1225
- includedFiles,
1226
- sharedModuleIds
1227
- } = options;
1228
- return {
1229
- name: "virtual-fs",
1230
- setup(build) {
1231
- build.onResolve({ filter: /.*/ }, async (args) => {
1232
- if (args.kind === "entry-point") {
1233
- return { path: entryPoint, namespace: "vfs" };
1234
- }
1235
- if (isBareImport(args.path)) {
1236
- const sharedMatch = matchesSharedModule(args.path, sharedModuleIds);
1237
- if (sharedMatch) {
1238
- return {
1239
- path: sharedMatch,
1240
- namespace: "sandlot-shared"
1241
- };
1242
- }
1243
- switch (npmImports) {
1244
- case "cdn": {
1245
- const esmUrl = resolveToEsmUrl(args.path, installedPackages);
1246
- if (esmUrl) {
1247
- return { path: esmUrl, external: true };
1248
- }
1249
- return { path: args.path, external: true };
1250
- }
1251
- case "external":
1252
- return { path: args.path, external: true };
1253
- case "bundle": {
1254
- const resolved2 = fs.resolvePath(args.resolveDir, `node_modules/${args.path}`);
1255
- const exists = await fs.exists(resolved2);
1256
- if (exists) {
1257
- return { path: resolved2, namespace: "vfs" };
1258
- }
1259
- return { path: args.path, external: true };
1260
- }
1261
- }
1262
- }
1263
- const resolved = fs.resolvePath(args.resolveDir, args.path);
1264
- const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".json"];
1265
- const hasExtension = extensions.some((ext) => resolved.endsWith(ext));
1266
- if (hasExtension) {
1267
- const exists = await fs.exists(resolved);
1268
- if (exists) {
1269
- return { path: resolved, namespace: "vfs" };
1270
- }
1271
- return { errors: [{ text: `File not found: ${resolved}` }] };
1272
- }
1273
- for (const ext of extensions) {
1274
- const withExt = resolved + ext;
1275
- if (await fs.exists(withExt)) {
1276
- return { path: withExt, namespace: "vfs" };
1277
- }
1278
- }
1279
- for (const ext of extensions) {
1280
- const indexPath = `${resolved}/index${ext}`;
1281
- if (await fs.exists(indexPath)) {
1282
- return { path: indexPath, namespace: "vfs" };
1419
+ const checkEntryPoint = typecheckOptions?.entryPoint ?? getEntryPoint(fs);
1420
+ if (!fs.exists(checkEntryPoint)) {
1421
+ return {
1422
+ success: false,
1423
+ diagnostics: [
1424
+ {
1425
+ message: `Entry point not found: ${checkEntryPoint}`,
1426
+ severity: "error"
1283
1427
  }
1428
+ ]
1429
+ };
1430
+ }
1431
+ return typechecker.typecheck({
1432
+ fs,
1433
+ entryPoint: checkEntryPoint,
1434
+ tsconfigPath: TSCONFIG_PATH
1435
+ });
1436
+ }
1437
+ async function run(runOptions) {
1438
+ if (!executor) {
1439
+ throw new Error("[sandlot] No executor configured. Provide an executor when creating Sandlot to use run().");
1440
+ }
1441
+ const buildResult = await build({
1442
+ entryPoint: runOptions?.entryPoint,
1443
+ skipTypecheck: runOptions?.skipTypecheck
1444
+ });
1445
+ if (!buildResult.success) {
1446
+ return {
1447
+ success: false,
1448
+ logs: [],
1449
+ error: buildResult.message ?? `Build failed in ${buildResult.phase} phase`,
1450
+ buildFailure: {
1451
+ phase: buildResult.phase,
1452
+ message: buildResult.message,
1453
+ diagnostics: buildResult.diagnostics,
1454
+ bundleErrors: buildResult.bundleErrors,
1455
+ bundleWarnings: buildResult.bundleWarnings
1284
1456
  }
1285
- return { errors: [{ text: `Cannot resolve: ${args.path} from ${args.resolveDir}` }] };
1286
- });
1287
- build.onLoad({ filter: /.*/, namespace: "sandlot-shared" }, (args) => {
1288
- const contents = `
1289
- const __sandlot_mod__ = ${getSharedModuleRuntimeCode(args.path)};
1290
- export default __sandlot_mod__.default ?? __sandlot_mod__;
1291
- ${generateNamedExports(args.path)}
1292
- `;
1293
- return {
1294
- contents: contents.trim(),
1295
- loader: "js"
1296
- };
1297
- });
1298
- build.onLoad({ filter: /.*/, namespace: "vfs" }, async (args) => {
1299
- try {
1300
- const contents = await fs.readFile(args.path);
1301
- includedFiles.add(args.path);
1302
- return {
1303
- contents,
1304
- loader: getLoader(args.path),
1305
- resolveDir: args.path.substring(0, args.path.lastIndexOf("/"))
1306
- };
1307
- } catch (err) {
1308
- return {
1309
- errors: [{ text: `Failed to read ${args.path}: ${err}` }]
1310
- };
1311
- }
1312
- });
1457
+ };
1313
1458
  }
1314
- };
1315
- }
1316
- function generateNamedExports(moduleId) {
1317
- const exports = getSharedModuleExports(moduleId);
1318
- if (exports.length > 0) {
1319
- return exports.map((name) => `export const ${name} = __sandlot_mod__.${name};`).join(`
1320
- `);
1459
+ const executeResult = await executor.execute(buildResult.code, {
1460
+ entryExport: runOptions?.entryExport ?? "main",
1461
+ context: runOptions?.context,
1462
+ timeout: runOptions?.timeout
1463
+ });
1464
+ return {
1465
+ success: executeResult.success,
1466
+ logs: executeResult.logs,
1467
+ returnValue: executeResult.returnValue,
1468
+ error: executeResult.error,
1469
+ executionTimeMs: executeResult.executionTimeMs
1470
+ };
1321
1471
  }
1322
- return `// No exports discovered for "${moduleId}" - use default import or call registerSharedModules() first`;
1323
- }
1324
- async function bundle(options) {
1325
- await initBundler();
1326
- const {
1327
- fs,
1328
- entryPoint,
1329
- external = [],
1330
- npmImports = "cdn",
1331
- sharedModules = [],
1332
- format = "esm",
1333
- minify = false,
1334
- sourcemap = false,
1335
- globalName,
1336
- target = ["es2020"]
1337
- } = options;
1338
- const normalizedEntry = entryPoint.startsWith("/") ? entryPoint : `/${entryPoint}`;
1339
- if (!await fs.exists(normalizedEntry)) {
1340
- throw new Error(`Entry point not found: ${normalizedEntry}`);
1341
- }
1342
- const manifest = await getPackageManifest(fs);
1343
- const installedPackages = manifest.dependencies;
1344
- const sharedModuleIds = new Set(sharedModules);
1345
- const includedFiles = new Set;
1346
- const plugin = createVfsPlugin({
1472
+ const sandboxRef = {
1347
1473
  fs,
1348
- entryPoint: normalizedEntry,
1349
- npmImports,
1350
- installedPackages,
1351
- includedFiles,
1352
- sharedModuleIds
1353
- });
1354
- const es = await getEsbuild();
1355
- const result = await es.build({
1356
- entryPoints: [normalizedEntry],
1357
- bundle: true,
1358
- write: false,
1359
- format,
1360
- minify,
1361
- sourcemap: sourcemap ? "inline" : false,
1362
- globalName,
1363
- target,
1364
- external,
1365
- plugins: [plugin],
1366
- jsx: "automatic"
1367
- });
1368
- const code = result.outputFiles?.[0]?.text ?? "";
1369
- return {
1370
- code,
1371
- warnings: result.warnings,
1372
- includedFiles: Array.from(includedFiles)
1474
+ install,
1475
+ uninstall,
1476
+ build,
1477
+ typecheck,
1478
+ run
1373
1479
  };
1374
- }
1375
- async function bundleToUrl(options) {
1376
- const result = await bundle(options);
1377
- const blob = new Blob([result.code], { type: "application/javascript" });
1378
- return URL.createObjectURL(blob);
1379
- }
1380
- async function bundleAndImport(options) {
1381
- const url = await bundleToUrl(options);
1382
- try {
1383
- return await import(url);
1384
- } finally {
1385
- URL.revokeObjectURL(url);
1386
- }
1387
- }
1388
-
1389
- // src/commands/types.ts
1390
- function formatEsbuildMessages(messages) {
1391
- if (messages.length === 0)
1392
- return "";
1393
- return messages.map((msg) => {
1394
- if (msg.location) {
1395
- const { file, line, column } = msg.location;
1396
- const loc = file ? `${file}${line ? `:${line}` : ""}${column ? `:${column}` : ""}` : "";
1397
- return loc ? `${loc}: ${msg.text}` : msg.text;
1398
- }
1399
- return msg.text;
1400
- }).join(`
1401
- `);
1402
- }
1403
- // src/commands/compile.ts
1404
- import { defineCommand } from "just-bash/browser";
1405
-
1406
- // src/typechecker.ts
1407
- import ts from "typescript";
1408
- function categoryToString(category) {
1409
- switch (category) {
1410
- case ts.DiagnosticCategory.Error:
1411
- return "error";
1412
- case ts.DiagnosticCategory.Warning:
1413
- return "warning";
1414
- case ts.DiagnosticCategory.Suggestion:
1415
- return "suggestion";
1416
- case ts.DiagnosticCategory.Message:
1417
- return "message";
1418
- default:
1419
- return "error";
1420
- }
1421
- }
1422
- function convertDiagnostic(diag) {
1423
- let file = null;
1424
- let line = null;
1425
- let column = null;
1426
- if (diag.file && diag.start !== undefined) {
1427
- file = diag.file.fileName;
1428
- const pos = diag.file.getLineAndCharacterOfPosition(diag.start);
1429
- line = pos.line + 1;
1430
- column = pos.character + 1;
1431
- }
1432
- return {
1433
- file,
1434
- line,
1435
- column,
1436
- code: diag.code,
1437
- category: categoryToString(diag.category),
1438
- message: ts.flattenDiagnosticMessageText(diag.messageText, `
1439
- `)
1440
- };
1441
- }
1442
- function normalizePath(path) {
1443
- if (!path.startsWith("/")) {
1444
- return "/" + path;
1445
- }
1446
- return path;
1447
- }
1448
- async function preloadFiles(fs) {
1449
- const cache = new Map;
1450
- const allPaths = fs.getAllPaths();
1451
- for (const path of allPaths) {
1452
- if (path.endsWith(".ts") || path.endsWith(".tsx") || path.endsWith(".js") || path.endsWith(".jsx") || path.endsWith(".json") || path.endsWith(".d.ts")) {
1453
- try {
1454
- const stat = await fs.stat(path);
1455
- if (stat.isFile) {
1456
- const content = await fs.readFile(path);
1457
- cache.set(path, content);
1458
- }
1459
- } catch {}
1480
+ let bashInstance = null;
1481
+ function getBash() {
1482
+ if (!bashInstance) {
1483
+ const commands = createDefaultCommands(sandboxRef);
1484
+ bashInstance = new Bash({
1485
+ cwd: "/",
1486
+ fs: wrapFilesystemForJustBash(fs),
1487
+ customCommands: commands
1488
+ });
1460
1489
  }
1490
+ return bashInstance;
1461
1491
  }
1462
- return cache;
1463
- }
1464
- function parseTsConfig(configText, _configPath) {
1465
- try {
1466
- const config = JSON.parse(configText);
1467
- const compilerOptions = config.compilerOptions || {};
1468
- const options = {
1469
- ...getDefaultCompilerOptions()
1492
+ async function exec(command) {
1493
+ const bash = getBash();
1494
+ const result = await bash.exec(command);
1495
+ return {
1496
+ exitCode: result.exitCode,
1497
+ stdout: result.stdout,
1498
+ stderr: result.stderr
1470
1499
  };
1471
- if (compilerOptions.target) {
1472
- const targetMap = {
1473
- es5: ts.ScriptTarget.ES5,
1474
- es6: ts.ScriptTarget.ES2015,
1475
- es2015: ts.ScriptTarget.ES2015,
1476
- es2016: ts.ScriptTarget.ES2016,
1477
- es2017: ts.ScriptTarget.ES2017,
1478
- es2018: ts.ScriptTarget.ES2018,
1479
- es2019: ts.ScriptTarget.ES2019,
1480
- es2020: ts.ScriptTarget.ES2020,
1481
- es2021: ts.ScriptTarget.ES2021,
1482
- es2022: ts.ScriptTarget.ES2022,
1483
- esnext: ts.ScriptTarget.ESNext
1484
- };
1485
- options.target = targetMap[compilerOptions.target.toLowerCase()] ?? ts.ScriptTarget.ES2020;
1486
- }
1487
- if (compilerOptions.module) {
1488
- const moduleMap = {
1489
- commonjs: ts.ModuleKind.CommonJS,
1490
- amd: ts.ModuleKind.AMD,
1491
- umd: ts.ModuleKind.UMD,
1492
- system: ts.ModuleKind.System,
1493
- es6: ts.ModuleKind.ES2015,
1494
- es2015: ts.ModuleKind.ES2015,
1495
- es2020: ts.ModuleKind.ES2020,
1496
- es2022: ts.ModuleKind.ES2022,
1497
- esnext: ts.ModuleKind.ESNext,
1498
- node16: ts.ModuleKind.Node16,
1499
- nodenext: ts.ModuleKind.NodeNext
1500
- };
1501
- options.module = moduleMap[compilerOptions.module.toLowerCase()] ?? ts.ModuleKind.ESNext;
1502
- }
1503
- if (compilerOptions.moduleResolution) {
1504
- const resolutionMap = {
1505
- classic: ts.ModuleResolutionKind.Classic,
1506
- node: ts.ModuleResolutionKind.NodeJs,
1507
- node10: ts.ModuleResolutionKind.NodeJs,
1508
- node16: ts.ModuleResolutionKind.Node16,
1509
- nodenext: ts.ModuleResolutionKind.NodeNext,
1510
- bundler: ts.ModuleResolutionKind.Bundler
1511
- };
1512
- options.moduleResolution = resolutionMap[compilerOptions.moduleResolution.toLowerCase()] ?? ts.ModuleResolutionKind.Bundler;
1513
- }
1514
- if (compilerOptions.jsx) {
1515
- const jsxMap = {
1516
- preserve: ts.JsxEmit.Preserve,
1517
- react: ts.JsxEmit.React,
1518
- "react-jsx": ts.JsxEmit.ReactJSX,
1519
- "react-jsxdev": ts.JsxEmit.ReactJSXDev,
1520
- "react-native": ts.JsxEmit.ReactNative
1521
- };
1522
- options.jsx = jsxMap[compilerOptions.jsx.toLowerCase()] ?? ts.JsxEmit.ReactJSX;
1523
- }
1524
- if (compilerOptions.strict !== undefined)
1525
- options.strict = compilerOptions.strict;
1526
- if (compilerOptions.esModuleInterop !== undefined)
1527
- options.esModuleInterop = compilerOptions.esModuleInterop;
1528
- if (compilerOptions.skipLibCheck !== undefined)
1529
- options.skipLibCheck = compilerOptions.skipLibCheck;
1530
- if (compilerOptions.allowJs !== undefined)
1531
- options.allowJs = compilerOptions.allowJs;
1532
- if (compilerOptions.resolveJsonModule !== undefined)
1533
- options.resolveJsonModule = compilerOptions.resolveJsonModule;
1534
- if (compilerOptions.noImplicitAny !== undefined)
1535
- options.noImplicitAny = compilerOptions.noImplicitAny;
1536
- if (compilerOptions.strictNullChecks !== undefined)
1537
- options.strictNullChecks = compilerOptions.strictNullChecks;
1538
- if (Array.isArray(compilerOptions.lib)) {
1539
- options.lib = compilerOptions.lib.map((lib) => lib.toLowerCase().startsWith("lib.") ? lib : `lib.${lib.toLowerCase()}.d.ts`);
1540
- }
1541
- options.noEmit = true;
1542
- return options;
1543
- } catch (err) {
1544
- console.warn("Error parsing tsconfig.json:", err);
1545
- return getDefaultCompilerOptions();
1546
- }
1547
- }
1548
- function getDefaultCompilerOptions() {
1549
- return {
1550
- target: ts.ScriptTarget.ES2020,
1551
- module: ts.ModuleKind.ESNext,
1552
- moduleResolution: ts.ModuleResolutionKind.Bundler,
1553
- esModuleInterop: true,
1554
- strict: true,
1555
- skipLibCheck: true,
1556
- noEmit: true,
1557
- jsx: ts.JsxEmit.ReactJSX,
1558
- allowJs: true,
1559
- resolveJsonModule: true,
1560
- noLib: false,
1561
- lib: [
1562
- "lib.es2020.d.ts",
1563
- "lib.dom.d.ts",
1564
- "lib.dom.iterable.d.ts"
1565
- ]
1566
- };
1567
- }
1568
- var LIB_PATH_PREFIX = "/node_modules/typescript/lib/";
1569
- function createVfsCompilerHost(fileCache, libFiles, _options) {
1570
- function getLibContent(fileName) {
1571
- const libMatch = fileName.match(/lib\.([^/]+)\.d\.ts$/);
1572
- if (libMatch && libMatch[1]) {
1573
- return libFiles.get(libMatch[1]);
1574
- }
1575
- return;
1576
1500
  }
1577
1501
  return {
1578
- getSourceFile(fileName, languageVersion, onError) {
1579
- const normalizedPath = normalizePath(fileName);
1580
- const content = fileCache.get(normalizedPath);
1581
- if (content !== undefined) {
1582
- return ts.createSourceFile(normalizedPath, content, languageVersion, true);
1583
- }
1584
- const altContent = fileCache.get(fileName);
1585
- if (altContent !== undefined) {
1586
- return ts.createSourceFile(fileName, altContent, languageVersion, true);
1587
- }
1588
- const libContent = getLibContent(fileName);
1589
- if (libContent !== undefined) {
1590
- return ts.createSourceFile(fileName, libContent, languageVersion, true);
1591
- }
1592
- if (onError) {
1593
- onError(`File not found: ${fileName}`);
1594
- }
1595
- return;
1596
- },
1597
- getDefaultLibFileName(options) {
1598
- return LIB_PATH_PREFIX + ts.getDefaultLibFileName(options);
1599
- },
1600
- writeFile() {},
1601
- getCurrentDirectory() {
1602
- return "/";
1603
- },
1604
- getCanonicalFileName(fileName) {
1605
- return fileName;
1606
- },
1607
- useCaseSensitiveFileNames() {
1608
- return true;
1609
- },
1610
- getNewLine() {
1611
- return `
1612
- `;
1613
- },
1614
- fileExists(fileName) {
1615
- const normalizedPath = normalizePath(fileName);
1616
- if (fileCache.has(normalizedPath) || fileCache.has(fileName)) {
1617
- return true;
1618
- }
1619
- return getLibContent(fileName) !== undefined;
1620
- },
1621
- readFile(fileName) {
1622
- const normalizedPath = normalizePath(fileName);
1623
- const content = fileCache.get(normalizedPath) ?? fileCache.get(fileName);
1624
- if (content !== undefined) {
1625
- return content;
1626
- }
1627
- return getLibContent(fileName);
1628
- },
1629
- directoryExists(directoryName) {
1630
- const normalizedDir = normalizePath(directoryName);
1631
- for (const path of fileCache.keys()) {
1632
- if (path.startsWith(normalizedDir + "/")) {
1633
- return true;
1634
- }
1635
- }
1636
- if (normalizedDir === "/node_modules/typescript/lib" || normalizedDir === "/node_modules/typescript") {
1637
- return libFiles.size > 0;
1638
- }
1639
- if (normalizedDir === "/node_modules") {
1640
- return libFiles.size > 0 || Array.from(fileCache.keys()).some((p) => p.startsWith("/node_modules/"));
1641
- }
1642
- if (normalizedDir === "/node_modules/@types") {
1643
- return Array.from(fileCache.keys()).some((p) => p.startsWith("/node_modules/@types/"));
1644
- }
1645
- return false;
1646
- },
1647
- getDirectories(path) {
1648
- const normalizedPath = normalizePath(path);
1649
- const prefix = normalizedPath === "/" ? "/" : normalizedPath + "/";
1650
- const dirs = new Set;
1651
- for (const filePath of fileCache.keys()) {
1652
- if (filePath.startsWith(prefix)) {
1653
- const relative = filePath.slice(prefix.length);
1654
- const firstSlash = relative.indexOf("/");
1655
- if (firstSlash > 0) {
1656
- dirs.add(relative.slice(0, firstSlash));
1657
- }
1658
- }
1659
- }
1660
- if (normalizedPath === "/") {
1661
- const hasNodeModules = libFiles.size > 0 || Array.from(fileCache.keys()).some((p) => p.startsWith("/node_modules/"));
1662
- if (hasNodeModules) {
1663
- dirs.add("node_modules");
1664
- }
1665
- }
1666
- return Array.from(dirs);
1667
- },
1668
- realpath(path) {
1669
- return path;
1670
- },
1671
- getEnvironmentVariable() {
1672
- return;
1673
- }
1674
- };
1675
- }
1676
- async function typecheck(options) {
1677
- const {
1678
1502
  fs,
1679
- entryPoint,
1680
- tsconfigPath = "/tsconfig.json",
1681
- libFiles = new Map
1682
- } = options;
1683
- const normalizedEntry = normalizePath(entryPoint);
1684
- if (!await fs.exists(normalizedEntry)) {
1685
- throw new Error(`Entry point not found: ${normalizedEntry}`);
1686
- }
1687
- const fileCache = await preloadFiles(fs);
1688
- let compilerOptions = getDefaultCompilerOptions();
1689
- const tsconfigContent = fileCache.get(normalizePath(tsconfigPath));
1690
- if (tsconfigContent) {
1691
- compilerOptions = {
1692
- ...parseTsConfig(tsconfigContent, tsconfigPath),
1693
- noEmit: true
1694
- };
1695
- }
1696
- const host = createVfsCompilerHost(fileCache, libFiles, compilerOptions);
1697
- const program = ts.createProgram([normalizedEntry], compilerOptions, host);
1698
- const allDiagnostics = [
1699
- ...program.getSyntacticDiagnostics(),
1700
- ...program.getSemanticDiagnostics(),
1701
- ...program.getDeclarationDiagnostics()
1702
- ];
1703
- const diagnostics = allDiagnostics.map(convertDiagnostic);
1704
- const checkedFiles = program.getSourceFiles().map((sf) => sf.fileName).filter((f) => !f.includes("node_modules/typescript/lib"));
1705
- const hasErrors = diagnostics.some((d) => d.category === "error");
1706
- return {
1707
- diagnostics,
1708
- hasErrors,
1709
- checkedFiles
1710
- };
1711
- }
1712
- function formatDiagnostics(diagnostics) {
1713
- return diagnostics.map((d) => {
1714
- const location = d.file ? `${d.file}${d.line ? `:${d.line}` : ""}${d.column ? `:${d.column}` : ""}` : "(global)";
1715
- return `${location} - ${d.category} TS${d.code}: ${d.message}`;
1716
- }).join(`
1717
- `);
1718
- }
1719
- function formatDiagnosticsForAgent(diagnostics) {
1720
- if (diagnostics.length === 0) {
1721
- return "No type errors found.";
1722
- }
1723
- const errorCount = diagnostics.filter((d) => d.category === "error").length;
1724
- const warningCount = diagnostics.filter((d) => d.category === "warning").length;
1725
- const summary = errorCount > 0 || warningCount > 0 ? `Found ${errorCount} error(s) and ${warningCount} warning(s):
1726
-
1727
- ` : "";
1728
- const formatted = diagnostics.map((d) => {
1729
- const location = d.file ? `${d.file}${d.line ? ` at line ${d.line}` : ""}` : "Global";
1730
- const prefix = d.category === "error" ? "Error" : d.category === "warning" ? "Warning" : "Info";
1731
- return `${prefix} in ${location}: ${d.message} (TS${d.code})`;
1732
- }).join(`
1733
-
1734
- `);
1735
- return summary + formatted;
1736
- }
1737
-
1738
- // src/loader.ts
1739
- class ModuleLoadError extends Error {
1740
- constructor(message, cause) {
1741
- super(message, { cause });
1742
- this.name = "ModuleLoadError";
1743
- }
1744
- }
1745
-
1746
- class ExportNotFoundError extends Error {
1747
- exportName;
1748
- availableExports;
1749
- constructor(exportName, availableExports) {
1750
- super(`Export "${exportName}" not found. Available exports: ${availableExports.length > 0 ? availableExports.join(", ") : "(none)"}`);
1751
- this.exportName = exportName;
1752
- this.availableExports = availableExports;
1753
- this.name = "ExportNotFoundError";
1754
- }
1755
- }
1756
- function createModuleUrl(result) {
1757
- const blob = new Blob([result.code], { type: "application/javascript" });
1758
- return URL.createObjectURL(blob);
1759
- }
1760
- function revokeModuleUrl(url) {
1761
- URL.revokeObjectURL(url);
1762
- }
1763
- async function loadModule(result) {
1764
- const url = createModuleUrl(result);
1765
- try {
1766
- const module = await import(url);
1767
- return module;
1768
- } catch (err) {
1769
- throw new ModuleLoadError(`Failed to load module: ${err instanceof Error ? err.message : String(err)}`, err);
1770
- } finally {
1771
- revokeModuleUrl(url);
1772
- }
1773
- }
1774
- async function loadExport(result, exportName = "default") {
1775
- const module = await loadModule(result);
1776
- if (!(exportName in module)) {
1777
- const availableExports = Object.keys(module).filter((key) => !key.startsWith("__"));
1778
- throw new ExportNotFoundError(exportName, availableExports);
1779
- }
1780
- return module[exportName];
1781
- }
1782
- async function loadDefault(result) {
1783
- return loadExport(result, "default");
1784
- }
1785
- async function getExportNames(result) {
1786
- const module = await loadModule(result);
1787
- return Object.keys(module).filter((key) => !key.startsWith("__"));
1788
- }
1789
- async function hasExport(result, exportName) {
1790
- const module = await loadModule(result);
1791
- return exportName in module;
1792
- }
1793
-
1794
- // src/commands/compile.ts
1795
- function createTscCommand(deps) {
1796
- const { fs, libFiles, tsconfigPath } = deps;
1797
- return defineCommand("tsc", async (args, _ctx) => {
1798
- const entryPoint = args[0];
1799
- if (!entryPoint) {
1800
- return {
1801
- stdout: "",
1802
- stderr: `Usage: tsc <entry-point>
1803
-
1804
- Example: tsc /src/index.ts
1805
- `,
1806
- exitCode: 1
1807
- };
1808
- }
1809
- try {
1810
- if (!await fs.exists(entryPoint)) {
1811
- return {
1812
- stdout: "",
1813
- stderr: `Error: Entry point not found: ${entryPoint}
1814
- `,
1815
- exitCode: 1
1816
- };
1817
- }
1818
- const result = await typecheck({
1819
- fs,
1820
- entryPoint,
1821
- tsconfigPath,
1822
- libFiles
1823
- });
1824
- if (result.hasErrors) {
1825
- const formatted = formatDiagnosticsForAgent(result.diagnostics);
1826
- return {
1827
- stdout: "",
1828
- stderr: formatted + `
1829
- `,
1830
- exitCode: 1
1831
- };
1832
- }
1833
- const checkedCount = result.checkedFiles.length;
1834
- const warningCount = result.diagnostics.filter((d) => d.category === "warning").length;
1835
- let output = `Type check passed. Checked ${checkedCount} file(s).
1836
- `;
1837
- if (warningCount > 0) {
1838
- output += `
1839
- Warnings:
1840
- ${formatDiagnosticsForAgent(result.diagnostics.filter((d) => d.category === "warning"))}
1841
- `;
1842
- }
1843
- return {
1844
- stdout: output,
1845
- stderr: "",
1846
- exitCode: 0
1847
- };
1848
- } catch (err) {
1849
- return {
1850
- stdout: "",
1851
- stderr: `Type check failed: ${err instanceof Error ? err.message : String(err)}
1852
- `,
1853
- exitCode: 1
1854
- };
1855
- }
1856
- });
1857
- }
1858
- function createBuildCommand(deps) {
1859
- const { fs, libFiles, tsconfigPath, onBuild, getValidation, sharedModules } = deps;
1860
- return defineCommand("build", async (args, _ctx) => {
1861
- let entryPoint = null;
1862
- let skipTypecheck = false;
1863
- let minify = false;
1864
- let format = "esm";
1865
- for (let i = 0;i < args.length; i++) {
1866
- const arg = args[i];
1867
- if (arg === "--skip-typecheck" || arg === "-s") {
1868
- skipTypecheck = true;
1869
- } else if (arg === "--minify" || arg === "-m") {
1870
- minify = true;
1871
- } else if ((arg === "--format" || arg === "-f") && args[i + 1]) {
1872
- const f = args[++i].toLowerCase();
1873
- if (f === "esm" || f === "iife" || f === "cjs") {
1874
- format = f;
1875
- }
1876
- } else if (!arg.startsWith("-")) {
1877
- entryPoint = arg;
1878
- }
1879
- }
1880
- if (!entryPoint) {
1881
- return {
1882
- stdout: "",
1883
- stderr: `Usage: build <entry-point> [options]
1884
-
1885
- Options:
1886
- --skip-typecheck, -s Skip type checking
1887
- --minify, -m Minify output
1888
- --format, -f <fmt> Output format (esm|iife|cjs)
1889
-
1890
- Example: build /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
- let typecheckResult = null;
1905
- if (!skipTypecheck) {
1906
- typecheckResult = await typecheck({
1907
- fs,
1908
- entryPoint,
1909
- tsconfigPath,
1910
- libFiles
1911
- });
1912
- if (typecheckResult.hasErrors) {
1913
- const formatted = formatDiagnosticsForAgent(typecheckResult.diagnostics);
1914
- return {
1915
- stdout: "",
1916
- stderr: `Build failed: Type errors found.
1917
-
1918
- ${formatted}
1919
- `,
1920
- exitCode: 1
1921
- };
1922
- }
1923
- }
1924
- const bundleResult = await bundle({
1925
- fs,
1926
- entryPoint,
1927
- format,
1928
- minify,
1929
- sharedModules
1930
- });
1931
- let loadedModule;
1932
- try {
1933
- loadedModule = await loadModule(bundleResult);
1934
- } catch (err) {
1935
- const errorMessage = err instanceof Error ? err.message : String(err);
1936
- return {
1937
- stdout: "",
1938
- stderr: `Build failed: Module failed to load.
1939
-
1940
- ${errorMessage}
1941
- `,
1942
- exitCode: 1
1943
- };
1944
- }
1945
- const validateFn = getValidation?.();
1946
- let validatedModule = loadedModule;
1947
- if (validateFn) {
1948
- try {
1949
- validatedModule = validateFn(loadedModule);
1950
- } catch (err) {
1951
- const errorMessage = err instanceof Error ? err.message : String(err);
1952
- return {
1953
- stdout: "",
1954
- stderr: `Build failed: Validation error.
1955
-
1956
- ${errorMessage}
1957
- `,
1958
- exitCode: 1
1959
- };
1960
- }
1961
- }
1962
- if (onBuild) {
1963
- await onBuild({ bundle: bundleResult, module: validatedModule });
1964
- }
1965
- let output = `Build successful!
1966
- `;
1967
- output += `Entry: ${entryPoint}
1968
- `;
1969
- output += `Format: ${format}
1970
- `;
1971
- output += `Size: ${(bundleResult.code.length / 1024).toFixed(2)} KB
1972
- `;
1973
- if (typecheckResult) {
1974
- output += `Type checked: ${typecheckResult.checkedFiles.length} file(s)
1975
- `;
1976
- }
1977
- output += `Bundled: ${bundleResult.includedFiles.length} file(s)
1978
- `;
1979
- const exportNames = Object.keys(loadedModule).filter((k) => !k.startsWith("__"));
1980
- if (exportNames.length > 0) {
1981
- output += `Exports: ${exportNames.join(", ")}
1982
- `;
1983
- }
1984
- if (validateFn) {
1985
- output += `Validation: passed
1986
- `;
1987
- }
1988
- if (bundleResult.warnings.length > 0) {
1989
- output += `
1990
- Build warnings:
1991
- ${formatEsbuildMessages(bundleResult.warnings)}
1992
- `;
1993
- }
1994
- if (typecheckResult) {
1995
- const warnings = typecheckResult.diagnostics.filter((d) => d.category === "warning");
1996
- if (warnings.length > 0) {
1997
- output += `
1998
- Type warnings:
1999
- ${formatDiagnosticsForAgent(warnings)}
2000
- `;
2001
- }
2002
- }
2003
- return {
2004
- stdout: output,
2005
- stderr: "",
2006
- exitCode: 0
2007
- };
2008
- } catch (err) {
2009
- const errorMessage = err instanceof Error ? err.message : String(err);
2010
- return {
2011
- stdout: "",
2012
- stderr: `Build failed: ${errorMessage}
2013
- `,
2014
- exitCode: 1
2015
- };
2016
- }
2017
- });
2018
- }
2019
- // src/commands/packages.ts
2020
- import { defineCommand as defineCommand2 } from "just-bash/browser";
2021
- function createInstallCommand(deps) {
2022
- const { fs, typesCache } = deps;
2023
- return defineCommand2("install", async (args, _ctx) => {
2024
- if (args.length === 0) {
2025
- return {
2026
- stdout: "",
2027
- stderr: `Usage: install <package>[@version] [...packages]
2028
-
2029
- Examples:
2030
- install react
2031
- install lodash@4.17.21
2032
- install @tanstack/react-query@5
2033
- `,
2034
- exitCode: 1
2035
- };
2036
- }
2037
- const results = [];
2038
- let hasError = false;
2039
- for (const packageSpec of args) {
2040
- try {
2041
- const result = await installPackage(fs, packageSpec, { cache: typesCache });
2042
- let status = `+ ${result.name}@${result.version}`;
2043
- if (result.typesInstalled) {
2044
- status += ` (${result.typeFilesCount} type file${result.typeFilesCount !== 1 ? "s" : ""})`;
2045
- if (result.fromCache) {
2046
- status += " [cached]";
2047
- }
2048
- } else if (result.typesError) {
2049
- status += ` (no types: ${result.typesError})`;
2050
- }
2051
- results.push(status);
2052
- } catch (err) {
2053
- hasError = true;
2054
- const message = err instanceof Error ? err.message : String(err);
2055
- results.push(`x ${packageSpec}: ${message}`);
2056
- }
2057
- }
2058
- const output = results.join(`
2059
- `) + `
2060
- `;
2061
- if (hasError) {
2062
- return {
2063
- stdout: "",
2064
- stderr: output,
2065
- exitCode: 1
2066
- };
2067
- }
2068
- return {
2069
- stdout: output,
2070
- stderr: "",
2071
- exitCode: 0
2072
- };
2073
- });
2074
- }
2075
- function createUninstallCommand(deps) {
2076
- const { fs } = deps;
2077
- return defineCommand2("uninstall", async (args, _ctx) => {
2078
- if (args.length === 0) {
2079
- return {
2080
- stdout: "",
2081
- stderr: `Usage: uninstall <package> [...packages]
2082
- `,
2083
- exitCode: 1
2084
- };
2085
- }
2086
- const results = [];
2087
- let hasError = false;
2088
- for (const packageName of args) {
2089
- try {
2090
- const removed = await uninstallPackage(fs, packageName);
2091
- if (removed) {
2092
- results.push(`- ${packageName}`);
2093
- } else {
2094
- results.push(`x ${packageName}: not installed`);
2095
- hasError = true;
2096
- }
2097
- } catch (err) {
2098
- hasError = true;
2099
- const message = err instanceof Error ? err.message : String(err);
2100
- results.push(`x ${packageName}: ${message}`);
2101
- }
2102
- }
2103
- const output = results.join(`
2104
- `) + `
2105
- `;
2106
- if (hasError) {
2107
- return {
2108
- stdout: "",
2109
- stderr: output,
2110
- exitCode: 1
2111
- };
2112
- }
2113
- return {
2114
- stdout: output,
2115
- stderr: "",
2116
- exitCode: 0
2117
- };
2118
- });
2119
- }
2120
- function createListCommand(deps) {
2121
- const { fs } = deps;
2122
- return defineCommand2("list", async (_args, _ctx) => {
2123
- try {
2124
- const packages = await listPackages(fs);
2125
- if (packages.length === 0) {
2126
- return {
2127
- stdout: `No packages installed.
2128
- `,
2129
- stderr: "",
2130
- exitCode: 0
2131
- };
2132
- }
2133
- const output = packages.map((pkg) => `${pkg.name}@${pkg.version}`).join(`
2134
- `) + `
2135
- `;
2136
- return {
2137
- stdout: output,
2138
- stderr: "",
2139
- exitCode: 0
2140
- };
2141
- } catch (err) {
2142
- const message = err instanceof Error ? err.message : String(err);
2143
- return {
2144
- stdout: "",
2145
- stderr: `Failed to list packages: ${message}
2146
- `,
2147
- exitCode: 1
2148
- };
2149
- }
2150
- });
2151
- }
2152
- // src/commands/run.ts
2153
- import { defineCommand as defineCommand3 } from "just-bash/browser";
2154
- function createRunCommand(deps) {
2155
- const { fs, libFiles, tsconfigPath, runOptions = {}, sharedModules } = deps;
2156
- return defineCommand3("run", async (args, _ctx) => {
2157
- let entryPoint = null;
2158
- let skipTypecheck = runOptions.skipTypecheck ?? false;
2159
- let timeout = runOptions.timeout ?? 30000;
2160
- const scriptArgs = [];
2161
- let collectingArgs = false;
2162
- for (let i = 0;i < args.length; i++) {
2163
- const arg = args[i];
2164
- if (collectingArgs) {
2165
- scriptArgs.push(arg);
2166
- continue;
2167
- }
2168
- if (arg === "--") {
2169
- collectingArgs = true;
2170
- } else if (arg === "--skip-typecheck" || arg === "-s") {
2171
- skipTypecheck = true;
2172
- } else if ((arg === "--timeout" || arg === "-t") && args[i + 1]) {
2173
- timeout = parseInt(args[++i], 10);
2174
- if (isNaN(timeout))
2175
- timeout = 30000;
2176
- } else if (!arg.startsWith("-")) {
2177
- entryPoint = arg;
2178
- }
2179
- }
2180
- if (!entryPoint) {
2181
- return {
2182
- stdout: "",
2183
- stderr: `Usage: run <entry-point> [options] [-- args...]
2184
-
2185
- Options:
2186
- --skip-typecheck, -s Skip type checking
2187
- --timeout, -t <ms> Execution timeout (default: 30000)
2188
-
2189
- Example: run /src/index.ts
2190
- `,
2191
- exitCode: 1
2192
- };
2193
- }
2194
- const logs = [];
2195
- const originalConsole = {
2196
- log: console.log,
2197
- warn: console.warn,
2198
- error: console.error,
2199
- info: console.info,
2200
- debug: console.debug
2201
- };
2202
- const formatArgs = (...a) => a.map((v) => typeof v === "object" ? JSON.stringify(v) : String(v)).join(" ");
2203
- const captureLog = (...a) => {
2204
- logs.push(formatArgs(...a));
2205
- originalConsole.log.apply(console, a);
2206
- };
2207
- const captureWarn = (...a) => {
2208
- logs.push(`[warn] ${formatArgs(...a)}`);
2209
- originalConsole.warn.apply(console, a);
2210
- };
2211
- const captureError = (...a) => {
2212
- logs.push(`[error] ${formatArgs(...a)}`);
2213
- originalConsole.error.apply(console, a);
2214
- };
2215
- const captureInfo = (...a) => {
2216
- logs.push(`[info] ${formatArgs(...a)}`);
2217
- originalConsole.info.apply(console, a);
2218
- };
2219
- const captureDebug = (...a) => {
2220
- logs.push(`[debug] ${formatArgs(...a)}`);
2221
- originalConsole.debug.apply(console, a);
2222
- };
2223
- const restoreConsole = () => {
2224
- console.log = originalConsole.log;
2225
- console.warn = originalConsole.warn;
2226
- console.error = originalConsole.error;
2227
- console.info = originalConsole.info;
2228
- console.debug = originalConsole.debug;
2229
- };
2230
- try {
2231
- if (!await fs.exists(entryPoint)) {
2232
- return {
2233
- stdout: "",
2234
- stderr: `Error: Entry point not found: ${entryPoint}
2235
- `,
2236
- exitCode: 1
2237
- };
2238
- }
2239
- if (!skipTypecheck) {
2240
- const typecheckResult = await typecheck({
2241
- fs,
2242
- entryPoint,
2243
- tsconfigPath,
2244
- libFiles
2245
- });
2246
- if (typecheckResult.hasErrors) {
2247
- const formatted = formatDiagnosticsForAgent(typecheckResult.diagnostics);
2248
- return {
2249
- stdout: "",
2250
- stderr: `Type errors:
2251
- ${formatted}
2252
- `,
2253
- exitCode: 1
2254
- };
2255
- }
2256
- }
2257
- const bundleResult = await bundle({
2258
- fs,
2259
- entryPoint,
2260
- format: "esm",
2261
- sharedModules
2262
- });
2263
- console.log = captureLog;
2264
- console.warn = captureWarn;
2265
- console.error = captureError;
2266
- console.info = captureInfo;
2267
- console.debug = captureDebug;
2268
- const context = {
2269
- fs,
2270
- env: { ...runOptions.env },
2271
- args: scriptArgs,
2272
- log: captureLog,
2273
- error: captureError
2274
- };
2275
- const startTime = performance.now();
2276
- let returnValue;
2277
- const executeCode = async () => {
2278
- const module = await loadModule(bundleResult);
2279
- if (typeof module.main === "function") {
2280
- returnValue = await module.main(context);
2281
- }
2282
- };
2283
- if (timeout > 0) {
2284
- const timeoutPromise = new Promise((_, reject) => {
2285
- setTimeout(() => reject(new Error(`Execution timed out after ${timeout}ms`)), timeout);
2286
- });
2287
- await Promise.race([executeCode(), timeoutPromise]);
2288
- } else {
2289
- await executeCode();
2290
- }
2291
- const executionTimeMs = performance.now() - startTime;
2292
- restoreConsole();
2293
- let output = "";
2294
- if (logs.length > 0) {
2295
- output = logs.join(`
2296
- `) + `
2297
- `;
2298
- }
2299
- if (returnValue !== undefined) {
2300
- const returnStr = typeof returnValue === "object" ? JSON.stringify(returnValue, null, 2) : String(returnValue);
2301
- output += `[return] ${returnStr}
2302
- `;
2303
- }
2304
- output += `
2305
- Execution completed in ${executionTimeMs.toFixed(2)}ms
2306
- `;
2307
- return {
2308
- stdout: output,
2309
- stderr: "",
2310
- exitCode: 0
2311
- };
2312
- } catch (err) {
2313
- restoreConsole();
2314
- const errorMessage = err instanceof Error ? err.message : String(err);
2315
- const errorStack = err instanceof Error && err.stack ? `
2316
- ${err.stack}` : "";
2317
- let output = "";
2318
- if (logs.length > 0) {
2319
- output = logs.join(`
2320
- `) + `
2321
-
2322
- `;
2323
- }
2324
- return {
2325
- stdout: output,
2326
- stderr: `Runtime error: ${errorMessage}${errorStack}
2327
- `,
2328
- exitCode: 1
1503
+ exec,
1504
+ get lastBuild() {
1505
+ return lastBuild;
1506
+ },
1507
+ getState() {
1508
+ return { files: fs.getFiles() };
1509
+ },
1510
+ onBuild(callback) {
1511
+ onBuildCallbacks.add(callback);
1512
+ return () => {
1513
+ onBuildCallbacks.delete(callback);
2329
1514
  };
2330
- }
2331
- });
2332
- }
2333
- // src/commands/index.ts
2334
- function createDefaultCommands(deps) {
2335
- return [
2336
- createTscCommand(deps),
2337
- createBuildCommand(deps),
2338
- createRunCommand(deps),
2339
- createInstallCommand(deps),
2340
- createUninstallCommand(deps),
2341
- createListCommand(deps)
2342
- ];
1515
+ },
1516
+ install,
1517
+ uninstall,
1518
+ build,
1519
+ typecheck,
1520
+ run,
1521
+ readFile: (path) => fs.readFile(path),
1522
+ writeFile: (path, content) => fs.writeFile(path, content)
1523
+ };
2343
1524
  }
2344
1525
 
2345
- // src/ts-libs.ts
2346
- var TS_VERSION = "5.9.3";
2347
- var CDN_BASE = `https://cdn.jsdelivr.net/npm/typescript@${TS_VERSION}/lib`;
2348
- function getDefaultBrowserLibs() {
2349
- return ["es2020", "dom", "dom.iterable"];
1526
+ // src/core/sandlot.ts
1527
+ function createSandlot(options) {
1528
+ const {
1529
+ bundler,
1530
+ typechecker,
1531
+ typesResolver,
1532
+ executor,
1533
+ sharedModules,
1534
+ sandboxDefaults = {}
1535
+ } = options;
1536
+ const sharedModuleRegistry = createSharedModuleRegistry(sharedModules);
1537
+ const sandboxContext = {
1538
+ bundler,
1539
+ typechecker,
1540
+ typesResolver,
1541
+ sharedModuleRegistry,
1542
+ executor
1543
+ };
1544
+ return {
1545
+ async createSandbox(sandboxOptions = {}) {
1546
+ const fs = Filesystem.create({
1547
+ maxSizeBytes: sandboxOptions.maxFilesystemSize ?? sandboxDefaults.maxFilesystemSize
1548
+ });
1549
+ return createSandboxImpl(fs, sandboxOptions, sandboxContext);
1550
+ },
1551
+ get sharedModules() {
1552
+ return sharedModuleRegistry;
1553
+ }
1554
+ };
2350
1555
  }
1556
+ // src/core/typechecker.ts
1557
+ import ts from "typescript";
1558
+ var TS_VERSION = "5.9.3";
1559
+ var DEFAULT_CDN_BASE = `https://cdn.jsdelivr.net/npm/typescript@${TS_VERSION}/lib`;
1560
+ var DEFAULT_LIBS = ["es2020", "dom", "dom.iterable"];
1561
+ var LIB_PATH_PREFIX = "/node_modules/typescript/lib/";
2351
1562
  function parseLibReferences(content) {
2352
1563
  const refs = [];
2353
1564
  const regex = /\/\/\/\s*<reference\s+lib="([^"]+)"\s*\/>/g;
@@ -2362,20 +1573,16 @@ function parseLibReferences(content) {
2362
1573
  function libNameToFileName(name) {
2363
1574
  return `lib.${name}.d.ts`;
2364
1575
  }
2365
- function extractLibName(filePath) {
2366
- const match = filePath.match(/lib\.([^/]+)\.d\.ts$/);
2367
- return match?.[1] ?? null;
2368
- }
2369
- async function fetchLibFile(name) {
1576
+ async function fetchLibFile(name, baseUrl) {
2370
1577
  const fileName = libNameToFileName(name);
2371
- const url = `${CDN_BASE}/${fileName}`;
1578
+ const url = `${baseUrl}/${fileName}`;
2372
1579
  const response = await fetch(url);
2373
1580
  if (!response.ok) {
2374
1581
  throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
2375
1582
  }
2376
1583
  return response.text();
2377
1584
  }
2378
- async function fetchAllLibs(libs) {
1585
+ async function fetchAllLibs(libs, baseUrl) {
2379
1586
  const result = new Map;
2380
1587
  const pending = new Set(libs);
2381
1588
  const fetched = new Set;
@@ -2388,10 +1595,10 @@ async function fetchAllLibs(libs) {
2388
1595
  }
2389
1596
  fetched.add(name);
2390
1597
  try {
2391
- const content = await fetchLibFile(name);
1598
+ const content = await fetchLibFile(name, baseUrl);
2392
1599
  return { name, content };
2393
1600
  } catch (err) {
2394
- console.warn(`Failed to fetch lib.${name}.d.ts:`, err);
1601
+ console.warn(`[typechecker] Failed to fetch lib.${name}.d.ts:`, err);
2395
1602
  return { name, content: null };
2396
1603
  }
2397
1604
  }));
@@ -2409,292 +1616,473 @@ async function fetchAllLibs(libs) {
2409
1616
  }
2410
1617
  return result;
2411
1618
  }
2412
- var memoryCache = null;
2413
-
2414
- class LibCache {
2415
- async getOrFetch(libs) {
2416
- if (memoryCache) {
2417
- const missing = libs.filter((lib) => !memoryCache.has(lib));
2418
- if (missing.length === 0) {
2419
- return memoryCache;
2420
- }
2421
- console.log(`Cache missing libs: ${missing.join(", ")}, fetching all...`);
2422
- }
2423
- console.log(`Fetching TypeScript libs from CDN: ${libs.join(", ")}...`);
2424
- const fetched = await fetchAllLibs(libs);
2425
- console.log(`Fetched ${fetched.size} lib files`);
2426
- memoryCache = fetched;
2427
- return fetched;
2428
- }
2429
- get() {
2430
- return memoryCache;
2431
- }
2432
- set(libs) {
2433
- memoryCache = libs;
1619
+ function normalizePath(path) {
1620
+ if (!path.startsWith("/")) {
1621
+ return "/" + path;
2434
1622
  }
2435
- clear() {
2436
- memoryCache = null;
1623
+ return path;
1624
+ }
1625
+ function getLibContent(fileName, libFiles) {
1626
+ const match = fileName.match(/lib\.([^/]+)\.d\.ts$/);
1627
+ if (match?.[1]) {
1628
+ return libFiles.get(match[1]);
2437
1629
  }
1630
+ return;
2438
1631
  }
2439
- async function fetchAndCacheLibs(libs = getDefaultBrowserLibs()) {
2440
- const cache = new LibCache;
2441
- return cache.getOrFetch(libs);
1632
+ function createCompilerHost(fs, libFiles, options) {
1633
+ return {
1634
+ getSourceFile(fileName, languageVersion, onError) {
1635
+ const normalizedPath = normalizePath(fileName);
1636
+ try {
1637
+ if (fs.exists(normalizedPath)) {
1638
+ const stat = fs.stat(normalizedPath);
1639
+ if (stat.isFile) {
1640
+ const content = fs.readFile(normalizedPath);
1641
+ return ts.createSourceFile(normalizedPath, content, languageVersion, true);
1642
+ }
1643
+ }
1644
+ } catch {}
1645
+ try {
1646
+ if (fs.exists(fileName)) {
1647
+ const stat = fs.stat(fileName);
1648
+ if (stat.isFile) {
1649
+ const content = fs.readFile(fileName);
1650
+ return ts.createSourceFile(fileName, content, languageVersion, true);
1651
+ }
1652
+ }
1653
+ } catch {}
1654
+ const libContent = getLibContent(fileName, libFiles);
1655
+ if (libContent !== undefined) {
1656
+ return ts.createSourceFile(fileName, libContent, languageVersion, true);
1657
+ }
1658
+ if (onError) {
1659
+ onError(`File not found: ${fileName}`);
1660
+ }
1661
+ return;
1662
+ },
1663
+ getDefaultLibFileName(opts) {
1664
+ return LIB_PATH_PREFIX + ts.getDefaultLibFileName(opts);
1665
+ },
1666
+ writeFile() {},
1667
+ getCurrentDirectory() {
1668
+ return "/";
1669
+ },
1670
+ getCanonicalFileName(fileName) {
1671
+ return fileName;
1672
+ },
1673
+ useCaseSensitiveFileNames() {
1674
+ return true;
1675
+ },
1676
+ getNewLine() {
1677
+ return `
1678
+ `;
1679
+ },
1680
+ fileExists(fileName) {
1681
+ const normalizedPath = normalizePath(fileName);
1682
+ try {
1683
+ if (fs.exists(normalizedPath)) {
1684
+ return fs.stat(normalizedPath).isFile;
1685
+ }
1686
+ } catch {}
1687
+ return getLibContent(fileName, libFiles) !== undefined;
1688
+ },
1689
+ readFile(fileName) {
1690
+ const normalizedPath = normalizePath(fileName);
1691
+ try {
1692
+ if (fs.exists(normalizedPath)) {
1693
+ return fs.readFile(normalizedPath);
1694
+ }
1695
+ } catch {}
1696
+ return getLibContent(fileName, libFiles);
1697
+ },
1698
+ directoryExists(directoryName) {
1699
+ const normalizedDir = normalizePath(directoryName);
1700
+ try {
1701
+ if (fs.exists(normalizedDir)) {
1702
+ return fs.stat(normalizedDir).isDirectory;
1703
+ }
1704
+ } catch {}
1705
+ if (normalizedDir === "/node_modules/typescript/lib" || normalizedDir === "/node_modules/typescript" || normalizedDir === "/node_modules") {
1706
+ return libFiles.size > 0;
1707
+ }
1708
+ return false;
1709
+ },
1710
+ getDirectories(path) {
1711
+ const normalizedPath = normalizePath(path);
1712
+ try {
1713
+ if (!fs.exists(normalizedPath)) {
1714
+ return [];
1715
+ }
1716
+ const stat = fs.stat(normalizedPath);
1717
+ if (!stat.isDirectory) {
1718
+ return [];
1719
+ }
1720
+ const entries = fs.readdir(normalizedPath);
1721
+ const dirs = [];
1722
+ for (const name of entries) {
1723
+ const childPath = normalizedPath === "/" ? `/${name}` : `${normalizedPath}/${name}`;
1724
+ try {
1725
+ if (fs.stat(childPath).isDirectory) {
1726
+ dirs.push(name);
1727
+ }
1728
+ } catch {}
1729
+ }
1730
+ return dirs;
1731
+ } catch {
1732
+ return [];
1733
+ }
1734
+ },
1735
+ realpath(path) {
1736
+ return path;
1737
+ },
1738
+ getEnvironmentVariable() {
1739
+ return;
1740
+ }
1741
+ };
2442
1742
  }
2443
-
2444
- // src/shared-resources.ts
2445
- async function createSharedResources(options = {}) {
2446
- const { libs = getDefaultBrowserLibs(), skipLibs = false, skipBundler = false } = options;
2447
- const libsPromise = skipLibs ? Promise.resolve(new Map) : fetchAndCacheLibs(libs);
2448
- const bundlerPromise = skipBundler ? Promise.resolve() : initBundler();
2449
- const typesCache = new InMemoryTypesCache;
2450
- const [libFiles] = await Promise.all([libsPromise, bundlerPromise]);
1743
+ function getDefaultCompilerOptions() {
2451
1744
  return {
2452
- libFiles,
2453
- bundlerReady: Promise.resolve(),
2454
- typesCache
1745
+ target: ts.ScriptTarget.ES2020,
1746
+ module: ts.ModuleKind.ESNext,
1747
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
1748
+ esModuleInterop: true,
1749
+ strict: true,
1750
+ skipLibCheck: true,
1751
+ noEmit: true,
1752
+ jsx: ts.JsxEmit.ReactJSX,
1753
+ allowJs: true,
1754
+ resolveJsonModule: true,
1755
+ lib: ["lib.es2020.d.ts", "lib.dom.d.ts", "lib.dom.iterable.d.ts"]
2455
1756
  };
2456
1757
  }
2457
- var defaultResourcesInstance = null;
2458
- var defaultResourcesPromise = null;
2459
- async function getDefaultResources() {
2460
- if (defaultResourcesInstance) {
2461
- return defaultResourcesInstance;
2462
- }
2463
- if (!defaultResourcesPromise) {
2464
- defaultResourcesPromise = createSharedResources().then((resources) => {
2465
- defaultResourcesInstance = resources;
2466
- return resources;
2467
- });
1758
+ function parseTsConfig(fs, configPath) {
1759
+ try {
1760
+ if (!fs.exists(configPath)) {
1761
+ return getDefaultCompilerOptions();
1762
+ }
1763
+ const configText = fs.readFile(configPath);
1764
+ const { config, error } = ts.parseConfigFileTextToJson(configPath, configText);
1765
+ if (error) {
1766
+ console.warn("[typechecker] Error parsing tsconfig:", error.messageText);
1767
+ return getDefaultCompilerOptions();
1768
+ }
1769
+ const parseHost = {
1770
+ useCaseSensitiveFileNames: true,
1771
+ readDirectory: () => [],
1772
+ fileExists: (path) => fs.exists(normalizePath(path)),
1773
+ readFile: (path) => {
1774
+ try {
1775
+ return fs.readFile(normalizePath(path));
1776
+ } catch {
1777
+ return;
1778
+ }
1779
+ }
1780
+ };
1781
+ const parsed = ts.parseJsonConfigFileContent(config, parseHost, "/", undefined, configPath);
1782
+ const relevantErrors = parsed.errors.filter((e) => e.code !== 18003);
1783
+ if (relevantErrors.length > 0) {
1784
+ console.warn("[typechecker] tsconfig parse errors:", relevantErrors.map((e) => e.messageText));
1785
+ }
1786
+ return {
1787
+ ...parsed.options,
1788
+ noEmit: true
1789
+ };
1790
+ } catch (err) {
1791
+ console.warn("[typechecker] Error reading tsconfig:", err);
1792
+ return getDefaultCompilerOptions();
2468
1793
  }
2469
- return defaultResourcesPromise;
2470
1794
  }
2471
- function clearDefaultResources() {
2472
- defaultResourcesInstance = null;
2473
- defaultResourcesPromise = null;
1795
+ function categoryToSeverity(category) {
1796
+ switch (category) {
1797
+ case ts.DiagnosticCategory.Error:
1798
+ return "error";
1799
+ case ts.DiagnosticCategory.Warning:
1800
+ return "warning";
1801
+ default:
1802
+ return "info";
1803
+ }
2474
1804
  }
2475
- function hasDefaultResources() {
2476
- return defaultResourcesInstance !== null;
1805
+ function convertDiagnostic(diag) {
1806
+ let file;
1807
+ let line;
1808
+ let column;
1809
+ if (diag.file && diag.start !== undefined) {
1810
+ file = diag.file.fileName;
1811
+ const pos = diag.file.getLineAndCharacterOfPosition(diag.start);
1812
+ line = pos.line + 1;
1813
+ column = pos.character + 1;
1814
+ }
1815
+ return {
1816
+ file,
1817
+ line,
1818
+ column,
1819
+ message: ts.flattenDiagnosticMessageText(diag.messageText, `
1820
+ `),
1821
+ severity: categoryToSeverity(diag.category)
1822
+ };
2477
1823
  }
2478
1824
 
2479
- // src/build-emitter.ts
2480
- class BuildEmitter {
2481
- listeners = new Set;
2482
- emit = async (result) => {
2483
- const promises = [];
2484
- for (const listener of this.listeners) {
2485
- const ret = listener(result);
2486
- if (ret instanceof Promise) {
2487
- promises.push(ret);
2488
- }
1825
+ class Typechecker {
1826
+ options;
1827
+ libCache = new Map;
1828
+ initPromise = null;
1829
+ constructor(options = {}) {
1830
+ this.options = options;
1831
+ }
1832
+ async initialize() {
1833
+ if (this.initPromise) {
1834
+ await this.initPromise;
1835
+ return;
2489
1836
  }
2490
- await Promise.all(promises);
2491
- };
2492
- on(callback) {
2493
- this.listeners.add(callback);
2494
- return () => {
2495
- this.listeners.delete(callback);
1837
+ if (this.libCache.size > 0) {
1838
+ return;
1839
+ }
1840
+ this.initPromise = this.fetchLibs();
1841
+ await this.initPromise;
1842
+ }
1843
+ async fetchLibs() {
1844
+ const libs = this.options.libs ?? DEFAULT_LIBS;
1845
+ const baseUrl = this.options.libsBaseUrl ?? DEFAULT_CDN_BASE;
1846
+ console.log(`[typechecker] Fetching TypeScript libs: ${libs.join(", ")}...`);
1847
+ const fetched = await fetchAllLibs(libs, baseUrl);
1848
+ console.log(`[typechecker] Fetched ${fetched.size} lib files`);
1849
+ this.libCache = fetched;
1850
+ }
1851
+ async typecheck(options) {
1852
+ await this.initialize();
1853
+ const { fs, entryPoint, tsconfigPath = "/tsconfig.json" } = options;
1854
+ const normalizedEntry = normalizePath(entryPoint);
1855
+ if (!fs.exists(normalizedEntry)) {
1856
+ return {
1857
+ success: false,
1858
+ diagnostics: [
1859
+ {
1860
+ file: normalizedEntry,
1861
+ message: `Entry point not found: ${normalizedEntry}`,
1862
+ severity: "error"
1863
+ }
1864
+ ]
1865
+ };
1866
+ }
1867
+ const compilerOptions = parseTsConfig(fs, tsconfigPath);
1868
+ const host = createCompilerHost(fs, this.libCache, compilerOptions);
1869
+ const program = ts.createProgram([normalizedEntry], compilerOptions, host);
1870
+ const allDiagnostics = [
1871
+ ...program.getSyntacticDiagnostics(),
1872
+ ...program.getSemanticDiagnostics(),
1873
+ ...program.getDeclarationDiagnostics()
1874
+ ];
1875
+ const diagnostics = allDiagnostics.map(convertDiagnostic);
1876
+ const success = !diagnostics.some((d) => d.severity === "error");
1877
+ return {
1878
+ success,
1879
+ diagnostics
2496
1880
  };
2497
1881
  }
2498
1882
  }
2499
-
2500
- // src/sandbox.ts
2501
- async function createSandbox(options = {}) {
2502
- const {
2503
- initialFiles,
2504
- maxFilesystemSize,
2505
- tsconfigPath = "/tsconfig.json",
2506
- resources: providedResources,
2507
- onBuild: onBuildCallback,
2508
- customCommands = [],
2509
- sharedModules,
2510
- bashOptions = {}
2511
- } = options;
2512
- const fs = Filesystem.create({
2513
- initialFiles,
2514
- maxSizeBytes: maxFilesystemSize
2515
- });
2516
- const resourcesPromise = providedResources ? Promise.resolve(providedResources) : getDefaultResources();
2517
- const bundlerPromise = initBundler();
2518
- const [resources] = await Promise.all([resourcesPromise, bundlerPromise]);
2519
- const libFiles = resources.libFiles;
2520
- const typesCache = resources.typesCache;
2521
- if (sharedModules && sharedModules.length > 0) {
2522
- const basePackages = new Set;
2523
- for (const moduleId of sharedModules) {
2524
- const { packageName } = parseImportPath(moduleId);
2525
- basePackages.add(packageName);
2526
- }
2527
- await Promise.all(Array.from(basePackages).map(async (packageName) => {
2528
- try {
2529
- await installPackage(fs, packageName, { cache: typesCache });
2530
- } catch (err) {
2531
- console.warn(`[sandlot] Failed to install types for shared module "${packageName}":`, err);
2532
- }
2533
- }));
1883
+ function createTypechecker(options) {
1884
+ return new Typechecker(options);
1885
+ }
1886
+ // src/core/esm-types-resolver.ts
1887
+ class InMemoryTypesCache {
1888
+ cache = new Map;
1889
+ async get(key) {
1890
+ return this.cache.get(key) ?? null;
2534
1891
  }
2535
- const buildEmitter = new BuildEmitter;
2536
- let lastBuild = null;
2537
- buildEmitter.on((result) => {
2538
- lastBuild = result;
2539
- });
2540
- if (onBuildCallback) {
2541
- buildEmitter.on(onBuildCallback);
1892
+ async set(key, value) {
1893
+ this.cache.set(key, value);
1894
+ }
1895
+ async has(key) {
1896
+ return this.cache.has(key);
1897
+ }
1898
+ clear() {
1899
+ this.cache.clear();
2542
1900
  }
2543
- let validationFn = null;
2544
- const commandDeps = {
2545
- fs,
2546
- libFiles,
2547
- tsconfigPath,
2548
- onBuild: buildEmitter.emit,
2549
- getValidation: () => validationFn,
2550
- typesCache,
2551
- sharedModules
2552
- };
2553
- const defaultCommands = createDefaultCommands(commandDeps);
2554
- const bash = new Bash({
2555
- ...bashOptions,
2556
- cwd: "/",
2557
- fs,
2558
- customCommands: [...defaultCommands, ...customCommands]
2559
- });
2560
- return {
2561
- fs,
2562
- bash,
2563
- get lastBuild() {
2564
- return lastBuild;
2565
- },
2566
- getState: () => ({ files: fs.getFiles() }),
2567
- onBuild: (callback) => buildEmitter.on(callback),
2568
- setValidation: (fn) => {
2569
- validationFn = fn;
2570
- },
2571
- clearValidation: () => {
2572
- validationFn = null;
2573
- }
2574
- };
2575
1901
  }
2576
- // src/builder.ts
2577
- function createBuilder(options) {
2578
- return async (prompt, callOptions) => {
2579
- const sandbox = options.sandbox ?? await createSandbox(options.sandboxOptions);
2580
- if (callOptions?.validate) {
2581
- sandbox.setValidation(callOptions.validate);
2582
- }
2583
- const captured = { output: null };
2584
- const unsubscribe = sandbox.onBuild((output) => {
2585
- captured.output = output;
2586
- });
2587
- const { timeout, signal } = callOptions ?? {};
2588
- let timeoutId;
2589
- let abortController;
2590
- if (timeout !== undefined || signal !== undefined) {
2591
- abortController = new AbortController;
2592
- if (timeout !== undefined) {
2593
- timeoutId = setTimeout(() => {
2594
- abortController.abort(new Error(`Build timed out after ${timeout}ms`));
2595
- }, timeout);
1902
+
1903
+ class EsmTypesResolver {
1904
+ baseUrl;
1905
+ cache;
1906
+ tryTypesPackages;
1907
+ constructor(options = {}) {
1908
+ this.baseUrl = options.baseUrl ?? "https://esm.sh";
1909
+ this.cache = options.cache ?? null;
1910
+ this.tryTypesPackages = options.tryTypesPackages ?? true;
1911
+ }
1912
+ async resolveTypes(specifier, version) {
1913
+ const resolved = await this.resolve(specifier, version);
1914
+ if (!resolved) {
1915
+ return {};
1916
+ }
1917
+ const result = {};
1918
+ const pkgPath = `/node_modules/${resolved.packageName}`;
1919
+ for (const [relativePath, content] of Object.entries(resolved.files)) {
1920
+ result[`${pkgPath}/${relativePath}`] = content;
1921
+ }
1922
+ return result;
1923
+ }
1924
+ async resolve(specifier, version) {
1925
+ const { packageName, subpath } = parseSpecifier(specifier);
1926
+ const cacheKey = makeCacheKey(packageName, subpath, version);
1927
+ if (this.cache) {
1928
+ const cached = await this.cache.get(cacheKey);
1929
+ if (cached) {
1930
+ return cached;
2596
1931
  }
2597
- if (signal !== undefined) {
2598
- if (signal.aborted) {
2599
- abortController.abort(signal.reason);
2600
- } else {
2601
- signal.addEventListener("abort", () => {
2602
- abortController.abort(signal.reason);
2603
- }, { once: true });
2604
- }
1932
+ }
1933
+ let result = await this.tryResolve(packageName, subpath, version);
1934
+ if (!result && this.tryTypesPackages && !packageName.startsWith("@types/")) {
1935
+ const typesPackageName = toTypesPackageName(packageName);
1936
+ result = await this.tryResolve(typesPackageName, subpath, version);
1937
+ if (result) {
1938
+ result.fromTypesPackage = true;
1939
+ result.packageName = packageName;
2605
1940
  }
2606
1941
  }
2607
- let result;
2608
- let error = null;
1942
+ if (result && this.cache) {
1943
+ await this.cache.set(cacheKey, result);
1944
+ }
1945
+ return result;
1946
+ }
1947
+ async tryResolve(packageName, subpath, version) {
2609
1948
  try {
2610
- const buildPromise = options.build(sandbox, prompt);
2611
- if (abortController) {
2612
- const abortPromise = new Promise((_, reject) => {
2613
- abortController.signal.addEventListener("abort", () => {
2614
- const err = abortController.signal.reason instanceof Error ? abortController.signal.reason : new Error("Build aborted");
2615
- err.name = "AbortError";
2616
- reject(err);
2617
- }, { once: true });
2618
- if (abortController.signal.aborted) {
2619
- const err = abortController.signal.reason instanceof Error ? abortController.signal.reason : new Error("Build aborted");
2620
- err.name = "AbortError";
2621
- reject(err);
2622
- }
2623
- });
2624
- result = await Promise.race([buildPromise, abortPromise]);
2625
- } else {
2626
- result = await buildPromise;
1949
+ const versionSuffix = version ? `@${version}` : "";
1950
+ const pathSuffix = subpath ? `/${subpath}` : "";
1951
+ const url = `${this.baseUrl}/${packageName}${versionSuffix}${pathSuffix}`;
1952
+ const response = await fetch(url, { method: "HEAD" });
1953
+ if (!response.ok) {
1954
+ return null;
2627
1955
  }
2628
- } catch (err) {
2629
- error = err instanceof Error ? err : new Error(String(err));
2630
- } finally {
2631
- if (timeoutId !== undefined) {
2632
- clearTimeout(timeoutId);
1956
+ const resolvedVersion = this.extractVersion(response, packageName, version);
1957
+ const typesHeader = response.headers.get("X-TypeScript-Types");
1958
+ if (!typesHeader) {
1959
+ return null;
2633
1960
  }
2634
- unsubscribe();
2635
- if (callOptions?.validate) {
2636
- sandbox.clearValidation();
1961
+ const typesUrl = new URL(typesHeader, response.url).href;
1962
+ const files = await this.fetchTypesRecursively(typesUrl, subpath);
1963
+ if (Object.keys(files).length === 0) {
1964
+ return null;
1965
+ }
1966
+ return {
1967
+ packageName,
1968
+ version: resolvedVersion,
1969
+ files,
1970
+ fromTypesPackage: packageName.startsWith("@types/")
1971
+ };
1972
+ } catch {
1973
+ return null;
1974
+ }
1975
+ }
1976
+ async fetchTypesRecursively(entryUrl, subpath, visited = new Set) {
1977
+ if (visited.has(entryUrl)) {
1978
+ return {};
1979
+ }
1980
+ visited.add(entryUrl);
1981
+ const response = await fetch(entryUrl);
1982
+ if (!response.ok) {
1983
+ return {};
1984
+ }
1985
+ const content = await response.text();
1986
+ const files = {};
1987
+ const fileName = subpath ? `${subpath}.d.ts` : "index.d.ts";
1988
+ files[fileName] = content;
1989
+ if (subpath) {
1990
+ files[`${subpath}/index.d.ts`] = content;
1991
+ }
1992
+ const refs = parseReferences(content);
1993
+ for (const ref of refs.paths) {
1994
+ const refUrl = new URL(ref, entryUrl).href;
1995
+ const refFiles = await this.fetchTypesRecursively(refUrl, undefined, visited);
1996
+ for (const [refPath, refContent] of Object.entries(refFiles)) {
1997
+ const normalizedRef = ref.replace(/^\.\//, "");
1998
+ if (refPath === "index.d.ts") {
1999
+ files[normalizedRef] = refContent;
2000
+ } else {
2001
+ const dir = normalizedRef.replace(/\.d\.ts$/, "");
2002
+ files[`${dir}/${refPath}`] = refContent;
2003
+ }
2637
2004
  }
2638
2005
  }
2639
- const buildOutput = captured.output;
2640
- return {
2641
- result,
2642
- error,
2643
- bundle: buildOutput?.bundle ?? null,
2644
- module: buildOutput?.module ?? null,
2645
- sandbox
2646
- };
2647
- };
2006
+ return files;
2007
+ }
2008
+ extractVersion(response, packageName, requestedVersion) {
2009
+ const esmId = response.headers.get("x-esm-id");
2010
+ if (esmId) {
2011
+ const match = esmId.match(new RegExp(`${escapeRegex(packageName)}@([^/]+)`));
2012
+ if (match?.[1]) {
2013
+ return match[1];
2014
+ }
2015
+ }
2016
+ const urlMatch = response.url.match(new RegExp(`${escapeRegex(packageName)}@([^/]+)`));
2017
+ if (urlMatch?.[1]) {
2018
+ return urlMatch[1];
2019
+ }
2020
+ return requestedVersion ?? "latest";
2021
+ }
2648
2022
  }
2649
-
2650
- // src/index.ts
2651
- if (typeof window !== "undefined" && typeof globalThis.process === "undefined") {
2652
- globalThis.process = {
2653
- env: {},
2654
- platform: "browser",
2655
- version: "v20.0.0",
2656
- browser: true,
2657
- cwd: () => "/",
2658
- nextTick: (fn) => setTimeout(fn, 0)
2023
+ function parseSpecifier(specifier) {
2024
+ if (specifier.startsWith("@")) {
2025
+ const parts = specifier.split("/");
2026
+ if (parts.length >= 2) {
2027
+ const packageName = `${parts[0]}/${parts[1]}`;
2028
+ const subpath = parts.length > 2 ? parts.slice(2).join("/") : undefined;
2029
+ return { packageName, subpath };
2030
+ }
2031
+ return { packageName: specifier, subpath: undefined };
2032
+ }
2033
+ const slashIndex = specifier.indexOf("/");
2034
+ if (slashIndex === -1) {
2035
+ return { packageName: specifier, subpath: undefined };
2036
+ }
2037
+ return {
2038
+ packageName: specifier.slice(0, slashIndex),
2039
+ subpath: specifier.slice(slashIndex + 1)
2659
2040
  };
2660
2041
  }
2042
+ function toTypesPackageName(packageName) {
2043
+ if (packageName.startsWith("@")) {
2044
+ return "@types/" + packageName.slice(1).replace("/", "__");
2045
+ }
2046
+ return `@types/${packageName}`;
2047
+ }
2048
+ function parseReferences(content) {
2049
+ const paths = [];
2050
+ const types = [];
2051
+ const pathRegex = /\/\/\/\s*<reference\s+path="([^"]+)"\s*\/>/g;
2052
+ let match;
2053
+ while ((match = pathRegex.exec(content)) !== null) {
2054
+ if (match[1])
2055
+ paths.push(match[1]);
2056
+ }
2057
+ const typesRegex = /\/\/\/\s*<reference\s+types="([^"]+)"\s*\/>/g;
2058
+ while ((match = typesRegex.exec(content)) !== null) {
2059
+ if (match[1])
2060
+ types.push(match[1]);
2061
+ }
2062
+ return { paths, types };
2063
+ }
2064
+ function makeCacheKey(packageName, subpath, version) {
2065
+ const base = version ? `${packageName}@${version}` : packageName;
2066
+ return subpath ? `${base}/${subpath}` : base;
2067
+ }
2068
+ function escapeRegex(str) {
2069
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2070
+ }
2661
2071
  export {
2662
- unregisterSharedModule,
2663
- uninstallPackage,
2664
- typecheck,
2665
- registerSharedModules,
2666
- loadModule,
2667
- loadExport,
2668
- loadDefault,
2669
- listPackages,
2670
- installPackage,
2671
- initBundler,
2672
- hasExport,
2673
- hasDefaultResources,
2674
- getPackageManifest,
2675
- getExportNames,
2676
- getDefaultResources,
2677
- getDefaultBrowserLibs,
2678
- formatDiagnosticsForAgent,
2072
+ wrapFilesystemForJustBash,
2073
+ formatSize,
2679
2074
  formatDiagnostics,
2680
- fetchAndCacheLibs,
2681
- createUninstallCommand,
2682
- createTscCommand,
2683
- createSharedResources,
2684
- createSandbox,
2685
- createRunCommand,
2686
- createListCommand,
2687
- createInstallCommand,
2075
+ formatBundleErrors,
2076
+ formatBuildFailure,
2077
+ createTypechecker,
2078
+ createSharedModuleRegistry,
2079
+ createSandlotCommand,
2080
+ createSandlot,
2688
2081
  createFilesystem,
2689
2082
  createDefaultCommands,
2690
- createBuilder,
2691
- createBuildCommand,
2692
- clearSharedModules,
2693
- clearDefaultResources,
2694
- bundleToUrl,
2695
- bundleAndImport,
2696
- bundle,
2697
- ModuleLoadError,
2083
+ Typechecker,
2084
+ SharedModuleRegistry,
2085
+ InMemoryTypesCache,
2698
2086
  Filesystem,
2699
- ExportNotFoundError
2087
+ EsmTypesResolver
2700
2088
  };