sandlot 0.1.1 → 0.1.3

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 (59) hide show
  1. package/README.md +145 -518
  2. package/dist/build-emitter.d.ts +47 -0
  3. package/dist/build-emitter.d.ts.map +1 -0
  4. package/dist/builder.d.ts +370 -0
  5. package/dist/builder.d.ts.map +1 -0
  6. package/dist/bundler.d.ts +3 -3
  7. package/dist/bundler.d.ts.map +1 -1
  8. package/dist/commands/compile.d.ts +13 -0
  9. package/dist/commands/compile.d.ts.map +1 -0
  10. package/dist/commands/index.d.ts +17 -0
  11. package/dist/commands/index.d.ts.map +1 -0
  12. package/dist/commands/packages.d.ts +17 -0
  13. package/dist/commands/packages.d.ts.map +1 -0
  14. package/dist/commands/run.d.ts +40 -0
  15. package/dist/commands/run.d.ts.map +1 -0
  16. package/dist/commands/types.d.ts +141 -0
  17. package/dist/commands/types.d.ts.map +1 -0
  18. package/dist/fs.d.ts +60 -42
  19. package/dist/fs.d.ts.map +1 -1
  20. package/dist/index.d.ts +5 -4
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +304 -491
  23. package/dist/internal.d.ts +5 -0
  24. package/dist/internal.d.ts.map +1 -1
  25. package/dist/internal.js +174 -95
  26. package/dist/runner.d.ts +314 -0
  27. package/dist/runner.d.ts.map +1 -0
  28. package/dist/sandbox-manager.d.ts +45 -33
  29. package/dist/sandbox-manager.d.ts.map +1 -1
  30. package/dist/sandbox.d.ts +144 -70
  31. package/dist/sandbox.d.ts.map +1 -1
  32. package/dist/shared-modules.d.ts +22 -3
  33. package/dist/shared-modules.d.ts.map +1 -1
  34. package/dist/shared-resources.d.ts +0 -3
  35. package/dist/shared-resources.d.ts.map +1 -1
  36. package/dist/typechecker.d.ts +1 -1
  37. package/package.json +3 -17
  38. package/src/build-emitter.ts +64 -0
  39. package/src/builder.ts +498 -0
  40. package/src/bundler.ts +86 -57
  41. package/src/commands/compile.ts +236 -0
  42. package/src/commands/index.ts +51 -0
  43. package/src/commands/packages.ts +154 -0
  44. package/src/commands/run.ts +245 -0
  45. package/src/commands/types.ts +172 -0
  46. package/src/fs.ts +90 -216
  47. package/src/index.ts +34 -12
  48. package/src/internal.ts +5 -2
  49. package/src/sandbox.ts +214 -220
  50. package/src/shared-modules.ts +74 -4
  51. package/src/shared-resources.ts +0 -3
  52. package/src/ts-libs.ts +1 -1
  53. package/src/typechecker.ts +1 -1
  54. package/dist/react.d.ts +0 -159
  55. package/dist/react.d.ts.map +0 -1
  56. package/dist/react.js +0 -149
  57. package/src/commands.ts +0 -733
  58. package/src/react.tsx +0 -331
  59. package/src/sandbox-manager.ts +0 -490
package/src/commands.ts DELETED
@@ -1,733 +0,0 @@
1
- /**
2
- * Command factories for sandbox bash environments.
3
- *
4
- * Pure factories that create commands for type checking and bundling.
5
- * No global state - all dependencies are passed in explicitly.
6
- */
7
-
8
- import { defineCommand, type CommandContext, type IFileSystem } from "just-bash/browser";
9
- import { typecheck, formatDiagnosticsForAgent, type TypecheckResult } from "./typechecker";
10
- import { bundle, type BundleResult } from "./bundler";
11
- import { installPackage, uninstallPackage, listPackages, type TypesCache } from "./packages";
12
- import { loadModule } from "./loader";
13
-
14
- /**
15
- * Dependencies required by command factories
16
- */
17
- export interface CommandDeps {
18
- /**
19
- * The virtual filesystem to operate on
20
- */
21
- fs: IFileSystem;
22
-
23
- /**
24
- * Pre-loaded TypeScript lib files for type checking
25
- */
26
- libFiles: Map<string, string>;
27
-
28
- /**
29
- * Path to tsconfig.json in the virtual filesystem
30
- */
31
- tsconfigPath: string;
32
-
33
- /**
34
- * Callback invoked when a build succeeds
35
- */
36
- onBuild?: (result: BundleResult) => void | Promise<void>;
37
-
38
- /**
39
- * Cache for package type definitions.
40
- * When provided, avoids redundant network fetches for packages
41
- * that have already been installed in other sandboxes.
42
- */
43
- typesCache?: TypesCache;
44
-
45
- /**
46
- * Options for the `run` command
47
- */
48
- runOptions?: RunOptions;
49
-
50
- /**
51
- * Module IDs that should be resolved from the host's SharedModuleRegistry
52
- * instead of esm.sh CDN. The host must have registered these modules.
53
- *
54
- * Example: ['react', 'react-dom/client']
55
- */
56
- sharedModules?: string[];
57
- }
58
-
59
- /**
60
- * Runtime context passed to the `main()` function when code is executed.
61
- * This provides sandboxed code with access to sandbox capabilities.
62
- */
63
- export interface RunContext {
64
- /**
65
- * The virtual filesystem - read/write files within the sandbox
66
- */
67
- fs: IFileSystem;
68
-
69
- /**
70
- * Environment variables (configurable per-sandbox)
71
- */
72
- env: Record<string, string>;
73
-
74
- /**
75
- * Command-line arguments passed to `run`
76
- */
77
- args: string[];
78
-
79
- /**
80
- * Explicit logging function (alternative to console.log)
81
- */
82
- log: (...args: unknown[]) => void;
83
-
84
- /**
85
- * Explicit error logging function (alternative to console.error)
86
- */
87
- error: (...args: unknown[]) => void;
88
- }
89
-
90
- /**
91
- * Options for configuring the `run` command behavior
92
- */
93
- export interface RunOptions {
94
- /**
95
- * Environment variables available via ctx.env
96
- */
97
- env?: Record<string, string>;
98
-
99
- /**
100
- * Maximum execution time in milliseconds (default: 30000 = 30s)
101
- * Set to 0 to disable timeout.
102
- */
103
- timeout?: number;
104
-
105
- /**
106
- * Whether to skip type checking before running (default: false)
107
- */
108
- skipTypecheck?: boolean;
109
- }
110
-
111
- /**
112
- * Result of running code via the `run` command
113
- */
114
- export interface RunResult {
115
- /**
116
- * Captured console output (log, warn, error)
117
- */
118
- logs: string[];
119
-
120
- /**
121
- * Return value from main() if present
122
- */
123
- returnValue?: unknown;
124
-
125
- /**
126
- * Execution time in milliseconds
127
- */
128
- executionTimeMs: number;
129
- }
130
-
131
- /**
132
- * Format esbuild messages (warnings/errors) for display
133
- */
134
- export function formatEsbuildMessages(
135
- messages: { text: string; location?: { file?: string; line?: number; column?: number } | null }[]
136
- ): string {
137
- if (messages.length === 0) return "";
138
-
139
- return messages
140
- .map((msg) => {
141
- if (msg.location) {
142
- const { file, line, column } = msg.location;
143
- const loc = file ? `${file}${line ? `:${line}` : ""}${column ? `:${column}` : ""}` : "";
144
- return loc ? `${loc}: ${msg.text}` : msg.text;
145
- }
146
- return msg.text;
147
- })
148
- .join("\n");
149
- }
150
-
151
- /**
152
- * Create the `tsc` command for type checking
153
- */
154
- export function createTscCommand(deps: CommandDeps) {
155
- const { fs, libFiles, tsconfigPath } = deps;
156
-
157
- return defineCommand("tsc", async (args, _ctx: CommandContext) => {
158
- const entryPoint = args[0];
159
- if (!entryPoint) {
160
- return {
161
- stdout: "",
162
- stderr: `Usage: tsc <entry-point>\n\nExample: tsc /src/index.ts\n`,
163
- exitCode: 1,
164
- };
165
- }
166
-
167
- try {
168
- // Check if entry point exists
169
- if (!(await fs.exists(entryPoint))) {
170
- return {
171
- stdout: "",
172
- stderr: `Error: Entry point not found: ${entryPoint}\n`,
173
- exitCode: 1,
174
- };
175
- }
176
-
177
- const result = await typecheck({
178
- fs,
179
- entryPoint,
180
- tsconfigPath,
181
- libFiles,
182
- });
183
-
184
- if (result.hasErrors) {
185
- const formatted = formatDiagnosticsForAgent(result.diagnostics);
186
- return {
187
- stdout: "",
188
- stderr: formatted + "\n",
189
- exitCode: 1,
190
- };
191
- }
192
-
193
- const checkedCount = result.checkedFiles.length;
194
- const warningCount = result.diagnostics.filter((d) => d.category === "warning").length;
195
-
196
- let output = `Type check passed. Checked ${checkedCount} file(s).\n`;
197
- if (warningCount > 0) {
198
- output += `\nWarnings:\n${formatDiagnosticsForAgent(result.diagnostics.filter((d) => d.category === "warning"))}\n`;
199
- }
200
-
201
- return {
202
- stdout: output,
203
- stderr: "",
204
- exitCode: 0,
205
- };
206
- } catch (err) {
207
- return {
208
- stdout: "",
209
- stderr: `Type check failed: ${err instanceof Error ? err.message : String(err)}\n`,
210
- exitCode: 1,
211
- };
212
- }
213
- });
214
- }
215
-
216
- /**
217
- * Create the `build` command for bundling (with automatic type checking)
218
- */
219
- export function createBuildCommand(deps: CommandDeps) {
220
- const { fs, libFiles, tsconfigPath, onBuild, sharedModules } = deps;
221
-
222
- return defineCommand("build", async (args, _ctx: CommandContext) => {
223
- // Parse arguments
224
- let entryPoint: string | null = null;
225
- let skipTypecheck = false;
226
- let minify = false;
227
- let format: "esm" | "iife" | "cjs" = "esm";
228
-
229
- for (let i = 0; i < args.length; i++) {
230
- const arg = args[i];
231
- if (arg === "--skip-typecheck" || arg === "-s") {
232
- skipTypecheck = true;
233
- } else if (arg === "--minify" || arg === "-m") {
234
- minify = true;
235
- } else if ((arg === "--format" || arg === "-f") && args[i + 1]) {
236
- const f = args[++i]!.toLowerCase();
237
- if (f === "esm" || f === "iife" || f === "cjs") {
238
- format = f;
239
- }
240
- } else if (!arg!.startsWith("-")) {
241
- entryPoint = arg!;
242
- }
243
- }
244
-
245
- // Entry point is required
246
- if (!entryPoint) {
247
- return {
248
- stdout: "",
249
- stderr: `Usage: build <entry-point> [options]\n\nOptions:\n --skip-typecheck, -s Skip type checking\n --minify, -m Minify output\n --format, -f <fmt> Output format (esm|iife|cjs)\n\nExample: build /src/index.ts\n`,
250
- exitCode: 1,
251
- };
252
- }
253
-
254
- try {
255
- // Check if entry point exists
256
- if (!(await fs.exists(entryPoint))) {
257
- return {
258
- stdout: "",
259
- stderr: `Error: Entry point not found: ${entryPoint}\n`,
260
- exitCode: 1,
261
- };
262
- }
263
-
264
- // Step 1: Type check (unless skipped)
265
- let typecheckResult: TypecheckResult | null = null;
266
- if (!skipTypecheck) {
267
- typecheckResult = await typecheck({
268
- fs,
269
- entryPoint,
270
- tsconfigPath,
271
- libFiles,
272
- });
273
-
274
- if (typecheckResult.hasErrors) {
275
- const formatted = formatDiagnosticsForAgent(typecheckResult.diagnostics);
276
- return {
277
- stdout: "",
278
- stderr: `Build failed: Type errors found.\n\n${formatted}\n`,
279
- exitCode: 1,
280
- };
281
- }
282
- }
283
-
284
- // Step 2: Bundle
285
- const bundleResult = await bundle({
286
- fs,
287
- entryPoint,
288
- format,
289
- minify,
290
- sharedModules,
291
- });
292
-
293
- // Invoke callback with bundle result (caller can dynamically import, halt agent, etc.)
294
- if (onBuild) {
295
- await onBuild(bundleResult);
296
- }
297
-
298
- // Build success message
299
- let output = `Build successful!\n`;
300
- output += `Entry: ${entryPoint}\n`;
301
- output += `Format: ${format}\n`;
302
- output += `Size: ${(bundleResult.code.length / 1024).toFixed(2)} KB\n`;
303
-
304
- if (typecheckResult) {
305
- output += `Type checked: ${typecheckResult.checkedFiles.length} file(s)\n`;
306
- }
307
-
308
- output += `Bundled: ${bundleResult.includedFiles.length} file(s)\n`;
309
-
310
- // Include warnings if any
311
- if (bundleResult.warnings.length > 0) {
312
- output += `\nBuild warnings:\n${formatEsbuildMessages(bundleResult.warnings)}\n`;
313
- }
314
-
315
- if (typecheckResult) {
316
- const warnings = typecheckResult.diagnostics.filter((d) => d.category === "warning");
317
- if (warnings.length > 0) {
318
- output += `\nType warnings:\n${formatDiagnosticsForAgent(warnings)}\n`;
319
- }
320
- }
321
-
322
- return {
323
- stdout: output,
324
- stderr: "",
325
- exitCode: 0,
326
- };
327
- } catch (err) {
328
- const errorMessage = err instanceof Error ? err.message : String(err);
329
- return {
330
- stdout: "",
331
- stderr: `Build failed: ${errorMessage}\n`,
332
- exitCode: 1,
333
- };
334
- }
335
- });
336
- }
337
-
338
- /**
339
- * Create the `install` command for adding packages from npm
340
- */
341
- export function createInstallCommand(deps: CommandDeps) {
342
- const { fs, typesCache } = deps;
343
-
344
- return defineCommand("install", async (args, _ctx: CommandContext) => {
345
- if (args.length === 0) {
346
- return {
347
- stdout: "",
348
- stderr: "Usage: install <package>[@version] [...packages]\n\nExamples:\n install react\n install lodash@4.17.21\n install @tanstack/react-query@5\n",
349
- exitCode: 1,
350
- };
351
- }
352
-
353
- const results: string[] = [];
354
- let hasError = false;
355
-
356
- for (const packageSpec of args) {
357
- try {
358
- const result = await installPackage(fs, packageSpec!, { cache: typesCache });
359
-
360
- let status = `+ ${result.name}@${result.version}`;
361
- if (result.typesInstalled) {
362
- status += ` (${result.typeFilesCount} type file${result.typeFilesCount !== 1 ? "s" : ""})`;
363
- if (result.fromCache) {
364
- status += " [cached]";
365
- }
366
- } else if (result.typesError) {
367
- status += ` (no types: ${result.typesError})`;
368
- }
369
- results.push(status);
370
- } catch (err) {
371
- hasError = true;
372
- const message = err instanceof Error ? err.message : String(err);
373
- results.push(`x ${packageSpec}: ${message}`);
374
- }
375
- }
376
-
377
- const output = results.join("\n") + "\n";
378
-
379
- if (hasError) {
380
- return {
381
- stdout: "",
382
- stderr: output,
383
- exitCode: 1,
384
- };
385
- }
386
-
387
- return {
388
- stdout: output,
389
- stderr: "",
390
- exitCode: 0,
391
- };
392
- });
393
- }
394
-
395
- /**
396
- * Create the `uninstall` command for removing packages
397
- */
398
- export function createUninstallCommand(deps: CommandDeps) {
399
- const { fs } = deps;
400
-
401
- return defineCommand("uninstall", async (args, _ctx: CommandContext) => {
402
- if (args.length === 0) {
403
- return {
404
- stdout: "",
405
- stderr: "Usage: uninstall <package> [...packages]\n",
406
- exitCode: 1,
407
- };
408
- }
409
-
410
- const results: string[] = [];
411
- let hasError = false;
412
-
413
- for (const packageName of args) {
414
- try {
415
- const removed = await uninstallPackage(fs, packageName!);
416
- if (removed) {
417
- results.push(`- ${packageName}`);
418
- } else {
419
- results.push(`x ${packageName}: not installed`);
420
- hasError = true;
421
- }
422
- } catch (err) {
423
- hasError = true;
424
- const message = err instanceof Error ? err.message : String(err);
425
- results.push(`x ${packageName}: ${message}`);
426
- }
427
- }
428
-
429
- const output = results.join("\n") + "\n";
430
-
431
- if (hasError) {
432
- return {
433
- stdout: "",
434
- stderr: output,
435
- exitCode: 1,
436
- };
437
- }
438
-
439
- return {
440
- stdout: output,
441
- stderr: "",
442
- exitCode: 0,
443
- };
444
- });
445
- }
446
-
447
- /**
448
- * Create the `list` command (alias: `ls`) for showing installed packages
449
- */
450
- export function createListCommand(deps: CommandDeps) {
451
- const { fs } = deps;
452
-
453
- return defineCommand("list", async (_args, _ctx: CommandContext) => {
454
- try {
455
- const packages = await listPackages(fs);
456
-
457
- if (packages.length === 0) {
458
- return {
459
- stdout: "No packages installed.\n",
460
- stderr: "",
461
- exitCode: 0,
462
- };
463
- }
464
-
465
- const output = packages
466
- .map((pkg) => `${pkg.name}@${pkg.version}`)
467
- .join("\n") + "\n";
468
-
469
- return {
470
- stdout: output,
471
- stderr: "",
472
- exitCode: 0,
473
- };
474
- } catch (err) {
475
- const message = err instanceof Error ? err.message : String(err);
476
- return {
477
- stdout: "",
478
- stderr: `Failed to list packages: ${message}\n`,
479
- exitCode: 1,
480
- };
481
- }
482
- });
483
- }
484
-
485
- /**
486
- * Create the `run` command for executing code in the sandbox.
487
- *
488
- * The run command:
489
- * 1. Builds the entry point (with type checking by default)
490
- * 2. Dynamically imports the bundle
491
- * 3. If a `main` export exists, calls it with a RunContext
492
- * 4. Captures all console output (log, warn, error)
493
- * 5. Returns the captured output and any return value from main()
494
- *
495
- * Usage:
496
- * run [entry] [--skip-typecheck|-s] [--timeout|-t <ms>] [-- args...]
497
- *
498
- * Code can be written in two styles:
499
- *
500
- * 1. Script style (top-level code, runs on import):
501
- * ```ts
502
- * console.log("Hello from script!");
503
- * const result = 2 + 2;
504
- * console.log("Result:", result);
505
- * ```
506
- *
507
- * 2. Main function style (with context access):
508
- * ```ts
509
- * import type { RunContext } from "sandlot";
510
- *
511
- * export async function main(ctx: RunContext) {
512
- * ctx.log("Reading file...");
513
- * const content = await ctx.fs.readFile("/data/input.txt");
514
- * ctx.log("Content:", content);
515
- * return { success: true };
516
- * }
517
- * ```
518
- */
519
- export function createRunCommand(deps: CommandDeps) {
520
- const { fs, libFiles, tsconfigPath, runOptions = {}, sharedModules } = deps;
521
-
522
- return defineCommand("run", async (args, _ctx: CommandContext) => {
523
- // Parse arguments
524
- let entryPoint: string | null = null;
525
- let skipTypecheck = runOptions.skipTypecheck ?? false;
526
- let timeout = runOptions.timeout ?? 30000;
527
- const scriptArgs: string[] = [];
528
- let collectingArgs = false;
529
-
530
- for (let i = 0; i < args.length; i++) {
531
- const arg = args[i];
532
-
533
- if (collectingArgs) {
534
- scriptArgs.push(arg!);
535
- continue;
536
- }
537
-
538
- if (arg === "--") {
539
- collectingArgs = true;
540
- } else if (arg === "--skip-typecheck" || arg === "-s") {
541
- skipTypecheck = true;
542
- } else if ((arg === "--timeout" || arg === "-t") && args[i + 1]) {
543
- timeout = parseInt(args[++i]!, 10);
544
- if (isNaN(timeout)) timeout = 30000;
545
- } else if (!arg!.startsWith("-")) {
546
- entryPoint = arg!;
547
- }
548
- }
549
-
550
- // Entry point is required
551
- if (!entryPoint) {
552
- return {
553
- stdout: "",
554
- stderr: `Usage: run <entry-point> [options] [-- args...]\n\nOptions:\n --skip-typecheck, -s Skip type checking\n --timeout, -t <ms> Execution timeout (default: 30000)\n\nExample: run /src/index.ts\n`,
555
- exitCode: 1,
556
- };
557
- }
558
-
559
- // Capture console output
560
- const logs: string[] = [];
561
- const originalConsole = {
562
- log: console.log,
563
- warn: console.warn,
564
- error: console.error,
565
- info: console.info,
566
- debug: console.debug,
567
- };
568
-
569
- const formatArgs = (...a: unknown[]) =>
570
- a.map((v) => (typeof v === "object" ? JSON.stringify(v) : String(v))).join(" ");
571
-
572
- const captureLog = (...a: unknown[]) => {
573
- logs.push(formatArgs(...a));
574
- originalConsole.log.apply(console, a);
575
- };
576
- const captureWarn = (...a: unknown[]) => {
577
- logs.push(`[warn] ${formatArgs(...a)}`);
578
- originalConsole.warn.apply(console, a);
579
- };
580
- const captureError = (...a: unknown[]) => {
581
- logs.push(`[error] ${formatArgs(...a)}`);
582
- originalConsole.error.apply(console, a);
583
- };
584
- const captureInfo = (...a: unknown[]) => {
585
- logs.push(`[info] ${formatArgs(...a)}`);
586
- originalConsole.info.apply(console, a);
587
- };
588
- const captureDebug = (...a: unknown[]) => {
589
- logs.push(`[debug] ${formatArgs(...a)}`);
590
- originalConsole.debug.apply(console, a);
591
- };
592
-
593
- const restoreConsole = () => {
594
- console.log = originalConsole.log;
595
- console.warn = originalConsole.warn;
596
- console.error = originalConsole.error;
597
- console.info = originalConsole.info;
598
- console.debug = originalConsole.debug;
599
- };
600
-
601
- try {
602
- // Check if entry point exists
603
- if (!(await fs.exists(entryPoint))) {
604
- return {
605
- stdout: "",
606
- stderr: `Error: Entry point not found: ${entryPoint}\n`,
607
- exitCode: 1,
608
- };
609
- }
610
-
611
- // Type check (unless skipped)
612
- if (!skipTypecheck) {
613
- const typecheckResult = await typecheck({
614
- fs,
615
- entryPoint,
616
- tsconfigPath,
617
- libFiles,
618
- });
619
-
620
- if (typecheckResult.hasErrors) {
621
- const formatted = formatDiagnosticsForAgent(typecheckResult.diagnostics);
622
- return {
623
- stdout: "",
624
- stderr: `Type errors:\n${formatted}\n`,
625
- exitCode: 1,
626
- };
627
- }
628
- }
629
-
630
- // Bundle the code
631
- const bundleResult = await bundle({
632
- fs,
633
- entryPoint,
634
- format: "esm",
635
- sharedModules,
636
- });
637
-
638
- // Install console interceptors
639
- console.log = captureLog;
640
- console.warn = captureWarn;
641
- console.error = captureError;
642
- console.info = captureInfo;
643
- console.debug = captureDebug;
644
-
645
- // Create the run context
646
- const context: RunContext = {
647
- fs,
648
- env: { ...runOptions.env },
649
- args: scriptArgs,
650
- log: captureLog,
651
- error: captureError,
652
- };
653
-
654
- // Execute the code with optional timeout
655
- const startTime = performance.now();
656
- let returnValue: unknown;
657
-
658
- const executeCode = async () => {
659
- // Load the module (this executes top-level code)
660
- const module = await loadModule<{ main?: (ctx: RunContext) => unknown }>(bundleResult);
661
-
662
- // If there's a main export, call it with context
663
- if (typeof module.main === "function") {
664
- returnValue = await module.main(context);
665
- }
666
- };
667
-
668
- if (timeout > 0) {
669
- const timeoutPromise = new Promise<never>((_, reject) => {
670
- setTimeout(() => reject(new Error(`Execution timed out after ${timeout}ms`)), timeout);
671
- });
672
- await Promise.race([executeCode(), timeoutPromise]);
673
- } else {
674
- await executeCode();
675
- }
676
-
677
- const executionTimeMs = performance.now() - startTime;
678
-
679
- // Restore console before building output
680
- restoreConsole();
681
-
682
- // Build output
683
- let output = "";
684
- if (logs.length > 0) {
685
- output = logs.join("\n") + "\n";
686
- }
687
- if (returnValue !== undefined) {
688
- const returnStr =
689
- typeof returnValue === "object"
690
- ? JSON.stringify(returnValue, null, 2)
691
- : String(returnValue);
692
- output += `[return] ${returnStr}\n`;
693
- }
694
- output += `\nExecution completed in ${executionTimeMs.toFixed(2)}ms\n`;
695
-
696
- return {
697
- stdout: output,
698
- stderr: "",
699
- exitCode: 0,
700
- };
701
- } catch (err) {
702
- restoreConsole();
703
-
704
- const errorMessage = err instanceof Error ? err.message : String(err);
705
- const errorStack = err instanceof Error && err.stack ? `\n${err.stack}` : "";
706
-
707
- let output = "";
708
- if (logs.length > 0) {
709
- output = logs.join("\n") + "\n\n";
710
- }
711
-
712
- return {
713
- stdout: output,
714
- stderr: `Runtime error: ${errorMessage}${errorStack}\n`,
715
- exitCode: 1,
716
- };
717
- }
718
- });
719
- }
720
-
721
- /**
722
- * Create all default sandbox commands
723
- */
724
- export function createDefaultCommands(deps: CommandDeps) {
725
- return [
726
- createTscCommand(deps),
727
- createBuildCommand(deps),
728
- createRunCommand(deps),
729
- createInstallCommand(deps),
730
- createUninstallCommand(deps),
731
- createListCommand(deps),
732
- ];
733
- }