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