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/README.md
CHANGED
|
@@ -1,616 +1,243 @@
|
|
|
1
1
|
# Sandlot
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Sandlot provides a complete in-browser development environment with a virtual filesystem, TypeScript type checking, esbuild bundling, and npm package management—all without a server.
|
|
6
|
-
|
|
7
|
-
## Features
|
|
8
|
-
|
|
9
|
-
- **Virtual Filesystem** - In-memory or IndexedDB-backed file storage
|
|
10
|
-
- **TypeScript Type Checking** - Full type checking
|
|
11
|
-
- **esbuild Bundling** - Fast bundling with automatic npm import handling via esbuild-wasm
|
|
12
|
-
- **Package Management** - Install npm packages via esm.sh CDN
|
|
13
|
-
- **Bash Shell** - Familiar command interface (`tsc`, `build`, `install`, etc.) via just-bash
|
|
14
|
-
- **React Integration** - Share your React instance with dynamic components
|
|
3
|
+
Browser-based TypeScript sandbox with esbuild bundling and type checking. Designed for AI agent workflows where code needs to be written, validated, and executed in real-time, all in the browser.
|
|
15
4
|
|
|
16
5
|
## Installation
|
|
17
6
|
|
|
18
7
|
```bash
|
|
19
8
|
npm install sandlot
|
|
20
|
-
# or
|
|
21
|
-
bun add sandlot
|
|
22
9
|
```
|
|
23
10
|
|
|
24
11
|
## Quick Start
|
|
25
12
|
|
|
26
13
|
```typescript
|
|
27
|
-
import {
|
|
14
|
+
import { createBuilder, registerSharedModules } from "sandlot";
|
|
15
|
+
import * as React from "react";
|
|
28
16
|
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
fsOptions: {
|
|
32
|
-
initialFiles: {
|
|
33
|
-
"/tsconfig.json": JSON.stringify({
|
|
34
|
-
compilerOptions: { target: "ES2020", module: "ESNext", strict: true },
|
|
35
|
-
}),
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
});
|
|
17
|
+
// Share React with dynamic code to avoid duplicate instances
|
|
18
|
+
registerSharedModules({ react: React });
|
|
39
19
|
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const unsubscribe = sandbox.onBuild((result) => {
|
|
51
|
-
bundle = result;
|
|
20
|
+
// Create a reusable builder
|
|
21
|
+
const runAgent = createBuilder({
|
|
22
|
+
sandboxOptions: { sharedModules: ["react"] },
|
|
23
|
+
build: async (sandbox, prompt) => {
|
|
24
|
+
// Your agent logic here - execute commands via sandbox.bash.exec()
|
|
25
|
+
await sandbox.bash.exec(
|
|
26
|
+
'echo "export const App = () => <div>Hello</div>" > /src/index.tsx',
|
|
27
|
+
);
|
|
28
|
+
await sandbox.bash.exec("build /src/index.tsx");
|
|
29
|
+
},
|
|
52
30
|
});
|
|
53
31
|
|
|
54
|
-
//
|
|
55
|
-
const result = await
|
|
56
|
-
unsubscribe();
|
|
32
|
+
// Run with a prompt
|
|
33
|
+
const result = await runAgent("Create a counter component");
|
|
57
34
|
|
|
58
|
-
if (result.
|
|
59
|
-
|
|
60
|
-
|
|
35
|
+
if (result.module?.App) {
|
|
36
|
+
// Use the generated component
|
|
37
|
+
const App = result.module.App as React.ComponentType;
|
|
61
38
|
}
|
|
62
|
-
|
|
63
|
-
sandbox.close();
|
|
64
39
|
```
|
|
65
40
|
|
|
66
|
-
##
|
|
41
|
+
## Builder API
|
|
67
42
|
|
|
68
|
-
The
|
|
43
|
+
The `createBuilder()` function is the primary API for agent workflows. It handles sandbox lifecycle, build capture, validation, and cancellation.
|
|
69
44
|
|
|
70
|
-
###
|
|
45
|
+
### `createBuilder(options)`
|
|
71
46
|
|
|
72
|
-
|
|
73
|
-
import { createSandbox, createInMemorySandbox } from "sandlot";
|
|
74
|
-
|
|
75
|
-
// Persistent sandbox (IndexedDB-backed)
|
|
76
|
-
const sandbox = await createSandbox({
|
|
77
|
-
fsOptions: {
|
|
78
|
-
dbName: "my-project",
|
|
79
|
-
initialFiles: {
|
|
80
|
-
"/src/index.ts": "export const x = 1;",
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// In-memory sandbox (no persistence)
|
|
86
|
-
const memSandbox = await createInMemorySandbox({
|
|
87
|
-
initialFiles: {
|
|
88
|
-
"/src/index.ts": "export const x = 1;",
|
|
89
|
-
},
|
|
90
|
-
});
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### Using Bash Commands
|
|
47
|
+
Returns a reusable builder function that can be called with different prompts.
|
|
94
48
|
|
|
95
49
|
```typescript
|
|
96
|
-
|
|
97
|
-
|
|
50
|
+
const runAgent = createBuilder<T>({
|
|
51
|
+
// Sandbox configuration (pick one)
|
|
52
|
+
sandbox?: Sandbox, // Reuse an existing sandbox (state persists between calls)
|
|
53
|
+
sandboxOptions?: SandboxOptions, // Create fresh sandbox per call (isolated runs)
|
|
98
54
|
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
console.log(tscResult.stdout);
|
|
102
|
-
|
|
103
|
-
// Build (entry point required) - capture result via onBuild callback
|
|
104
|
-
let bundleResult: BundleResult | null = null;
|
|
105
|
-
const unsubscribe = sandbox.onBuild((result) => {
|
|
106
|
-
bundleResult = result;
|
|
55
|
+
// Your agent logic
|
|
56
|
+
build: (sandbox: Sandbox, prompt: string) => Promise<T>,
|
|
107
57
|
});
|
|
108
|
-
|
|
109
|
-
const buildCmd = await sandbox.bash.exec("build /src/index.ts");
|
|
110
|
-
if (buildCmd.exitCode === 0 && bundleResult) {
|
|
111
|
-
console.log(bundleResult.code); // Compiled JavaScript
|
|
112
|
-
}
|
|
113
|
-
unsubscribe();
|
|
114
|
-
|
|
115
|
-
// Install packages
|
|
116
|
-
await sandbox.bash.exec("install react lodash@4.17.21");
|
|
117
|
-
|
|
118
|
-
// List installed packages
|
|
119
|
-
const listResult = await sandbox.bash.exec("list");
|
|
120
|
-
console.log(listResult.stdout);
|
|
121
|
-
|
|
122
|
-
// Run code (entry point required)
|
|
123
|
-
await sandbox.bash.exec("run /src/script.ts");
|
|
124
58
|
```
|
|
125
59
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
```bash
|
|
129
|
-
build <entry> [options]
|
|
60
|
+
#### Options
|
|
130
61
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
Options
|
|
135
|
-
|
|
136
|
-
--minify, -m Enable minification
|
|
137
|
-
--format, -f <format> Output format: esm (default), iife, cjs
|
|
138
|
-
```
|
|
62
|
+
| Option | Type | Description |
|
|
63
|
+
| ---------------- | --------------------------------- | ------------------------------------------------------- |
|
|
64
|
+
| `sandbox` | `Sandbox` | Existing sandbox to reuse. Files persist between calls. |
|
|
65
|
+
| `sandboxOptions` | `SandboxOptions` | Options for creating fresh sandboxes per call. |
|
|
66
|
+
| `build` | `(sandbox, prompt) => Promise<T>` | Your agent logic. Receives the sandbox and prompt. |
|
|
139
67
|
|
|
140
|
-
###
|
|
68
|
+
### Calling the Builder
|
|
141
69
|
|
|
142
70
|
```typescript
|
|
143
|
-
|
|
144
|
-
let bundleResult: BundleResult | null = null;
|
|
145
|
-
const unsubscribe = sandbox.onBuild((result) => {
|
|
146
|
-
bundleResult = result;
|
|
147
|
-
console.log("Build completed:", result.code.length, "bytes");
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
// Build with entry point (required)
|
|
151
|
-
const buildCmd = await sandbox.bash.exec("build /src/index.ts");
|
|
152
|
-
if (buildCmd.exitCode !== 0) {
|
|
153
|
-
console.error("Build failed:", buildCmd.stderr);
|
|
154
|
-
} else if (bundleResult) {
|
|
155
|
-
console.log(bundleResult.code);
|
|
156
|
-
}
|
|
157
|
-
unsubscribe();
|
|
158
|
-
|
|
159
|
-
// Check for unsaved changes
|
|
160
|
-
if (sandbox.isDirty()) {
|
|
161
|
-
await sandbox.save(); // Persist to IndexedDB
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Clean up
|
|
165
|
-
sandbox.close();
|
|
71
|
+
const result = await runAgent(prompt, options?);
|
|
166
72
|
```
|
|
167
73
|
|
|
168
|
-
|
|
169
|
-
> Use it to capture the `BundleResult` for loading modules.
|
|
170
|
-
|
|
171
|
-
### Sandbox Manager
|
|
172
|
-
|
|
173
|
-
For managing multiple sandboxes with shared resources:
|
|
174
|
-
|
|
175
|
-
```typescript
|
|
176
|
-
import { createSandboxManager } from "sandlot";
|
|
177
|
-
|
|
178
|
-
const manager = await createSandboxManager({
|
|
179
|
-
sharedModules: ["react", "react-dom/client"],
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
// Create multiple sandboxes - they share TypeScript libs and bundler
|
|
183
|
-
const sandbox1 = await manager.createSandbox({ id: "agent-1" });
|
|
184
|
-
const sandbox2 = await manager.createSandbox({ id: "agent-2" });
|
|
74
|
+
#### Call Options
|
|
185
75
|
|
|
186
|
-
|
|
187
|
-
|
|
76
|
+
| Option | Type | Description |
|
|
77
|
+
| ---------- | --------------- | --------------------------------------------------------------- |
|
|
78
|
+
| `validate` | `(module) => M` | Validate exports during build. Errors are visible to the agent. |
|
|
79
|
+
| `timeout` | `number` | Max time in ms. Throws `AbortError` if exceeded. |
|
|
80
|
+
| `signal` | `AbortSignal` | For external cancellation. |
|
|
188
81
|
|
|
189
|
-
|
|
190
|
-
await manager.saveAll();
|
|
191
|
-
|
|
192
|
-
// Get dirty sandbox IDs
|
|
193
|
-
const unsaved = manager.getDirtySandboxes();
|
|
194
|
-
|
|
195
|
-
// Clean up
|
|
196
|
-
manager.destroyAll();
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
### Sandbox Options
|
|
82
|
+
#### Result
|
|
200
83
|
|
|
201
84
|
```typescript
|
|
202
|
-
interface
|
|
203
|
-
//
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
// Build configuration
|
|
211
|
-
tsconfigPath?: string; // Default: "/tsconfig.json"
|
|
212
|
-
|
|
213
|
-
// Module sharing (see React Integration)
|
|
214
|
-
sharedModules?: string[];
|
|
215
|
-
|
|
216
|
-
// Build callback - use to capture successful build results
|
|
217
|
-
onBuild?: (result: BundleResult) => void | Promise<void>;
|
|
218
|
-
|
|
219
|
-
// Custom bash commands
|
|
220
|
-
customCommands?: Command[];
|
|
85
|
+
interface BuildResult<T, M> {
|
|
86
|
+
result: T | undefined; // Return value from your build function
|
|
87
|
+
error: Error | null; // Error if build function threw
|
|
88
|
+
bundle: BundleResult | null; // The compiled JavaScript bundle
|
|
89
|
+
module: M | null; // Loaded module exports (validated if validate provided)
|
|
90
|
+
sandbox: Sandbox; // The sandbox used (for inspection or reuse)
|
|
221
91
|
}
|
|
222
92
|
```
|
|
223
93
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
## React Integration
|
|
94
|
+
### Validation
|
|
227
95
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
### Setup
|
|
96
|
+
Validation runs as part of the `build` command. If it throws, the build fails and the agent sees the error, allowing it to fix the code and retry.
|
|
231
97
|
|
|
232
98
|
```typescript
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
react: React,
|
|
241
|
-
"react-dom/client": ReactDOM,
|
|
99
|
+
const result = await runAgent("Create a counter", {
|
|
100
|
+
validate: (mod) => {
|
|
101
|
+
if (typeof mod.App !== "function") {
|
|
102
|
+
throw new Error("Must export an App component");
|
|
103
|
+
}
|
|
104
|
+
return { App: mod.App as React.ComponentType };
|
|
105
|
+
},
|
|
242
106
|
});
|
|
107
|
+
// result.module is typed as { App: React.ComponentType } | null
|
|
243
108
|
```
|
|
244
109
|
|
|
245
|
-
|
|
110
|
+
With Zod:
|
|
246
111
|
|
|
247
112
|
```typescript
|
|
248
|
-
import {
|
|
113
|
+
import { z } from "zod";
|
|
249
114
|
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
"/tsconfig.json": JSON.stringify({
|
|
254
|
-
compilerOptions: {
|
|
255
|
-
target: "ES2020",
|
|
256
|
-
module: "ESNext",
|
|
257
|
-
jsx: "react-jsx",
|
|
258
|
-
strict: true,
|
|
259
|
-
},
|
|
260
|
-
}),
|
|
261
|
-
},
|
|
262
|
-
},
|
|
263
|
-
sharedModules: ["react", "react-dom/client"],
|
|
115
|
+
const Schema = z.object({
|
|
116
|
+
App: z.custom<React.ComponentType>((v) => typeof v === "function"),
|
|
117
|
+
initialCount: z.number().optional(),
|
|
264
118
|
});
|
|
265
119
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
await sandbox.bash.exec("install react react-dom");
|
|
269
|
-
|
|
270
|
-
await sandbox.fs.writeFile(
|
|
271
|
-
"/src/index.tsx",
|
|
272
|
-
`
|
|
273
|
-
import React, { useState } from "react";
|
|
274
|
-
import { createRoot } from "react-dom/client";
|
|
275
|
-
|
|
276
|
-
function Counter() {
|
|
277
|
-
const [count, setCount] = useState(0);
|
|
278
|
-
return (
|
|
279
|
-
<button onClick={() => setCount(c => c + 1)}>
|
|
280
|
-
Count: {count}
|
|
281
|
-
</button>
|
|
282
|
-
);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
export function render(container: HTMLElement) {
|
|
286
|
-
const root = createRoot(container);
|
|
287
|
-
root.render(<Counter />);
|
|
288
|
-
return () => root.unmount();
|
|
289
|
-
}
|
|
290
|
-
`,
|
|
291
|
-
);
|
|
292
|
-
|
|
293
|
-
// Capture build result
|
|
294
|
-
let bundle: BundleResult | null = null;
|
|
295
|
-
const unsubscribe = sandbox.onBuild((result) => {
|
|
296
|
-
bundle = result;
|
|
120
|
+
const result = await runAgent("Create a counter", {
|
|
121
|
+
validate: (mod) => Schema.parse(mod),
|
|
297
122
|
});
|
|
298
|
-
|
|
299
|
-
const buildResult = await sandbox.bash.exec("build /src/index.tsx");
|
|
300
|
-
unsubscribe();
|
|
301
|
-
|
|
302
|
-
if (buildResult.exitCode === 0 && bundle) {
|
|
303
|
-
// Load the module and call render
|
|
304
|
-
const mod = await loadModule<{ render: (el: HTMLElement) => () => void }>(
|
|
305
|
-
bundle,
|
|
306
|
-
);
|
|
307
|
-
// mod.render is ready to use
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
sandbox.close();
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
### Rendering Dynamic Components
|
|
314
|
-
|
|
315
|
-
Use `DynamicMount` to render the generated component:
|
|
316
|
-
|
|
317
|
-
```tsx
|
|
318
|
-
import { DynamicMount } from "sandlot/react";
|
|
319
|
-
|
|
320
|
-
function App() {
|
|
321
|
-
const [module, setModule] = useState(null);
|
|
322
|
-
|
|
323
|
-
const generate = async () => {
|
|
324
|
-
// ... build with sandbox, capture bundle via onBuild ...
|
|
325
|
-
setModule(bundle);
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
return (
|
|
329
|
-
<div>
|
|
330
|
-
<button onClick={generate}>Generate</button>
|
|
331
|
-
<DynamicMount
|
|
332
|
-
module={module}
|
|
333
|
-
props={{ name: "World" }}
|
|
334
|
-
fallback={<div>Click to generate...</div>}
|
|
335
|
-
onMount={() => console.log("Mounted")}
|
|
336
|
-
onError={(err) => console.error(err)}
|
|
337
|
-
/>
|
|
338
|
-
</div>
|
|
339
|
-
);
|
|
340
|
-
}
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
### Using the Hook
|
|
344
|
-
|
|
345
|
-
For more control, use `useDynamicComponent`:
|
|
346
|
-
|
|
347
|
-
```tsx
|
|
348
|
-
import { useDynamicComponent } from "sandlot/react";
|
|
349
|
-
|
|
350
|
-
function App() {
|
|
351
|
-
const { containerRef, isMounted, error, unmount } = useDynamicComponent(
|
|
352
|
-
module,
|
|
353
|
-
{ name: "World" },
|
|
354
|
-
);
|
|
355
|
-
|
|
356
|
-
return (
|
|
357
|
-
<div>
|
|
358
|
-
<div ref={containerRef} />
|
|
359
|
-
{isMounted && <button onClick={unmount}>Remove</button>}
|
|
360
|
-
{error && <div>Error: {error.message}</div>}
|
|
361
|
-
</div>
|
|
362
|
-
);
|
|
363
|
-
}
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
### Render Function Pattern
|
|
367
|
-
|
|
368
|
-
Dynamic components must export a `render` function:
|
|
369
|
-
|
|
370
|
-
```typescript
|
|
371
|
-
export function render(container: HTMLElement, props?: MyProps) {
|
|
372
|
-
const root = createRoot(container);
|
|
373
|
-
root.render(<MyComponent {...props} />);
|
|
374
|
-
return () => root.unmount(); // Cleanup function
|
|
375
|
-
}
|
|
376
123
|
```
|
|
377
124
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
## Package Management
|
|
381
|
-
|
|
382
|
-
Sandlot installs packages via esm.sh CDN and fetches TypeScript type definitions.
|
|
383
|
-
|
|
384
|
-
### Installing Packages
|
|
125
|
+
### Cancellation
|
|
385
126
|
|
|
386
127
|
```typescript
|
|
387
|
-
|
|
388
|
-
await sandbox.bash.exec("install lodash date-fns@3.0.0");
|
|
389
|
-
|
|
390
|
-
// Via direct API
|
|
391
|
-
import { installPackage } from "sandlot";
|
|
392
|
-
await installPackage(fs, "lodash@4.17.21");
|
|
393
|
-
```
|
|
394
|
-
|
|
395
|
-
### How It Works
|
|
128
|
+
const controller = new AbortController();
|
|
396
129
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
5. At bundle time, imports are rewritten to esm.sh URLs
|
|
402
|
-
|
|
403
|
-
### Package Commands
|
|
404
|
-
|
|
405
|
-
```bash
|
|
406
|
-
install <package>[@version] [...] # Install packages
|
|
407
|
-
uninstall <package> [...] # Remove packages
|
|
408
|
-
list # Show installed packages
|
|
409
|
-
```
|
|
410
|
-
|
|
411
|
-
---
|
|
412
|
-
|
|
413
|
-
## Loading Modules
|
|
414
|
-
|
|
415
|
-
After building, use loaders to access the compiled code:
|
|
416
|
-
|
|
417
|
-
```typescript
|
|
418
|
-
import { loadModule, loadExport, loadDefault } from "sandlot";
|
|
419
|
-
|
|
420
|
-
// Load all exports
|
|
421
|
-
const mod = await loadModule<{ add: Function; multiply: Function }>(
|
|
422
|
-
result.bundle,
|
|
423
|
-
);
|
|
424
|
-
|
|
425
|
-
// Load a specific export
|
|
426
|
-
const add = await loadExport<(a: number, b: number) => number>(
|
|
427
|
-
result.bundle,
|
|
428
|
-
"add",
|
|
429
|
-
);
|
|
430
|
-
|
|
431
|
-
// Load default export
|
|
432
|
-
const Calculator = await loadDefault<typeof Calculator>(result.bundle);
|
|
130
|
+
const promise = runAgent("Create a dashboard", {
|
|
131
|
+
signal: controller.signal,
|
|
132
|
+
timeout: 60_000, // 1 minute
|
|
133
|
+
});
|
|
433
134
|
|
|
434
|
-
//
|
|
435
|
-
|
|
436
|
-
const hasAdd = await hasExport(result.bundle, "add"); // true
|
|
135
|
+
// Cancel on user action
|
|
136
|
+
cancelButton.onclick = () => controller.abort();
|
|
437
137
|
```
|
|
438
138
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
## Advanced Usage
|
|
139
|
+
## Sandbox API
|
|
442
140
|
|
|
443
|
-
|
|
141
|
+
For lower-level control, use `createSandbox()` directly.
|
|
444
142
|
|
|
445
143
|
```typescript
|
|
446
|
-
import {
|
|
447
|
-
|
|
448
|
-
// Pre-warm the bundler (optional)
|
|
449
|
-
await initBundler();
|
|
450
|
-
|
|
451
|
-
const result = await bundle({
|
|
452
|
-
fs: myFilesystem,
|
|
453
|
-
entryPoint: "/src/index.ts",
|
|
454
|
-
format: "esm", // "esm" | "iife" | "cjs"
|
|
455
|
-
minify: false,
|
|
456
|
-
sourcemap: false,
|
|
457
|
-
sharedModules: ["react"],
|
|
458
|
-
npmImports: "cdn", // "cdn" | "external" | "bundle"
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
console.log(result.code);
|
|
462
|
-
console.log(result.warnings);
|
|
463
|
-
console.log(result.includedFiles);
|
|
464
|
-
```
|
|
465
|
-
|
|
466
|
-
### Direct Type Checking
|
|
144
|
+
import { createSandbox } from "sandlot";
|
|
467
145
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
const result = await typecheck({
|
|
476
|
-
fs: myFilesystem,
|
|
477
|
-
entryPoint: "/src/index.ts",
|
|
478
|
-
tsconfigPath: "/tsconfig.json",
|
|
479
|
-
libFiles: myLibFiles, // Map of lib name to content
|
|
146
|
+
const sandbox = await createSandbox({
|
|
147
|
+
initialFiles: {
|
|
148
|
+
"/src/index.ts": "export const x = 1;",
|
|
149
|
+
"/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }),
|
|
150
|
+
},
|
|
151
|
+
sharedModules: ["react", "react-dom/client"],
|
|
152
|
+
onBuild: (result) => console.log("Build succeeded:", result),
|
|
480
153
|
});
|
|
481
154
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
155
|
+
// Execute shell commands
|
|
156
|
+
await sandbox.bash.exec("tsc /src/index.ts"); // Type check
|
|
157
|
+
await sandbox.bash.exec("build /src/index.ts"); // Bundle
|
|
158
|
+
await sandbox.bash.exec("install lodash"); // Install package
|
|
159
|
+
await sandbox.bash.exec("list"); // List packages
|
|
485
160
|
|
|
486
|
-
|
|
487
|
-
|
|
161
|
+
// Access the last successful build
|
|
162
|
+
if (sandbox.lastBuild) {
|
|
163
|
+
const { bundle, module } = sandbox.lastBuild;
|
|
488
164
|
}
|
|
489
|
-
```
|
|
490
165
|
|
|
491
|
-
|
|
166
|
+
// Serialize state for persistence
|
|
167
|
+
const state = sandbox.getState();
|
|
168
|
+
localStorage.setItem("project", JSON.stringify(state));
|
|
169
|
+
```
|
|
492
170
|
|
|
493
|
-
|
|
494
|
-
import { createIndexedDbFs, IndexedDbFs } from "sandlot";
|
|
171
|
+
### Sandbox Options
|
|
495
172
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
173
|
+
| Option | Type | Description |
|
|
174
|
+
| --------------- | ------------------------ | --------------------------------------------------- |
|
|
175
|
+
| `initialFiles` | `Record<string, string>` | Files to populate the filesystem with. |
|
|
176
|
+
| `sharedModules` | `string[]` | Modules to resolve from host (e.g., `['react']`). |
|
|
177
|
+
| `tsconfigPath` | `string` | Path to tsconfig.json (default: `/tsconfig.json`). |
|
|
178
|
+
| `onBuild` | `(result) => void` | Callback when a build succeeds. |
|
|
179
|
+
| `bashOptions` | `SandboxBashOptions` | Options for the just-bash shell (cwd, env, limits). |
|
|
502
180
|
|
|
503
|
-
|
|
504
|
-
const memFs = IndexedDbFs.createInMemory({
|
|
505
|
-
initialFiles: { "/README.md": "# Hello" },
|
|
506
|
-
});
|
|
181
|
+
## Shell Commands
|
|
507
182
|
|
|
508
|
-
|
|
509
|
-
await fs.writeFile("/src/index.ts", "export const x = 1;");
|
|
510
|
-
const content = await fs.readFile("/src/index.ts");
|
|
511
|
-
const exists = await fs.exists("/src/index.ts");
|
|
512
|
-
await fs.mkdir("/src/utils", { recursive: true });
|
|
513
|
-
await fs.rm("/src/old.ts");
|
|
183
|
+
The sandbox provides these built-in commands:
|
|
514
184
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
185
|
+
| Command | Description |
|
|
186
|
+
| ------------------------- | ----------------------------------- |
|
|
187
|
+
| `tsc [entry]` | Type check (uses tsconfig.json) |
|
|
188
|
+
| `build [entry] [options]` | Bundle (runs typecheck first) |
|
|
189
|
+
| `install <pkg>` | Install npm package (fetches types) |
|
|
190
|
+
| `uninstall <pkg>` | Remove package |
|
|
191
|
+
| `list` | List installed packages |
|
|
192
|
+
| `run <entry>` | Execute a script |
|
|
519
193
|
|
|
520
|
-
|
|
521
|
-
```
|
|
194
|
+
Build options: `--format <esm\|iife\|cjs>`, `--minify`, `--skip-typecheck`
|
|
522
195
|
|
|
523
|
-
|
|
196
|
+
## Shared Modules
|
|
524
197
|
|
|
525
|
-
|
|
198
|
+
To avoid duplicate library instances (important for React context/hooks), register shared modules before creating sandboxes:
|
|
526
199
|
|
|
527
200
|
```typescript
|
|
528
|
-
import {
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
const resources = await createSharedResources({
|
|
532
|
-
libs: ["ES2022", "DOM", "DOM.Iterable"],
|
|
533
|
-
});
|
|
201
|
+
import { registerSharedModules } from "sandlot";
|
|
202
|
+
import * as React from "react";
|
|
203
|
+
import * as ReactDOM from "react-dom/client";
|
|
534
204
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
205
|
+
registerSharedModules({
|
|
206
|
+
react: React,
|
|
207
|
+
"react-dom/client": ReactDOM,
|
|
538
208
|
});
|
|
539
|
-
|
|
540
|
-
// Or use the default singleton
|
|
541
|
-
const defaultResources = await getDefaultResources();
|
|
542
209
|
```
|
|
543
210
|
|
|
544
|
-
|
|
211
|
+
Then include them in `sharedModules` when creating a sandbox.
|
|
545
212
|
|
|
546
|
-
##
|
|
213
|
+
## Cross-Origin Isolation
|
|
547
214
|
|
|
548
|
-
|
|
215
|
+
For optimal esbuild-wasm performance, enable cross-origin isolation by adding these headers to your dev server:
|
|
549
216
|
|
|
550
|
-
```json
|
|
551
|
-
{
|
|
552
|
-
"compilerOptions": {
|
|
553
|
-
"target": "ES2020",
|
|
554
|
-
"module": "ESNext",
|
|
555
|
-
"moduleResolution": "bundler",
|
|
556
|
-
"jsx": "react-jsx",
|
|
557
|
-
"strict": true,
|
|
558
|
-
"esModuleInterop": true,
|
|
559
|
-
"skipLibCheck": true,
|
|
560
|
-
"lib": ["ES2020", "DOM", "DOM.Iterable"]
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
217
|
```
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
- `target`: ES5 through ESNext
|
|
568
|
-
- `module`: CommonJS, ES2015, ES2020, ESNext, Node16, NodeNext
|
|
569
|
-
- `moduleResolution`: Classic, Node, Node16, NodeNext, Bundler
|
|
570
|
-
- `jsx`: preserve, react, react-jsx, react-jsxdev, react-native
|
|
571
|
-
- `strict`, `noImplicitAny`, `strictNullChecks`
|
|
572
|
-
- `esModuleInterop`, `allowJs`, `resolveJsonModule`
|
|
573
|
-
- `lib`: Array of lib names
|
|
574
|
-
|
|
575
|
-
---
|
|
576
|
-
|
|
577
|
-
## API Reference
|
|
578
|
-
|
|
579
|
-
### Exports from `sandlot`
|
|
580
|
-
|
|
581
|
-
```typescript
|
|
582
|
-
// Sandbox API
|
|
583
|
-
export { createSandbox, createInMemorySandbox } from "sandlot";
|
|
584
|
-
export { SandboxManager, createSandboxManager } from "sandlot";
|
|
585
|
-
|
|
586
|
-
// Module Loading
|
|
587
|
-
export { loadModule, loadExport, loadDefault } from "sandlot";
|
|
588
|
-
export { getExportNames, hasExport } from "sandlot";
|
|
589
|
-
|
|
590
|
-
// Shared Modules
|
|
591
|
-
export { registerSharedModules, clearSharedModules } from "sandlot";
|
|
592
|
-
|
|
593
|
-
// Types
|
|
594
|
-
export type { Sandbox, SandboxOptions } from "sandlot";
|
|
595
|
-
export type { BundleResult, TypecheckResult } from "sandlot";
|
|
218
|
+
Cross-Origin-Embedder-Policy: require-corp
|
|
219
|
+
Cross-Origin-Opener-Policy: same-origin
|
|
596
220
|
```
|
|
597
221
|
|
|
598
|
-
|
|
222
|
+
In Vite:
|
|
599
223
|
|
|
600
224
|
```typescript
|
|
601
|
-
export {
|
|
602
|
-
|
|
603
|
-
|
|
225
|
+
export default defineConfig({
|
|
226
|
+
plugins: [
|
|
227
|
+
{
|
|
228
|
+
name: "isolation",
|
|
229
|
+
configureServer: (server) => {
|
|
230
|
+
server.middlewares.use((_, res, next) => {
|
|
231
|
+
res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
|
|
232
|
+
res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
|
|
233
|
+
next();
|
|
234
|
+
});
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
],
|
|
238
|
+
});
|
|
604
239
|
```
|
|
605
240
|
|
|
606
|
-
---
|
|
607
|
-
|
|
608
|
-
## Requirements
|
|
609
|
-
|
|
610
|
-
- Modern browser with ES2020 support
|
|
611
|
-
- IndexedDB (for persistent filesystems)
|
|
612
|
-
- WebAssembly (for esbuild)
|
|
613
|
-
|
|
614
241
|
## License
|
|
615
242
|
|
|
616
243
|
MIT
|