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