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.
- package/README.md +145 -518
- package/dist/build-emitter.d.ts +47 -0
- package/dist/build-emitter.d.ts.map +1 -0
- package/dist/builder.d.ts +370 -0
- package/dist/builder.d.ts.map +1 -0
- package/dist/bundler.d.ts +3 -3
- package/dist/bundler.d.ts.map +1 -1
- package/dist/commands/compile.d.ts +13 -0
- package/dist/commands/compile.d.ts.map +1 -0
- package/dist/commands/index.d.ts +17 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/packages.d.ts +17 -0
- package/dist/commands/packages.d.ts.map +1 -0
- package/dist/commands/run.d.ts +40 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/types.d.ts +141 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/fs.d.ts +60 -42
- package/dist/fs.d.ts.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +304 -491
- package/dist/internal.d.ts +5 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +174 -95
- package/dist/runner.d.ts +314 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/sandbox-manager.d.ts +45 -33
- package/dist/sandbox-manager.d.ts.map +1 -1
- package/dist/sandbox.d.ts +144 -70
- package/dist/sandbox.d.ts.map +1 -1
- package/dist/shared-modules.d.ts +22 -3
- package/dist/shared-modules.d.ts.map +1 -1
- package/dist/shared-resources.d.ts +0 -3
- package/dist/shared-resources.d.ts.map +1 -1
- package/dist/typechecker.d.ts +1 -1
- package/package.json +3 -17
- package/src/build-emitter.ts +64 -0
- package/src/builder.ts +498 -0
- package/src/bundler.ts +86 -57
- package/src/commands/compile.ts +236 -0
- package/src/commands/index.ts +51 -0
- package/src/commands/packages.ts +154 -0
- package/src/commands/run.ts +245 -0
- package/src/commands/types.ts +172 -0
- package/src/fs.ts +90 -216
- package/src/index.ts +34 -12
- package/src/internal.ts +5 -2
- package/src/sandbox.ts +214 -220
- package/src/shared-modules.ts +74 -4
- package/src/shared-resources.ts +0 -3
- package/src/ts-libs.ts +1 -1
- package/src/typechecker.ts +1 -1
- package/dist/react.d.ts +0 -159
- package/dist/react.d.ts.map +0 -1
- package/dist/react.js +0 -149
- package/src/commands.ts +0 -733
- package/src/react.tsx +0 -331
- 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
|
-
}
|