sandlot 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +2692 -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 +31 -132
  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 +1399 -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 +2646 -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 +525 -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 +526 -43
  50. package/src/commands/types.ts +82 -146
  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 +621 -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 +607 -0
  59. package/src/index.ts +104 -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 +668 -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,559 @@ 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
+ // src/commands/index.ts
711
+ function createSandlotCommand(sandboxRef) {
712
+ return defineCommand("sandlot", async (args, ctx) => {
713
+ const subcommand = args[0];
714
+ if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
715
+ return showHelp();
716
+ }
717
+ switch (subcommand) {
718
+ case "build":
719
+ return handleBuild(sandboxRef, args.slice(1));
720
+ case "typecheck":
721
+ case "tsc":
722
+ return handleTypecheck(sandboxRef, args.slice(1));
723
+ case "install":
724
+ case "add":
725
+ case "i":
726
+ return handleInstall(sandboxRef, args.slice(1));
727
+ case "uninstall":
728
+ case "remove":
729
+ case "rm":
730
+ return handleUninstall(sandboxRef, args.slice(1));
731
+ case "run":
732
+ return handleRun(sandboxRef, args.slice(1));
733
+ default:
734
+ return {
735
+ stdout: "",
736
+ stderr: `Unknown command: sandlot ${subcommand}
737
+
738
+ Run 'sandlot help' for available commands.
739
+ `,
740
+ exitCode: 1
741
+ };
742
+ }
743
+ });
744
+ }
745
+ function showHelp() {
746
+ return {
747
+ stdout: `sandlot - In-browser TypeScript sandbox
748
+
749
+ Usage: sandlot <command> [options]
750
+
751
+ Commands:
752
+ build Build the project (typecheck, bundle)
753
+ run Build and execute code
754
+ typecheck Type check without building (alias: tsc)
755
+ install Install packages (aliases: add, i)
756
+ uninstall Remove packages (aliases: remove, rm)
757
+ help Show this help message
758
+
759
+ Run 'sandlot <command> --help' for command-specific options.
760
+
761
+ Examples:
762
+ sandlot build
763
+ sandlot run
764
+ sandlot run --skip-typecheck --timeout 5000
765
+ sandlot install react react-dom
766
+ sandlot typecheck
767
+ `,
768
+ stderr: "",
769
+ exitCode: 0
770
+ };
771
+ }
772
+ async function handleBuild(sandboxRef, args) {
773
+ let entryPoint;
774
+ let skipTypecheck = false;
775
+ let minify = false;
776
+ let format = "esm";
777
+ for (let i = 0;i < args.length; i++) {
778
+ const arg = args[i];
779
+ if (arg === "--skip-typecheck" || arg === "-s") {
780
+ skipTypecheck = true;
781
+ } else if (arg === "--minify" || arg === "-m") {
782
+ minify = true;
783
+ } else if ((arg === "--format" || arg === "-f") && args[i + 1]) {
784
+ const f = args[++i].toLowerCase();
785
+ if (f === "esm" || f === "iife" || f === "cjs") {
786
+ format = f;
787
+ }
788
+ } else if ((arg === "--entry" || arg === "-e") && args[i + 1]) {
789
+ entryPoint = args[++i];
790
+ } else if (arg === "--help" || arg === "-h") {
791
+ return {
792
+ stdout: `Usage: sandlot build [options]
793
+
794
+ Options:
795
+ --entry, -e <path> Entry point (default: from package.json main)
796
+ --skip-typecheck, -s Skip type checking
797
+ --minify, -m Minify output
798
+ --format, -f <fmt> Output format (esm|iife|cjs)
799
+ --help, -h Show this help message
800
+
801
+ Examples:
802
+ sandlot build
803
+ sandlot build --entry /src/main.ts
804
+ sandlot build --skip-typecheck --minify
805
+ `,
806
+ stderr: "",
807
+ exitCode: 0
808
+ };
809
+ } else if (arg && !arg.startsWith("-") && !entryPoint) {
810
+ entryPoint = arg;
811
+ }
812
+ }
813
+ const result = await sandboxRef.build({
814
+ entryPoint,
815
+ skipTypecheck,
816
+ minify,
817
+ format
818
+ });
819
+ if (!result.success) {
820
+ let stderr = `Build failed`;
821
+ switch (result.phase) {
822
+ case "entry":
823
+ stderr = `Build failed: ${result.message}
824
+ `;
825
+ break;
826
+ case "typecheck":
827
+ if (result.diagnostics) {
828
+ const errors = result.diagnostics.filter((d) => d.severity === "error");
829
+ stderr = `Build failed: Type check errors
830
+
831
+ ${formatDiagnostics(errors)}
832
+ `;
833
+ } else {
834
+ stderr = `Build failed: Type check errors
835
+ `;
836
+ }
837
+ break;
838
+ case "bundle":
839
+ if (result.bundleErrors && result.bundleErrors.length > 0) {
840
+ stderr = `Build failed: Bundle errors
841
+
842
+ ${formatBundleErrors(result.bundleErrors)}
843
+ `;
844
+ } else {
845
+ stderr = `Build failed: Bundle error
846
+ `;
847
+ }
848
+ break;
849
+ default:
850
+ stderr = `Build failed: Unknown error
851
+ `;
852
+ }
853
+ return {
854
+ stdout: "",
855
+ stderr,
856
+ exitCode: 1
857
+ };
858
+ }
859
+ let output = `Build successful!
860
+ `;
861
+ output += `Size: ${formatSize(result.code.length)}
862
+ `;
863
+ output += `Files: ${result.includedFiles.length}
864
+ `;
865
+ if (result.warnings.length > 0) {
866
+ output += `
867
+ Warnings:
868
+ `;
869
+ for (const warning of result.warnings) {
870
+ if (warning.location) {
871
+ output += ` ${warning.location.file}:${warning.location.line}: ${warning.text}
872
+ `;
873
+ } else {
874
+ output += ` ${warning.text}
875
+ `;
876
+ }
877
+ }
878
+ }
879
+ return {
880
+ stdout: output,
881
+ stderr: "",
882
+ exitCode: 0
883
+ };
884
+ }
885
+ async function handleTypecheck(sandboxRef, args) {
886
+ let entryPoint;
887
+ for (let i = 0;i < args.length; i++) {
888
+ const arg = args[i];
889
+ if ((arg === "--entry" || arg === "-e") && args[i + 1]) {
890
+ entryPoint = args[++i];
891
+ } else if (arg === "--help" || arg === "-h") {
892
+ return {
893
+ stdout: `Usage: sandlot typecheck [options]
894
+
895
+ Options:
896
+ --entry, -e <path> Entry point (default: from package.json main)
897
+ --help, -h Show this help message
898
+
899
+ Aliases: sandlot tsc
900
+
901
+ Examples:
902
+ sandlot typecheck
903
+ sandlot typecheck --entry /src/main.ts
904
+ `,
905
+ stderr: "",
906
+ exitCode: 0
907
+ };
908
+ } else if (arg && !arg.startsWith("-") && !entryPoint) {
909
+ entryPoint = arg;
910
+ }
911
+ }
912
+ try {
913
+ const result = await sandboxRef.typecheck({ entryPoint });
914
+ if (!result.success) {
915
+ const errors = result.diagnostics.filter((d) => d.severity === "error");
916
+ const formatted = formatDiagnostics(errors);
917
+ return {
918
+ stdout: "",
919
+ stderr: `Type check failed:
920
+ ${formatted}
921
+ `,
922
+ exitCode: 1
923
+ };
924
+ }
925
+ const warnings = result.diagnostics.filter((d) => d.severity === "warning");
926
+ let output = `Type check passed.
927
+ `;
928
+ if (warnings.length > 0) {
929
+ output += `
930
+ Warnings:
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
+ };
556
947
  }
557
- get(name, version) {
558
- return this.cache.get(this.key(name, version)) ?? null;
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
+ };
559
965
  }
560
- set(name, version, types) {
561
- this.cache.set(this.key(name, version), types);
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
+ };
562
976
  }
563
- has(name, version) {
564
- return this.cache.has(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
+ }
565
997
  }
566
- delete(name, version) {
567
- return this.cache.delete(this.key(name, version));
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
+ };
568
1017
  }
569
- clear() {
570
- this.cache.clear();
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
+ }
571
1094
  }
572
- get size() {
573
- return this.cache.size;
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 = `Run failed: Build error in ${result.buildFailure.phase} phase`;
1106
+ if (result.buildFailure.message) {
1107
+ stderr += `
1108
+ ${result.buildFailure.message}`;
1109
+ }
1110
+ stderr += `
1111
+ `;
1112
+ } else {
1113
+ stderr = `Run failed: ${result.error ?? "Unknown error"}
1114
+ `;
1115
+ }
1116
+ let stdout = "";
1117
+ if (result.logs.length > 0) {
1118
+ stdout = result.logs.join(`
1119
+ `) + `
1120
+ `;
1121
+ }
1122
+ return {
1123
+ stdout,
1124
+ stderr,
1125
+ exitCode: 1
1126
+ };
1127
+ }
1128
+ let output = "";
1129
+ if (result.logs.length > 0) {
1130
+ output = result.logs.join(`
1131
+ `) + `
1132
+ `;
1133
+ }
1134
+ if (result.returnValue !== undefined) {
1135
+ const returnStr = typeof result.returnValue === "object" ? JSON.stringify(result.returnValue, null, 2) : String(result.returnValue);
1136
+ output += `[return] ${returnStr}
1137
+ `;
1138
+ }
1139
+ if (result.executionTimeMs !== undefined) {
1140
+ output += `
1141
+ Completed in ${result.executionTimeMs.toFixed(2)}ms
1142
+ `;
1143
+ }
1144
+ return {
1145
+ stdout: output,
1146
+ stderr: "",
1147
+ exitCode: 0
1148
+ };
1149
+ } catch (err) {
1150
+ const message = err instanceof Error ? err.message : String(err);
1151
+ return {
1152
+ stdout: "",
1153
+ stderr: `Run error: ${message}
1154
+ `,
1155
+ exitCode: 1
1156
+ };
574
1157
  }
575
1158
  }
1159
+ function createDefaultCommands(sandboxRef) {
1160
+ return [createSandlotCommand(sandboxRef)];
1161
+ }
1162
+
1163
+ // src/core/sandbox.ts
1164
+ var DEFAULT_ENTRY_POINT = "./index.ts";
1165
+ var TSCONFIG_PATH = "/tsconfig.json";
576
1166
  var PACKAGE_JSON_PATH = "/package.json";
1167
+ var DEFAULT_PACKAGE_JSON = {
1168
+ main: DEFAULT_ENTRY_POINT,
1169
+ dependencies: {}
1170
+ };
1171
+ var DEFAULT_TSCONFIG = {
1172
+ compilerOptions: {
1173
+ target: "ES2020",
1174
+ lib: ["ES2020", "DOM", "DOM.Iterable"],
1175
+ module: "ESNext",
1176
+ moduleResolution: "bundler",
1177
+ jsx: "react-jsx",
1178
+ strict: true,
1179
+ noEmit: true,
1180
+ esModuleInterop: true,
1181
+ skipLibCheck: true,
1182
+ resolveJsonModule: true,
1183
+ isolatedModules: true
1184
+ },
1185
+ include: ["**/*.ts", "**/*.tsx"],
1186
+ exclude: ["node_modules"]
1187
+ };
577
1188
  function parsePackageSpec(spec) {
578
1189
  if (spec.startsWith("@")) {
579
1190
  const slashIndex = spec.indexOf("/");
@@ -599,1755 +1210,358 @@ function parsePackageSpec(spec) {
599
1210
  version: spec.slice(atIndex + 1)
600
1211
  };
601
1212
  }
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;
1213
+ function readPackageJson(fs) {
688
1214
  try {
689
- const response = await fetch(typesUrl);
690
- if (!response.ok) {
691
- throw new Error(`Failed to fetch types: ${response.status}`);
1215
+ if (fs.exists(PACKAGE_JSON_PATH)) {
1216
+ const content = fs.readFile(PACKAGE_JSON_PATH);
1217
+ return JSON.parse(content);
692
1218
  }
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
- }
1219
+ } catch {}
1220
+ return {};
738
1221
  }
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);
1222
+ function getEntryPoint(fs) {
1223
+ const pkg = readPackageJson(fs);
1224
+ const main = pkg.main ?? DEFAULT_ENTRY_POINT;
1225
+ if (main.startsWith("/")) {
1226
+ return main;
759
1227
  }
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 {}
1228
+ if (main.startsWith("./")) {
1229
+ return "/" + main.slice(2);
790
1230
  }
791
- return { version: resolvedVersion, types };
1231
+ return "/" + main;
792
1232
  }
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: {} };
1233
+ function getInstalledPackages(fs) {
1234
+ const pkg = readPackageJson(fs);
1235
+ return pkg.dependencies ?? {};
804
1236
  }
805
- async function savePackageManifest(fs, manifest) {
1237
+ function saveInstalledPackages(fs, dependencies) {
806
1238
  let existing = {};
807
1239
  try {
808
- if (await fs.exists(PACKAGE_JSON_PATH)) {
809
- const content = await fs.readFile(PACKAGE_JSON_PATH);
1240
+ if (fs.exists(PACKAGE_JSON_PATH)) {
1241
+ const content = fs.readFile(PACKAGE_JSON_PATH);
810
1242
  existing = JSON.parse(content);
811
1243
  }
812
1244
  } catch {}
813
1245
  const updated = {
814
1246
  ...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
1247
+ dependencies
936
1248
  };
1249
+ fs.writeFile(PACKAGE_JSON_PATH, JSON.stringify(updated, null, 2));
937
1250
  }
938
- async function ensureDir(fs, path) {
1251
+ function ensureDir(fs, path) {
939
1252
  if (path === "/" || path === "")
940
1253
  return;
941
- if (await fs.exists(path)) {
942
- const stat = await fs.stat(path);
1254
+ if (fs.exists(path)) {
1255
+ const stat = fs.stat(path);
943
1256
  if (stat.isDirectory)
944
1257
  return;
945
1258
  }
946
1259
  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 });
1260
+ ensureDir(fs, parent);
1261
+ fs.mkdir(path);
967
1262
  }
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;
976
- }
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 };
1263
+ async function createSandboxImpl(fs, options, context) {
1264
+ const {
1265
+ bundler,
1266
+ typechecker,
1267
+ typesResolver,
1268
+ sharedModuleRegistry,
1269
+ executor
1270
+ } = context;
1271
+ let lastBuild = null;
1272
+ const onBuildCallbacks = new Set;
1273
+ if (options.onBuild) {
1274
+ onBuildCallbacks.add(options.onBuild);
1275
+ }
1276
+ if (options.initialFiles) {
1277
+ for (const [path, content] of Object.entries(options.initialFiles)) {
1278
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
1279
+ const dir = normalizedPath.substring(0, normalizedPath.lastIndexOf("/"));
1280
+ if (dir && dir !== "/") {
1281
+ ensureDir(fs, dir);
1282
+ }
1283
+ fs.writeFile(normalizedPath, content);
1284
+ }
1285
+ }
1286
+ if (!fs.exists(PACKAGE_JSON_PATH)) {
1287
+ fs.writeFile(PACKAGE_JSON_PATH, JSON.stringify(DEFAULT_PACKAGE_JSON, null, 2));
1288
+ }
1289
+ if (!fs.exists(TSCONFIG_PATH)) {
1290
+ fs.writeFile(TSCONFIG_PATH, JSON.stringify(DEFAULT_TSCONFIG, null, 2));
1291
+ }
1292
+ async function install(packageSpec) {
1293
+ const { name, version } = parsePackageSpec(packageSpec);
1294
+ let resolvedVersion = version ?? "latest";
1295
+ let typesInstalled = false;
1296
+ let typeFilesCount = 0;
1297
+ let typesError;
1298
+ const fromCache = false;
1299
+ if (typesResolver) {
1300
+ try {
1301
+ const typeFiles = await typesResolver.resolveTypes(name, version);
1302
+ const packageDir = `/node_modules/${name}`;
1303
+ ensureDir(fs, packageDir);
1304
+ let typesEntry = "index.d.ts";
1305
+ for (const [filePath, content] of Object.entries(typeFiles)) {
1306
+ const fullPath = filePath.startsWith("/") ? filePath : `${packageDir}/${filePath}`;
1307
+ const dir = fullPath.substring(0, fullPath.lastIndexOf("/"));
1308
+ ensureDir(fs, dir);
1309
+ fs.writeFile(fullPath, content);
1310
+ typeFilesCount++;
1311
+ const relativePath = fullPath.replace(`${packageDir}/`, "");
1312
+ if (relativePath === "index.d.ts") {
1313
+ typesEntry = "index.d.ts";
1314
+ } else if (typesEntry === "index.d.ts" && relativePath.endsWith(".d.ts") && !relativePath.includes("/")) {
1315
+ typesEntry = relativePath;
1316
+ }
1317
+ }
1318
+ typesInstalled = typeFilesCount > 0;
1319
+ if (typesInstalled) {
1320
+ const pkgJsonPath = `${packageDir}/package.json`;
1321
+ const pkgJson = {
1322
+ name,
1323
+ version: resolvedVersion,
1324
+ types: typesEntry,
1325
+ main: typesEntry.replace(/\.d\.ts$/, ".js")
1326
+ };
1327
+ fs.writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
1328
+ }
1329
+ if (!version) {
1330
+ resolvedVersion = "latest";
1331
+ }
1332
+ } catch (err) {
1333
+ typesError = err instanceof Error ? err.message : String(err);
1334
+ }
984
1335
  }
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;
1336
+ const dependencies = getInstalledPackages(fs);
1337
+ dependencies[name] = resolvedVersion;
1338
+ saveInstalledPackages(fs, dependencies);
1339
+ return {
1340
+ name,
1341
+ version: resolvedVersion,
1342
+ typesInstalled,
1343
+ typeFilesCount,
1344
+ typesError,
1345
+ fromCache
1346
+ };
1017
1347
  }
1018
- registerAll(modules) {
1019
- for (const [id, mod] of Object.entries(modules)) {
1020
- this.register(id, mod);
1348
+ async function uninstall(packageName) {
1349
+ const dependencies = getInstalledPackages(fs);
1350
+ if (!(packageName in dependencies)) {
1351
+ return { name: packageName, removed: false };
1352
+ }
1353
+ delete dependencies[packageName];
1354
+ saveInstalledPackages(fs, dependencies);
1355
+ const typesPath = `/node_modules/${packageName}`;
1356
+ if (fs.exists(typesPath)) {
1357
+ fs.rm(typesPath, { recursive: true, force: true });
1358
+ }
1359
+ return { name: packageName, removed: true };
1360
+ }
1361
+ async function build(buildOptions) {
1362
+ const buildEntryPoint = buildOptions?.entryPoint ?? getEntryPoint(fs);
1363
+ const skipTypecheck = buildOptions?.skipTypecheck ?? false;
1364
+ const minify = buildOptions?.minify ?? false;
1365
+ const format = buildOptions?.format ?? "esm";
1366
+ if (!fs.exists(buildEntryPoint)) {
1367
+ return {
1368
+ success: false,
1369
+ phase: "entry",
1370
+ message: `Entry point not found: ${buildEntryPoint}`
1371
+ };
1021
1372
  }
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.`);
1373
+ if (!skipTypecheck && typechecker) {
1374
+ const typecheckResult = await typechecker.typecheck({
1375
+ fs,
1376
+ entryPoint: buildEntryPoint,
1377
+ tsconfigPath: TSCONFIG_PATH
1378
+ });
1379
+ if (!typecheckResult.success) {
1380
+ return {
1381
+ success: false,
1382
+ phase: "typecheck",
1383
+ diagnostics: typecheckResult.diagnostics
1384
+ };
1385
+ }
1032
1386
  }
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);
1387
+ const installedPackages = getInstalledPackages(fs);
1388
+ const bundleResult = await bundler.bundle({
1389
+ fs,
1390
+ entryPoint: buildEntryPoint,
1391
+ installedPackages,
1392
+ sharedModules: sharedModuleRegistry?.list() ?? [],
1393
+ sharedModuleRegistry: sharedModuleRegistry ?? undefined,
1394
+ format,
1395
+ minify
1396
+ });
1397
+ if (!bundleResult.success) {
1398
+ return {
1399
+ success: false,
1400
+ phase: "bundle",
1401
+ bundleErrors: bundleResult.errors,
1402
+ bundleWarnings: bundleResult.warnings
1403
+ };
1063
1404
  }
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
- });
1405
+ const output = {
1406
+ success: true,
1407
+ code: bundleResult.code,
1408
+ includedFiles: bundleResult.includedFiles,
1409
+ warnings: bundleResult.warnings
1410
+ };
1411
+ lastBuild = output;
1412
+ for (const callback of onBuildCallbacks) {
1413
+ try {
1414
+ await callback(output);
1415
+ } catch (err) {
1416
+ console.error("[sandlot] onBuild callback error:", err);
1417
+ }
1178
1418
  }
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;
1419
+ return output;
1211
1420
  }
1212
- for (const moduleId of sharedModuleIds) {
1213
- if (importPath === moduleId || importPath.startsWith(moduleId + "/")) {
1214
- return importPath;
1421
+ async function typecheck(typecheckOptions) {
1422
+ if (!typechecker) {
1423
+ return { success: true, diagnostics: [] };
1215
1424
  }
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" };
1425
+ const checkEntryPoint = typecheckOptions?.entryPoint ?? getEntryPoint(fs);
1426
+ if (!fs.exists(checkEntryPoint)) {
1427
+ return {
1428
+ success: false,
1429
+ diagnostics: [
1430
+ {
1431
+ message: `Entry point not found: ${checkEntryPoint}`,
1432
+ severity: "error"
1283
1433
  }
1434
+ ]
1435
+ };
1436
+ }
1437
+ return typechecker.typecheck({
1438
+ fs,
1439
+ entryPoint: checkEntryPoint,
1440
+ tsconfigPath: TSCONFIG_PATH
1441
+ });
1442
+ }
1443
+ async function run(runOptions) {
1444
+ if (!executor) {
1445
+ throw new Error("[sandlot] No executor configured. Provide an executor when creating Sandlot to use run().");
1446
+ }
1447
+ const buildResult = await build({
1448
+ entryPoint: runOptions?.entryPoint,
1449
+ skipTypecheck: runOptions?.skipTypecheck
1450
+ });
1451
+ if (!buildResult.success) {
1452
+ return {
1453
+ success: false,
1454
+ logs: [],
1455
+ error: buildResult.message ?? `Build failed in ${buildResult.phase} phase`,
1456
+ buildFailure: {
1457
+ phase: buildResult.phase,
1458
+ message: buildResult.message
1284
1459
  }
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
- });
1460
+ };
1313
1461
  }
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
- `);
1462
+ const executeResult = await executor.execute(buildResult.code, {
1463
+ entryExport: runOptions?.entryExport ?? "main",
1464
+ context: runOptions?.context,
1465
+ timeout: runOptions?.timeout
1466
+ });
1467
+ return {
1468
+ success: executeResult.success,
1469
+ logs: executeResult.logs,
1470
+ returnValue: executeResult.returnValue,
1471
+ error: executeResult.error,
1472
+ executionTimeMs: executeResult.executionTimeMs
1473
+ };
1321
1474
  }
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 {
1475
+ const sandboxRef = {
1327
1476
  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({
1347
- 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)
1477
+ install,
1478
+ uninstall,
1479
+ build,
1480
+ typecheck,
1481
+ run
1373
1482
  };
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 {}
1483
+ let bashInstance = null;
1484
+ function getBash() {
1485
+ if (!bashInstance) {
1486
+ const commands = createDefaultCommands(sandboxRef);
1487
+ bashInstance = new Bash({
1488
+ cwd: "/",
1489
+ fs: wrapFilesystemForJustBash(fs),
1490
+ customCommands: commands
1491
+ });
1460
1492
  }
1493
+ return bashInstance;
1461
1494
  }
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()
1495
+ async function exec(command) {
1496
+ const bash = getBash();
1497
+ const result = await bash.exec(command);
1498
+ return {
1499
+ exitCode: result.exitCode,
1500
+ stdout: result.stdout,
1501
+ stderr: result.stderr
1470
1502
  };
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
1503
  }
1577
1504
  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
1505
  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
1506
+ exec,
1507
+ get lastBuild() {
1508
+ return lastBuild;
1509
+ },
1510
+ getState() {
1511
+ return { files: fs.getFiles() };
1512
+ },
1513
+ onBuild(callback) {
1514
+ onBuildCallbacks.add(callback);
1515
+ return () => {
1516
+ onBuildCallbacks.delete(callback);
2329
1517
  };
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
- ];
1518
+ },
1519
+ install,
1520
+ uninstall,
1521
+ build,
1522
+ typecheck,
1523
+ run,
1524
+ readFile: (path) => fs.readFile(path),
1525
+ writeFile: (path, content) => fs.writeFile(path, content)
1526
+ };
2343
1527
  }
2344
1528
 
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"];
1529
+ // src/core/sandlot.ts
1530
+ function createSandlot(options) {
1531
+ const {
1532
+ bundler,
1533
+ typechecker,
1534
+ typesResolver,
1535
+ executor,
1536
+ sharedModules,
1537
+ sandboxDefaults = {}
1538
+ } = options;
1539
+ const sharedModuleRegistry = createSharedModuleRegistry(sharedModules);
1540
+ const sandboxContext = {
1541
+ bundler,
1542
+ typechecker,
1543
+ typesResolver,
1544
+ sharedModuleRegistry,
1545
+ executor
1546
+ };
1547
+ return {
1548
+ async createSandbox(sandboxOptions = {}) {
1549
+ const fs = Filesystem.create({
1550
+ maxSizeBytes: sandboxOptions.maxFilesystemSize ?? sandboxDefaults.maxFilesystemSize
1551
+ });
1552
+ return createSandboxImpl(fs, sandboxOptions, sandboxContext);
1553
+ },
1554
+ get sharedModules() {
1555
+ return sharedModuleRegistry;
1556
+ }
1557
+ };
2350
1558
  }
1559
+ // src/core/typechecker.ts
1560
+ import ts from "typescript";
1561
+ var TS_VERSION = "5.9.3";
1562
+ var DEFAULT_CDN_BASE = `https://cdn.jsdelivr.net/npm/typescript@${TS_VERSION}/lib`;
1563
+ var DEFAULT_LIBS = ["es2020", "dom", "dom.iterable"];
1564
+ var LIB_PATH_PREFIX = "/node_modules/typescript/lib/";
2351
1565
  function parseLibReferences(content) {
2352
1566
  const refs = [];
2353
1567
  const regex = /\/\/\/\s*<reference\s+lib="([^"]+)"\s*\/>/g;
@@ -2362,20 +1576,16 @@ function parseLibReferences(content) {
2362
1576
  function libNameToFileName(name) {
2363
1577
  return `lib.${name}.d.ts`;
2364
1578
  }
2365
- function extractLibName(filePath) {
2366
- const match = filePath.match(/lib\.([^/]+)\.d\.ts$/);
2367
- return match?.[1] ?? null;
2368
- }
2369
- async function fetchLibFile(name) {
1579
+ async function fetchLibFile(name, baseUrl) {
2370
1580
  const fileName = libNameToFileName(name);
2371
- const url = `${CDN_BASE}/${fileName}`;
1581
+ const url = `${baseUrl}/${fileName}`;
2372
1582
  const response = await fetch(url);
2373
1583
  if (!response.ok) {
2374
1584
  throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
2375
1585
  }
2376
1586
  return response.text();
2377
1587
  }
2378
- async function fetchAllLibs(libs) {
1588
+ async function fetchAllLibs(libs, baseUrl) {
2379
1589
  const result = new Map;
2380
1590
  const pending = new Set(libs);
2381
1591
  const fetched = new Set;
@@ -2388,10 +1598,10 @@ async function fetchAllLibs(libs) {
2388
1598
  }
2389
1599
  fetched.add(name);
2390
1600
  try {
2391
- const content = await fetchLibFile(name);
1601
+ const content = await fetchLibFile(name, baseUrl);
2392
1602
  return { name, content };
2393
1603
  } catch (err) {
2394
- console.warn(`Failed to fetch lib.${name}.d.ts:`, err);
1604
+ console.warn(`[typechecker] Failed to fetch lib.${name}.d.ts:`, err);
2395
1605
  return { name, content: null };
2396
1606
  }
2397
1607
  }));
@@ -2409,292 +1619,471 @@ async function fetchAllLibs(libs) {
2409
1619
  }
2410
1620
  return result;
2411
1621
  }
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;
1622
+ function normalizePath(path) {
1623
+ if (!path.startsWith("/")) {
1624
+ return "/" + path;
2434
1625
  }
2435
- clear() {
2436
- memoryCache = null;
1626
+ return path;
1627
+ }
1628
+ function getLibContent(fileName, libFiles) {
1629
+ const match = fileName.match(/lib\.([^/]+)\.d\.ts$/);
1630
+ if (match?.[1]) {
1631
+ return libFiles.get(match[1]);
2437
1632
  }
1633
+ return;
2438
1634
  }
2439
- async function fetchAndCacheLibs(libs = getDefaultBrowserLibs()) {
2440
- const cache = new LibCache;
2441
- return cache.getOrFetch(libs);
1635
+ function createCompilerHost(fs, libFiles, options) {
1636
+ return {
1637
+ getSourceFile(fileName, languageVersion, onError) {
1638
+ const normalizedPath = normalizePath(fileName);
1639
+ try {
1640
+ if (fs.exists(normalizedPath)) {
1641
+ const stat = fs.stat(normalizedPath);
1642
+ if (stat.isFile) {
1643
+ const content = fs.readFile(normalizedPath);
1644
+ return ts.createSourceFile(normalizedPath, content, languageVersion, true);
1645
+ }
1646
+ }
1647
+ } catch {}
1648
+ try {
1649
+ if (fs.exists(fileName)) {
1650
+ const stat = fs.stat(fileName);
1651
+ if (stat.isFile) {
1652
+ const content = fs.readFile(fileName);
1653
+ return ts.createSourceFile(fileName, content, languageVersion, true);
1654
+ }
1655
+ }
1656
+ } catch {}
1657
+ const libContent = getLibContent(fileName, libFiles);
1658
+ if (libContent !== undefined) {
1659
+ return ts.createSourceFile(fileName, libContent, languageVersion, true);
1660
+ }
1661
+ if (onError) {
1662
+ onError(`File not found: ${fileName}`);
1663
+ }
1664
+ return;
1665
+ },
1666
+ getDefaultLibFileName(opts) {
1667
+ return LIB_PATH_PREFIX + ts.getDefaultLibFileName(opts);
1668
+ },
1669
+ writeFile() {},
1670
+ getCurrentDirectory() {
1671
+ return "/";
1672
+ },
1673
+ getCanonicalFileName(fileName) {
1674
+ return fileName;
1675
+ },
1676
+ useCaseSensitiveFileNames() {
1677
+ return true;
1678
+ },
1679
+ getNewLine() {
1680
+ return `
1681
+ `;
1682
+ },
1683
+ fileExists(fileName) {
1684
+ const normalizedPath = normalizePath(fileName);
1685
+ try {
1686
+ if (fs.exists(normalizedPath)) {
1687
+ return fs.stat(normalizedPath).isFile;
1688
+ }
1689
+ } catch {}
1690
+ return getLibContent(fileName, libFiles) !== undefined;
1691
+ },
1692
+ readFile(fileName) {
1693
+ const normalizedPath = normalizePath(fileName);
1694
+ try {
1695
+ if (fs.exists(normalizedPath)) {
1696
+ return fs.readFile(normalizedPath);
1697
+ }
1698
+ } catch {}
1699
+ return getLibContent(fileName, libFiles);
1700
+ },
1701
+ directoryExists(directoryName) {
1702
+ const normalizedDir = normalizePath(directoryName);
1703
+ try {
1704
+ if (fs.exists(normalizedDir)) {
1705
+ return fs.stat(normalizedDir).isDirectory;
1706
+ }
1707
+ } catch {}
1708
+ if (normalizedDir === "/node_modules/typescript/lib" || normalizedDir === "/node_modules/typescript" || normalizedDir === "/node_modules") {
1709
+ return libFiles.size > 0;
1710
+ }
1711
+ return false;
1712
+ },
1713
+ getDirectories(path) {
1714
+ const normalizedPath = normalizePath(path);
1715
+ try {
1716
+ if (!fs.exists(normalizedPath)) {
1717
+ return [];
1718
+ }
1719
+ const stat = fs.stat(normalizedPath);
1720
+ if (!stat.isDirectory) {
1721
+ return [];
1722
+ }
1723
+ const entries = fs.readdir(normalizedPath);
1724
+ const dirs = [];
1725
+ for (const name of entries) {
1726
+ const childPath = normalizedPath === "/" ? `/${name}` : `${normalizedPath}/${name}`;
1727
+ try {
1728
+ if (fs.stat(childPath).isDirectory) {
1729
+ dirs.push(name);
1730
+ }
1731
+ } catch {}
1732
+ }
1733
+ return dirs;
1734
+ } catch {
1735
+ return [];
1736
+ }
1737
+ },
1738
+ realpath(path) {
1739
+ return path;
1740
+ },
1741
+ getEnvironmentVariable() {
1742
+ return;
1743
+ }
1744
+ };
2442
1745
  }
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]);
1746
+ function getDefaultCompilerOptions() {
2451
1747
  return {
2452
- libFiles,
2453
- bundlerReady: Promise.resolve(),
2454
- typesCache
1748
+ target: ts.ScriptTarget.ES2020,
1749
+ module: ts.ModuleKind.ESNext,
1750
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
1751
+ esModuleInterop: true,
1752
+ strict: true,
1753
+ skipLibCheck: true,
1754
+ noEmit: true,
1755
+ jsx: ts.JsxEmit.ReactJSX,
1756
+ allowJs: true,
1757
+ resolveJsonModule: true,
1758
+ lib: ["lib.es2020.d.ts", "lib.dom.d.ts", "lib.dom.iterable.d.ts"]
2455
1759
  };
2456
1760
  }
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
- });
1761
+ function parseTsConfig(fs, configPath) {
1762
+ try {
1763
+ if (!fs.exists(configPath)) {
1764
+ return getDefaultCompilerOptions();
1765
+ }
1766
+ const configText = fs.readFile(configPath);
1767
+ const { config, error } = ts.parseConfigFileTextToJson(configPath, configText);
1768
+ if (error) {
1769
+ console.warn("[typechecker] Error parsing tsconfig:", error.messageText);
1770
+ return getDefaultCompilerOptions();
1771
+ }
1772
+ const parseHost = {
1773
+ useCaseSensitiveFileNames: true,
1774
+ readDirectory: () => [],
1775
+ fileExists: (path) => fs.exists(normalizePath(path)),
1776
+ readFile: (path) => {
1777
+ try {
1778
+ return fs.readFile(normalizePath(path));
1779
+ } catch {
1780
+ return;
1781
+ }
1782
+ }
1783
+ };
1784
+ const parsed = ts.parseJsonConfigFileContent(config, parseHost, "/", undefined, configPath);
1785
+ if (parsed.errors.length > 0) {
1786
+ console.warn("[typechecker] tsconfig parse errors:", parsed.errors.map((e) => e.messageText));
1787
+ }
1788
+ return {
1789
+ ...parsed.options,
1790
+ noEmit: true
1791
+ };
1792
+ } catch (err) {
1793
+ console.warn("[typechecker] Error reading tsconfig:", err);
1794
+ return getDefaultCompilerOptions();
2468
1795
  }
2469
- return defaultResourcesPromise;
2470
1796
  }
2471
- function clearDefaultResources() {
2472
- defaultResourcesInstance = null;
2473
- defaultResourcesPromise = null;
1797
+ function categoryToSeverity(category) {
1798
+ switch (category) {
1799
+ case ts.DiagnosticCategory.Error:
1800
+ return "error";
1801
+ case ts.DiagnosticCategory.Warning:
1802
+ return "warning";
1803
+ default:
1804
+ return "info";
1805
+ }
2474
1806
  }
2475
- function hasDefaultResources() {
2476
- return defaultResourcesInstance !== null;
1807
+ function convertDiagnostic(diag) {
1808
+ let file;
1809
+ let line;
1810
+ let column;
1811
+ if (diag.file && diag.start !== undefined) {
1812
+ file = diag.file.fileName;
1813
+ const pos = diag.file.getLineAndCharacterOfPosition(diag.start);
1814
+ line = pos.line + 1;
1815
+ column = pos.character + 1;
1816
+ }
1817
+ return {
1818
+ file,
1819
+ line,
1820
+ column,
1821
+ message: ts.flattenDiagnosticMessageText(diag.messageText, `
1822
+ `),
1823
+ severity: categoryToSeverity(diag.category)
1824
+ };
2477
1825
  }
2478
1826
 
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
- }
1827
+ class Typechecker {
1828
+ options;
1829
+ libCache = new Map;
1830
+ initPromise = null;
1831
+ constructor(options = {}) {
1832
+ this.options = options;
1833
+ }
1834
+ async initialize() {
1835
+ if (this.initPromise) {
1836
+ await this.initPromise;
1837
+ return;
2489
1838
  }
2490
- await Promise.all(promises);
2491
- };
2492
- on(callback) {
2493
- this.listeners.add(callback);
2494
- return () => {
2495
- this.listeners.delete(callback);
1839
+ if (this.libCache.size > 0) {
1840
+ return;
1841
+ }
1842
+ this.initPromise = this.fetchLibs();
1843
+ await this.initPromise;
1844
+ }
1845
+ async fetchLibs() {
1846
+ const libs = this.options.libs ?? DEFAULT_LIBS;
1847
+ const baseUrl = this.options.libsBaseUrl ?? DEFAULT_CDN_BASE;
1848
+ console.log(`[typechecker] Fetching TypeScript libs: ${libs.join(", ")}...`);
1849
+ const fetched = await fetchAllLibs(libs, baseUrl);
1850
+ console.log(`[typechecker] Fetched ${fetched.size} lib files`);
1851
+ this.libCache = fetched;
1852
+ }
1853
+ async typecheck(options) {
1854
+ await this.initialize();
1855
+ const { fs, entryPoint, tsconfigPath = "/tsconfig.json" } = options;
1856
+ const normalizedEntry = normalizePath(entryPoint);
1857
+ if (!fs.exists(normalizedEntry)) {
1858
+ return {
1859
+ success: false,
1860
+ diagnostics: [
1861
+ {
1862
+ file: normalizedEntry,
1863
+ message: `Entry point not found: ${normalizedEntry}`,
1864
+ severity: "error"
1865
+ }
1866
+ ]
1867
+ };
1868
+ }
1869
+ const compilerOptions = parseTsConfig(fs, tsconfigPath);
1870
+ const host = createCompilerHost(fs, this.libCache, compilerOptions);
1871
+ const program = ts.createProgram([normalizedEntry], compilerOptions, host);
1872
+ const allDiagnostics = [
1873
+ ...program.getSyntacticDiagnostics(),
1874
+ ...program.getSemanticDiagnostics(),
1875
+ ...program.getDeclarationDiagnostics()
1876
+ ];
1877
+ const diagnostics = allDiagnostics.map(convertDiagnostic);
1878
+ const success = !diagnostics.some((d) => d.severity === "error");
1879
+ return {
1880
+ success,
1881
+ diagnostics
2496
1882
  };
2497
1883
  }
2498
1884
  }
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
- }));
1885
+ function createTypechecker(options) {
1886
+ return new Typechecker(options);
1887
+ }
1888
+ // src/core/esm-types-resolver.ts
1889
+ class InMemoryTypesCache {
1890
+ cache = new Map;
1891
+ async get(key) {
1892
+ return this.cache.get(key) ?? null;
2534
1893
  }
2535
- const buildEmitter = new BuildEmitter;
2536
- let lastBuild = null;
2537
- buildEmitter.on((result) => {
2538
- lastBuild = result;
2539
- });
2540
- if (onBuildCallback) {
2541
- buildEmitter.on(onBuildCallback);
1894
+ async set(key, value) {
1895
+ this.cache.set(key, value);
1896
+ }
1897
+ async has(key) {
1898
+ return this.cache.has(key);
1899
+ }
1900
+ clear() {
1901
+ this.cache.clear();
2542
1902
  }
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
1903
  }
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);
1904
+
1905
+ class EsmTypesResolver {
1906
+ baseUrl;
1907
+ cache;
1908
+ tryTypesPackages;
1909
+ constructor(options = {}) {
1910
+ this.baseUrl = options.baseUrl ?? "https://esm.sh";
1911
+ this.cache = options.cache ?? null;
1912
+ this.tryTypesPackages = options.tryTypesPackages ?? true;
1913
+ }
1914
+ async resolveTypes(specifier, version) {
1915
+ const resolved = await this.resolve(specifier, version);
1916
+ if (!resolved) {
1917
+ return {};
1918
+ }
1919
+ const result = {};
1920
+ const pkgPath = `/node_modules/${resolved.packageName}`;
1921
+ for (const [relativePath, content] of Object.entries(resolved.files)) {
1922
+ result[`${pkgPath}/${relativePath}`] = content;
1923
+ }
1924
+ return result;
1925
+ }
1926
+ async resolve(specifier, version) {
1927
+ const { packageName, subpath } = parseSpecifier(specifier);
1928
+ const cacheKey = makeCacheKey(packageName, subpath, version);
1929
+ if (this.cache) {
1930
+ const cached = await this.cache.get(cacheKey);
1931
+ if (cached) {
1932
+ return cached;
2596
1933
  }
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
- }
1934
+ }
1935
+ let result = await this.tryResolve(packageName, subpath, version);
1936
+ if (!result && this.tryTypesPackages && !packageName.startsWith("@types/")) {
1937
+ const typesPackageName = toTypesPackageName(packageName);
1938
+ result = await this.tryResolve(typesPackageName, subpath, version);
1939
+ if (result) {
1940
+ result.fromTypesPackage = true;
1941
+ result.packageName = packageName;
2605
1942
  }
2606
1943
  }
2607
- let result;
2608
- let error = null;
1944
+ if (result && this.cache) {
1945
+ await this.cache.set(cacheKey, result);
1946
+ }
1947
+ return result;
1948
+ }
1949
+ async tryResolve(packageName, subpath, version) {
2609
1950
  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;
1951
+ const versionSuffix = version ? `@${version}` : "";
1952
+ const pathSuffix = subpath ? `/${subpath}` : "";
1953
+ const url = `${this.baseUrl}/${packageName}${versionSuffix}${pathSuffix}`;
1954
+ const response = await fetch(url, { method: "HEAD" });
1955
+ if (!response.ok) {
1956
+ return null;
2627
1957
  }
2628
- } catch (err) {
2629
- error = err instanceof Error ? err : new Error(String(err));
2630
- } finally {
2631
- if (timeoutId !== undefined) {
2632
- clearTimeout(timeoutId);
1958
+ const resolvedVersion = this.extractVersion(response, packageName, version);
1959
+ const typesHeader = response.headers.get("X-TypeScript-Types");
1960
+ if (!typesHeader) {
1961
+ return null;
2633
1962
  }
2634
- unsubscribe();
2635
- if (callOptions?.validate) {
2636
- sandbox.clearValidation();
1963
+ const typesUrl = new URL(typesHeader, response.url).href;
1964
+ const files = await this.fetchTypesRecursively(typesUrl, subpath);
1965
+ if (Object.keys(files).length === 0) {
1966
+ return null;
1967
+ }
1968
+ return {
1969
+ packageName,
1970
+ version: resolvedVersion,
1971
+ files,
1972
+ fromTypesPackage: packageName.startsWith("@types/")
1973
+ };
1974
+ } catch {
1975
+ return null;
1976
+ }
1977
+ }
1978
+ async fetchTypesRecursively(entryUrl, subpath, visited = new Set) {
1979
+ if (visited.has(entryUrl)) {
1980
+ return {};
1981
+ }
1982
+ visited.add(entryUrl);
1983
+ const response = await fetch(entryUrl);
1984
+ if (!response.ok) {
1985
+ return {};
1986
+ }
1987
+ const content = await response.text();
1988
+ const files = {};
1989
+ const fileName = subpath ? `${subpath}.d.ts` : "index.d.ts";
1990
+ files[fileName] = content;
1991
+ if (subpath) {
1992
+ files[`${subpath}/index.d.ts`] = content;
1993
+ }
1994
+ const refs = parseReferences(content);
1995
+ for (const ref of refs.paths) {
1996
+ const refUrl = new URL(ref, entryUrl).href;
1997
+ const refFiles = await this.fetchTypesRecursively(refUrl, undefined, visited);
1998
+ for (const [refPath, refContent] of Object.entries(refFiles)) {
1999
+ const normalizedRef = ref.replace(/^\.\//, "");
2000
+ if (refPath === "index.d.ts") {
2001
+ files[normalizedRef] = refContent;
2002
+ } else {
2003
+ const dir = normalizedRef.replace(/\.d\.ts$/, "");
2004
+ files[`${dir}/${refPath}`] = refContent;
2005
+ }
2637
2006
  }
2638
2007
  }
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
- };
2008
+ return files;
2009
+ }
2010
+ extractVersion(response, packageName, requestedVersion) {
2011
+ const esmId = response.headers.get("x-esm-id");
2012
+ if (esmId) {
2013
+ const match = esmId.match(new RegExp(`${escapeRegex(packageName)}@([^/]+)`));
2014
+ if (match?.[1]) {
2015
+ return match[1];
2016
+ }
2017
+ }
2018
+ const urlMatch = response.url.match(new RegExp(`${escapeRegex(packageName)}@([^/]+)`));
2019
+ if (urlMatch?.[1]) {
2020
+ return urlMatch[1];
2021
+ }
2022
+ return requestedVersion ?? "latest";
2023
+ }
2648
2024
  }
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)
2025
+ function parseSpecifier(specifier) {
2026
+ if (specifier.startsWith("@")) {
2027
+ const parts = specifier.split("/");
2028
+ if (parts.length >= 2) {
2029
+ const packageName = `${parts[0]}/${parts[1]}`;
2030
+ const subpath = parts.length > 2 ? parts.slice(2).join("/") : undefined;
2031
+ return { packageName, subpath };
2032
+ }
2033
+ return { packageName: specifier, subpath: undefined };
2034
+ }
2035
+ const slashIndex = specifier.indexOf("/");
2036
+ if (slashIndex === -1) {
2037
+ return { packageName: specifier, subpath: undefined };
2038
+ }
2039
+ return {
2040
+ packageName: specifier.slice(0, slashIndex),
2041
+ subpath: specifier.slice(slashIndex + 1)
2659
2042
  };
2660
2043
  }
2044
+ function toTypesPackageName(packageName) {
2045
+ if (packageName.startsWith("@")) {
2046
+ return "@types/" + packageName.slice(1).replace("/", "__");
2047
+ }
2048
+ return `@types/${packageName}`;
2049
+ }
2050
+ function parseReferences(content) {
2051
+ const paths = [];
2052
+ const types = [];
2053
+ const pathRegex = /\/\/\/\s*<reference\s+path="([^"]+)"\s*\/>/g;
2054
+ let match;
2055
+ while ((match = pathRegex.exec(content)) !== null) {
2056
+ if (match[1])
2057
+ paths.push(match[1]);
2058
+ }
2059
+ const typesRegex = /\/\/\/\s*<reference\s+types="([^"]+)"\s*\/>/g;
2060
+ while ((match = typesRegex.exec(content)) !== null) {
2061
+ if (match[1])
2062
+ types.push(match[1]);
2063
+ }
2064
+ return { paths, types };
2065
+ }
2066
+ function makeCacheKey(packageName, subpath, version) {
2067
+ const base = version ? `${packageName}@${version}` : packageName;
2068
+ return subpath ? `${base}/${subpath}` : base;
2069
+ }
2070
+ function escapeRegex(str) {
2071
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2072
+ }
2661
2073
  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,
2074
+ wrapFilesystemForJustBash,
2075
+ formatSize,
2679
2076
  formatDiagnostics,
2680
- fetchAndCacheLibs,
2681
- createUninstallCommand,
2682
- createTscCommand,
2683
- createSharedResources,
2684
- createSandbox,
2685
- createRunCommand,
2686
- createListCommand,
2687
- createInstallCommand,
2077
+ formatBundleErrors,
2078
+ createTypechecker,
2079
+ createSharedModuleRegistry,
2080
+ createSandlotCommand,
2081
+ createSandlot,
2688
2082
  createFilesystem,
2689
2083
  createDefaultCommands,
2690
- createBuilder,
2691
- createBuildCommand,
2692
- clearSharedModules,
2693
- clearDefaultResources,
2694
- bundleToUrl,
2695
- bundleAndImport,
2696
- bundle,
2697
- ModuleLoadError,
2084
+ Typechecker,
2085
+ SharedModuleRegistry,
2086
+ InMemoryTypesCache,
2698
2087
  Filesystem,
2699
- ExportNotFoundError
2088
+ EsmTypesResolver
2700
2089
  };