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/sandbox.ts
CHANGED
|
@@ -1,92 +1,40 @@
|
|
|
1
|
-
import { Bash, defineCommand } from "just-bash/browser";
|
|
2
|
-
import {
|
|
3
|
-
import { initBundler
|
|
4
|
-
import { createDefaultCommands, type CommandDeps } from "./commands";
|
|
1
|
+
import { Bash, defineCommand, type BashOptions } from "just-bash/browser";
|
|
2
|
+
import { Filesystem, type FilesystemOptions } from "./fs";
|
|
3
|
+
import { initBundler } from "./bundler";
|
|
4
|
+
import { createDefaultCommands, type CommandDeps, type BuildOutput, type ValidateFn } from "./commands";
|
|
5
5
|
import { getDefaultResources, type SharedResources } from "./shared-resources";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export type { BundleResult } from "./bundler";
|
|
9
|
-
export type { TypecheckResult } from "./typechecker";
|
|
10
|
-
export type { SharedResources, TypesCache } from "./shared-resources";
|
|
11
|
-
export type { PackageManifest, InstallResult } from "./packages";
|
|
12
|
-
export type { RunContext, RunOptions, RunResult } from "./commands";
|
|
13
|
-
export { installPackage, uninstallPackage, listPackages, getPackageManifest } from "./packages";
|
|
14
|
-
export { InMemoryTypesCache } from "./shared-resources";
|
|
15
|
-
|
|
16
|
-
// Loader utilities
|
|
17
|
-
export {
|
|
18
|
-
loadModule,
|
|
19
|
-
loadExport,
|
|
20
|
-
loadDefault,
|
|
21
|
-
getExportNames,
|
|
22
|
-
hasExport,
|
|
23
|
-
createModuleUrl,
|
|
24
|
-
revokeModuleUrl,
|
|
25
|
-
ModuleLoadError,
|
|
26
|
-
ExportNotFoundError,
|
|
27
|
-
} from "./loader";
|
|
6
|
+
import { BuildEmitter } from "./build-emitter";
|
|
7
|
+
import { installPackage, parseImportPath } from "./packages";
|
|
28
8
|
|
|
29
9
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
10
|
+
* Options that can be passed through to the just-bash Bash constructor.
|
|
11
|
+
* Excludes options that sandlot controls internally (fs, customCommands, files).
|
|
32
12
|
*/
|
|
33
|
-
|
|
34
|
-
private listeners = new Set<(result: BundleResult) => void | Promise<void>>();
|
|
35
|
-
private lastResult: BundleResult | null = null;
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Emit a build result to all listeners and cache it
|
|
39
|
-
*/
|
|
40
|
-
emit = async (result: BundleResult): Promise<void> => {
|
|
41
|
-
this.lastResult = result;
|
|
42
|
-
const promises: Promise<void>[] = [];
|
|
43
|
-
for (const listener of this.listeners) {
|
|
44
|
-
const ret = listener(result);
|
|
45
|
-
if (ret instanceof Promise) {
|
|
46
|
-
promises.push(ret);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
await Promise.all(promises);
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Subscribe to build events. Returns an unsubscribe function.
|
|
54
|
-
*/
|
|
55
|
-
on(callback: (result: BundleResult) => void | Promise<void>): () => void {
|
|
56
|
-
this.listeners.add(callback);
|
|
57
|
-
return () => {
|
|
58
|
-
this.listeners.delete(callback);
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Get the last build result, or wait for the next one if none exists.
|
|
64
|
-
* Clears the cached result after returning, so subsequent calls wait for new builds.
|
|
65
|
-
*/
|
|
66
|
-
waitFor(): Promise<BundleResult> {
|
|
67
|
-
if (this.lastResult) {
|
|
68
|
-
const result = this.lastResult;
|
|
69
|
-
this.lastResult = null;
|
|
70
|
-
return Promise.resolve(result);
|
|
71
|
-
}
|
|
72
|
-
return new Promise((resolve) => {
|
|
73
|
-
const unsub = this.on((result) => {
|
|
74
|
-
unsub();
|
|
75
|
-
this.lastResult = null;
|
|
76
|
-
resolve(result);
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
}
|
|
13
|
+
export type SandboxBashOptions = Omit<BashOptions, 'fs' | 'customCommands' | 'files'>;
|
|
81
14
|
|
|
82
15
|
/**
|
|
83
16
|
* Options for creating a sandbox environment
|
|
84
17
|
*/
|
|
85
18
|
export interface SandboxOptions {
|
|
86
19
|
/**
|
|
87
|
-
*
|
|
20
|
+
* Initial files to populate the filesystem with.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* const sandbox = await createSandbox({
|
|
25
|
+
* initialFiles: {
|
|
26
|
+
* '/src/index.ts': 'export const x = 1;',
|
|
27
|
+
* '/tsconfig.json': JSON.stringify({ compilerOptions: { strict: true } }),
|
|
28
|
+
* },
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
initialFiles?: Record<string, string>;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Maximum filesystem size in bytes (default: 50MB)
|
|
88
36
|
*/
|
|
89
|
-
|
|
37
|
+
maxFilesystemSize?: number;
|
|
90
38
|
|
|
91
39
|
/**
|
|
92
40
|
* Path to tsconfig.json in the virtual filesystem.
|
|
@@ -105,10 +53,12 @@ export interface SandboxOptions {
|
|
|
105
53
|
|
|
106
54
|
/**
|
|
107
55
|
* Callback invoked when a build succeeds.
|
|
108
|
-
* Receives the
|
|
109
|
-
*
|
|
56
|
+
* Receives the build output with the bundle and loaded module.
|
|
57
|
+
*
|
|
58
|
+
* For agent workflows, prefer using `createBuilder()` which handles
|
|
59
|
+
* build capture automatically.
|
|
110
60
|
*/
|
|
111
|
-
onBuild?: (result:
|
|
61
|
+
onBuild?: (result: BuildOutput) => void | Promise<void>;
|
|
112
62
|
|
|
113
63
|
/**
|
|
114
64
|
* Additional custom commands to add to the bash environment
|
|
@@ -119,29 +69,64 @@ export interface SandboxOptions {
|
|
|
119
69
|
* Module IDs that should be resolved from the host's SharedModuleRegistry
|
|
120
70
|
* instead of esm.sh CDN. The host must have registered these modules
|
|
121
71
|
* using `registerSharedModules()` before loading dynamic code.
|
|
122
|
-
*
|
|
72
|
+
*
|
|
123
73
|
* This solves the "multiple React instances" problem by allowing dynamic
|
|
124
74
|
* components to share the same React instance as the host application.
|
|
125
|
-
*
|
|
75
|
+
*
|
|
76
|
+
* Type definitions are automatically installed for these modules so that
|
|
77
|
+
* TypeScript can typecheck code that imports them.
|
|
78
|
+
*
|
|
126
79
|
* @example
|
|
127
80
|
* ```ts
|
|
128
81
|
* // Host setup
|
|
129
82
|
* import * as React from 'react';
|
|
130
83
|
* import * as ReactDOM from 'react-dom/client';
|
|
131
84
|
* import { registerSharedModules } from 'sandlot';
|
|
132
|
-
*
|
|
85
|
+
*
|
|
133
86
|
* registerSharedModules({
|
|
134
87
|
* 'react': React,
|
|
135
88
|
* 'react-dom/client': ReactDOM,
|
|
136
89
|
* });
|
|
137
|
-
*
|
|
138
|
-
* // Create sandbox with shared modules
|
|
139
|
-
* const sandbox = await
|
|
90
|
+
*
|
|
91
|
+
* // Create sandbox with shared modules (types auto-installed)
|
|
92
|
+
* const sandbox = await createSandbox({
|
|
140
93
|
* sharedModules: ['react', 'react-dom/client'],
|
|
141
94
|
* });
|
|
142
95
|
* ```
|
|
143
96
|
*/
|
|
144
97
|
sharedModules?: string[];
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Options passed through to the just-bash Bash constructor.
|
|
101
|
+
* Use this to configure environment variables, execution limits,
|
|
102
|
+
* network access, logging, and other bash-level settings.
|
|
103
|
+
*
|
|
104
|
+
* Note: `fs`, `customCommands`, and `files` are controlled by sandlot
|
|
105
|
+
* and cannot be overridden here.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```ts
|
|
109
|
+
* const sandbox = await createSandbox({
|
|
110
|
+
* bashOptions: {
|
|
111
|
+
* cwd: '/src',
|
|
112
|
+
* env: { NODE_ENV: 'development' },
|
|
113
|
+
* executionLimits: { maxCommandCount: 1000 },
|
|
114
|
+
* },
|
|
115
|
+
* });
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
bashOptions?: SandboxBashOptions;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Sandbox state that can be serialized for persistence.
|
|
123
|
+
*/
|
|
124
|
+
export interface SandboxState {
|
|
125
|
+
/**
|
|
126
|
+
* All files in the filesystem as path -> content mapping.
|
|
127
|
+
* Can be passed as `initialFiles` when creating a new sandbox.
|
|
128
|
+
*/
|
|
129
|
+
files: Record<string, string>;
|
|
145
130
|
}
|
|
146
131
|
|
|
147
132
|
/**
|
|
@@ -149,9 +134,9 @@ export interface SandboxOptions {
|
|
|
149
134
|
*/
|
|
150
135
|
export interface Sandbox {
|
|
151
136
|
/**
|
|
152
|
-
* The virtual filesystem
|
|
137
|
+
* The virtual filesystem
|
|
153
138
|
*/
|
|
154
|
-
fs:
|
|
139
|
+
fs: Filesystem;
|
|
155
140
|
|
|
156
141
|
/**
|
|
157
142
|
* The just-bash shell environment
|
|
@@ -159,55 +144,94 @@ export interface Sandbox {
|
|
|
159
144
|
bash: Bash;
|
|
160
145
|
|
|
161
146
|
/**
|
|
162
|
-
*
|
|
163
|
-
*
|
|
147
|
+
* The last successful build output, or null if no build has succeeded yet.
|
|
148
|
+
*
|
|
149
|
+
* This is updated automatically whenever a `build` command succeeds.
|
|
150
|
+
* Contains both the bundle and the loaded (and validated, if applicable) module.
|
|
151
|
+
*
|
|
164
152
|
* @example
|
|
165
153
|
* ```ts
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
* await
|
|
169
|
-
*
|
|
154
|
+
* // Agent loop pattern
|
|
155
|
+
* while (!sandbox.lastBuild) {
|
|
156
|
+
* const response = await agent.step();
|
|
157
|
+
* await sandbox.bash.exec(response.command);
|
|
158
|
+
* }
|
|
159
|
+
* // Build succeeded, sandbox.lastBuild contains bundle + module
|
|
160
|
+
* const App = sandbox.lastBuild.module.App;
|
|
170
161
|
* ```
|
|
171
162
|
*/
|
|
172
|
-
|
|
163
|
+
lastBuild: BuildOutput | null;
|
|
173
164
|
|
|
174
165
|
/**
|
|
175
|
-
*
|
|
166
|
+
* Get the current sandbox state for persistence.
|
|
167
|
+
*
|
|
168
|
+
* Returns a serializable object containing all files that can be
|
|
169
|
+
* JSON-serialized and used to restore the sandbox later.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```ts
|
|
173
|
+
* // Save sandbox state
|
|
174
|
+
* const state = sandbox.getState();
|
|
175
|
+
* localStorage.setItem('my-project', JSON.stringify(state));
|
|
176
176
|
*
|
|
177
|
-
*
|
|
177
|
+
* // Later, restore the sandbox
|
|
178
|
+
* const saved = JSON.parse(localStorage.getItem('my-project'));
|
|
179
|
+
* const sandbox2 = await createSandbox({ initialFiles: saved.files });
|
|
180
|
+
* ```
|
|
178
181
|
*/
|
|
179
|
-
|
|
182
|
+
getState(): SandboxState;
|
|
180
183
|
|
|
181
184
|
/**
|
|
182
|
-
*
|
|
185
|
+
* Subscribe to build events. Called whenever a build succeeds.
|
|
186
|
+
* Returns an unsubscribe function.
|
|
187
|
+
*
|
|
188
|
+
* For agent workflows, prefer using `createBuilder()` which handles
|
|
189
|
+
* build capture automatically. Use `onBuild` directly when you need
|
|
190
|
+
* more control over the subscription lifecycle.
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* ```ts
|
|
194
|
+
* let lastBuild: BuildOutput | null = null;
|
|
195
|
+
* const unsubscribe = sandbox.onBuild((result) => {
|
|
196
|
+
* lastBuild = result;
|
|
197
|
+
* });
|
|
198
|
+
*
|
|
199
|
+
* await sandbox.bash.exec('build /src/index.ts');
|
|
200
|
+
* unsubscribe();
|
|
201
|
+
*
|
|
202
|
+
* if (lastBuild) {
|
|
203
|
+
* const App = lastBuild.module.App as React.ComponentType;
|
|
204
|
+
* }
|
|
205
|
+
* ```
|
|
183
206
|
*/
|
|
184
|
-
|
|
207
|
+
onBuild(callback: (result: BuildOutput) => void | Promise<void>): () => void;
|
|
185
208
|
|
|
186
209
|
/**
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
210
|
+
* Set a validation function for the build command.
|
|
211
|
+
*
|
|
212
|
+
* When set, the build command will run this function after loading
|
|
213
|
+
* the module. If validation throws, the build fails and the agent
|
|
214
|
+
* sees the error. If validation passes, the validated module is
|
|
215
|
+
* available in the build output.
|
|
216
|
+
*
|
|
193
217
|
* @example
|
|
194
218
|
* ```ts
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
* onBuild: (result) => {
|
|
199
|
-
* bundle = result;
|
|
200
|
-
* console.log('Built:', result.code.length, 'bytes');
|
|
201
|
-
* },
|
|
219
|
+
* sandbox.setValidation((mod) => {
|
|
220
|
+
* if (!mod.App) throw new Error("Must export App component");
|
|
221
|
+
* return { App: mod.App as React.ComponentType };
|
|
202
222
|
* });
|
|
203
|
-
*
|
|
204
|
-
* //
|
|
205
|
-
*
|
|
206
|
-
* // After successful build, bundle is available
|
|
207
|
-
* console.log(bundle?.code);
|
|
223
|
+
*
|
|
224
|
+
* // Now build will fail if App is missing
|
|
225
|
+
* await sandbox.bash.exec('build /src/index.ts');
|
|
208
226
|
* ```
|
|
209
227
|
*/
|
|
210
|
-
|
|
228
|
+
setValidation(fn: ValidateFn): void;
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Clear the validation function.
|
|
232
|
+
* After calling this, builds will not perform validation.
|
|
233
|
+
*/
|
|
234
|
+
clearValidation(): void;
|
|
211
235
|
}
|
|
212
236
|
|
|
213
237
|
/**
|
|
@@ -217,9 +241,12 @@ export interface Sandbox {
|
|
|
217
241
|
* The sandbox provides a just-bash shell with custom commands:
|
|
218
242
|
* - `tsc [entry]` - Type check the project
|
|
219
243
|
* - `build [entry] [options]` - Build the project (runs typecheck first)
|
|
244
|
+
* - `install <pkg>` - Install npm packages
|
|
245
|
+
* - `uninstall <pkg>` - Remove packages
|
|
246
|
+
* - `list` - List installed packages
|
|
247
|
+
* - `run <entry>` - Run a script
|
|
220
248
|
*
|
|
221
249
|
* Build options:
|
|
222
|
-
* - `--output, -o <path>` - Output path (default: /dist/bundle.js)
|
|
223
250
|
* - `--format, -f <esm|iife|cjs>` - Output format (default: esm)
|
|
224
251
|
* - `--minify, -m` - Enable minification
|
|
225
252
|
* - `--skip-typecheck, -s` - Skip type checking
|
|
@@ -229,18 +256,14 @@ export interface Sandbox {
|
|
|
229
256
|
* let bundleResult: BundleResult | null = null;
|
|
230
257
|
*
|
|
231
258
|
* const sandbox = await createSandbox({
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
* compilerOptions: { target: "ES2020", strict: true }
|
|
238
|
-
* }),
|
|
239
|
-
* },
|
|
259
|
+
* initialFiles: {
|
|
260
|
+
* '/src/index.ts': 'export const hello = "world";',
|
|
261
|
+
* '/tsconfig.json': JSON.stringify({
|
|
262
|
+
* compilerOptions: { target: 'ES2020', strict: true }
|
|
263
|
+
* }),
|
|
240
264
|
* },
|
|
241
265
|
* onBuild: (result) => {
|
|
242
266
|
* bundleResult = result;
|
|
243
|
-
* // Could also: dynamically import, halt agent, etc.
|
|
244
267
|
* },
|
|
245
268
|
* });
|
|
246
269
|
*
|
|
@@ -248,137 +271,100 @@ export interface Sandbox {
|
|
|
248
271
|
* await sandbox.bash.exec('echo "console.log(1);" > /src/index.ts');
|
|
249
272
|
*
|
|
250
273
|
* // Type check
|
|
251
|
-
* const tscResult = await sandbox.bash.exec(
|
|
274
|
+
* const tscResult = await sandbox.bash.exec('tsc /src/index.ts');
|
|
252
275
|
* console.log(tscResult.stdout);
|
|
253
276
|
*
|
|
254
277
|
* // Build (includes typecheck, triggers onBuild callback)
|
|
255
|
-
* const buildResult = await sandbox.bash.exec(
|
|
278
|
+
* const buildResult = await sandbox.bash.exec('build /src/index.ts');
|
|
256
279
|
* console.log(buildResult.stdout);
|
|
257
280
|
* console.log(bundleResult?.code); // The compiled bundle
|
|
258
281
|
*
|
|
259
|
-
* // Save
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
* // Clean up
|
|
263
|
-
* sandbox.close();
|
|
282
|
+
* // Save state for later
|
|
283
|
+
* const state = sandbox.getState();
|
|
284
|
+
* localStorage.setItem('my-project', JSON.stringify(state));
|
|
264
285
|
* ```
|
|
265
286
|
*/
|
|
266
287
|
export async function createSandbox(options: SandboxOptions = {}): Promise<Sandbox> {
|
|
267
288
|
const {
|
|
268
|
-
|
|
289
|
+
initialFiles,
|
|
290
|
+
maxFilesystemSize,
|
|
269
291
|
tsconfigPath = "/tsconfig.json",
|
|
270
292
|
resources: providedResources,
|
|
271
293
|
onBuild: onBuildCallback,
|
|
272
294
|
customCommands = [],
|
|
273
295
|
sharedModules,
|
|
296
|
+
bashOptions = {},
|
|
274
297
|
} = options;
|
|
275
298
|
|
|
276
|
-
//
|
|
277
|
-
const
|
|
299
|
+
// Create filesystem (synchronous)
|
|
300
|
+
const fs = Filesystem.create({
|
|
301
|
+
initialFiles,
|
|
302
|
+
maxSizeBytes: maxFilesystemSize,
|
|
303
|
+
});
|
|
278
304
|
|
|
279
|
-
//
|
|
280
|
-
// 1. Provided resources (preferred)
|
|
281
|
-
// 2. Default resources singleton
|
|
305
|
+
// Load shared resources and bundler in parallel
|
|
282
306
|
const resourcesPromise = providedResources
|
|
283
307
|
? Promise.resolve(providedResources)
|
|
284
308
|
: getDefaultResources();
|
|
285
309
|
|
|
286
310
|
const bundlerPromise = initBundler();
|
|
287
311
|
|
|
288
|
-
// Wait for
|
|
289
|
-
const [
|
|
312
|
+
// Wait for async initialization
|
|
313
|
+
const [resources] = await Promise.all([resourcesPromise, bundlerPromise]);
|
|
290
314
|
|
|
291
315
|
// Extract lib files and types cache from resources
|
|
292
316
|
const libFiles = resources.libFiles;
|
|
293
317
|
const typesCache = resources.typesCache;
|
|
294
318
|
|
|
295
|
-
//
|
|
296
|
-
|
|
319
|
+
// Auto-install types for shared modules so TypeScript can typecheck them
|
|
320
|
+
// Only install base packages, not subpath exports (e.g., "react" not "react/jsx-runtime")
|
|
321
|
+
// Subpath types are fetched automatically when the base package is installed
|
|
322
|
+
if (sharedModules && sharedModules.length > 0) {
|
|
323
|
+
// Extract unique base package names
|
|
324
|
+
const basePackages = new Set<string>();
|
|
325
|
+
for (const moduleId of sharedModules) {
|
|
326
|
+
const { packageName } = parseImportPath(moduleId);
|
|
327
|
+
basePackages.add(packageName);
|
|
328
|
+
}
|
|
297
329
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
330
|
+
await Promise.all(
|
|
331
|
+
Array.from(basePackages).map(async (packageName) => {
|
|
332
|
+
try {
|
|
333
|
+
// Install the package to get its type definitions
|
|
334
|
+
// The runtime will use the shared module, but we need types for typechecking
|
|
335
|
+
await installPackage(fs, packageName, { cache: typesCache });
|
|
336
|
+
} catch (err) {
|
|
337
|
+
// Log but don't fail - module might not have types available
|
|
338
|
+
console.warn(`[sandlot] Failed to install types for shared module "${packageName}":`, err);
|
|
339
|
+
}
|
|
340
|
+
})
|
|
341
|
+
);
|
|
301
342
|
}
|
|
302
343
|
|
|
303
|
-
// Create commands using the extracted factories
|
|
304
|
-
// Commands emit to the build emitter
|
|
305
|
-
const commandDeps: CommandDeps = {
|
|
306
|
-
fs,
|
|
307
|
-
libFiles,
|
|
308
|
-
tsconfigPath,
|
|
309
|
-
onBuild: buildEmitter.emit,
|
|
310
|
-
typesCache,
|
|
311
|
-
sharedModules,
|
|
312
|
-
};
|
|
313
|
-
const defaultCommands = createDefaultCommands(commandDeps);
|
|
314
|
-
|
|
315
|
-
// Create bash environment with the custom filesystem
|
|
316
|
-
const bash = new Bash({
|
|
317
|
-
fs,
|
|
318
|
-
cwd: "/",
|
|
319
|
-
customCommands: [...defaultCommands, ...customCommands],
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
return {
|
|
323
|
-
fs,
|
|
324
|
-
bash,
|
|
325
|
-
isDirty: () => fs.isDirty(),
|
|
326
|
-
save: () => fs.save(),
|
|
327
|
-
close: () => fs.close(),
|
|
328
|
-
onBuild: (callback) => buildEmitter.on(callback),
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* Create an in-memory sandbox (no IndexedDB persistence).
|
|
334
|
-
* Useful for testing or temporary workspaces.
|
|
335
|
-
*/
|
|
336
|
-
export async function createInMemorySandbox(
|
|
337
|
-
options: Omit<SandboxOptions, "fsOptions"> & {
|
|
338
|
-
initialFiles?: Record<string, string>;
|
|
339
|
-
} = {}
|
|
340
|
-
): Promise<Sandbox> {
|
|
341
|
-
const {
|
|
342
|
-
initialFiles,
|
|
343
|
-
tsconfigPath = "/tsconfig.json",
|
|
344
|
-
resources: providedResources,
|
|
345
|
-
onBuild: onBuildCallback,
|
|
346
|
-
customCommands = [],
|
|
347
|
-
sharedModules,
|
|
348
|
-
} = options;
|
|
349
|
-
|
|
350
|
-
// Determine which resources to use
|
|
351
|
-
const resourcesPromise = providedResources
|
|
352
|
-
? Promise.resolve(providedResources)
|
|
353
|
-
: getDefaultResources();
|
|
354
|
-
|
|
355
|
-
const bundlerPromise = initBundler();
|
|
356
|
-
|
|
357
|
-
// Create in-memory filesystem synchronously
|
|
358
|
-
const fs = IndexedDbFs.createInMemory({ initialFiles });
|
|
359
|
-
|
|
360
|
-
// Wait for resources and bundler
|
|
361
|
-
const [resources] = await Promise.all([resourcesPromise, bundlerPromise]);
|
|
362
|
-
|
|
363
|
-
// Extract lib files and types cache from resources
|
|
364
|
-
const libFiles = resources.libFiles;
|
|
365
|
-
const typesCache = resources.typesCache;
|
|
366
|
-
|
|
367
344
|
// Create build event emitter
|
|
368
345
|
const buildEmitter = new BuildEmitter();
|
|
369
346
|
|
|
347
|
+
// Track the last successful build
|
|
348
|
+
let lastBuild: BuildOutput | null = null;
|
|
349
|
+
buildEmitter.on((result) => {
|
|
350
|
+
lastBuild = result;
|
|
351
|
+
});
|
|
352
|
+
|
|
370
353
|
// If a callback was provided in options, subscribe it
|
|
371
354
|
if (onBuildCallback) {
|
|
372
355
|
buildEmitter.on(onBuildCallback);
|
|
373
356
|
}
|
|
374
357
|
|
|
375
|
-
//
|
|
376
|
-
|
|
358
|
+
// Validation function (can be set/cleared dynamically)
|
|
359
|
+
let validationFn: ValidateFn | null = null;
|
|
360
|
+
|
|
361
|
+
// Create commands
|
|
377
362
|
const commandDeps: CommandDeps = {
|
|
378
363
|
fs,
|
|
379
364
|
libFiles,
|
|
380
365
|
tsconfigPath,
|
|
381
366
|
onBuild: buildEmitter.emit,
|
|
367
|
+
getValidation: () => validationFn,
|
|
382
368
|
typesCache,
|
|
383
369
|
sharedModules,
|
|
384
370
|
};
|
|
@@ -386,17 +372,25 @@ export async function createInMemorySandbox(
|
|
|
386
372
|
|
|
387
373
|
// Create bash environment with the custom filesystem
|
|
388
374
|
const bash = new Bash({
|
|
375
|
+
...bashOptions,
|
|
389
376
|
fs,
|
|
390
|
-
cwd: "/",
|
|
391
377
|
customCommands: [...defaultCommands, ...customCommands],
|
|
392
378
|
});
|
|
393
379
|
|
|
394
380
|
return {
|
|
395
381
|
fs,
|
|
396
382
|
bash,
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
383
|
+
get lastBuild() {
|
|
384
|
+
return lastBuild;
|
|
385
|
+
},
|
|
386
|
+
getState: () => ({ files: fs.getFiles() }),
|
|
400
387
|
onBuild: (callback) => buildEmitter.on(callback),
|
|
388
|
+
setValidation: (fn: ValidateFn) => {
|
|
389
|
+
validationFn = fn;
|
|
390
|
+
},
|
|
391
|
+
clearValidation: () => {
|
|
392
|
+
validationFn = null;
|
|
393
|
+
},
|
|
401
394
|
};
|
|
402
395
|
}
|
|
396
|
+
|