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/fs.ts CHANGED
@@ -23,9 +23,12 @@ interface ReadFileOptions {
23
23
  }
24
24
 
25
25
  /**
26
- * Options for writing files
26
+ * Options for writing files.
27
+ * Note: In this browser-based filesystem, content is stored as-is.
28
+ * The encoding option is accepted for API compatibility but not used.
27
29
  */
28
30
  interface WriteFileOptions {
31
+ /** Accepted for API compatibility but not used - content is stored as-is */
29
32
  encoding?: BufferEncoding;
30
33
  }
31
34
 
@@ -45,85 +48,54 @@ const DEFAULT_SYMLINK_MODE = 0o777;
45
48
  const DEFAULT_MAX_SIZE_BYTES = 50 * 1024 * 1024; // 50MB default limit
46
49
 
47
50
  /**
48
- * Options for creating an IndexedDbFs instance
51
+ * Options for creating a Filesystem instance
49
52
  */
50
- export interface IndexedDbFsOptions {
51
- /** Database name in IndexedDB */
52
- dbName?: string;
53
+ export interface FilesystemOptions {
53
54
  /** Maximum total size in bytes (default: 50MB) */
54
55
  maxSizeBytes?: number;
55
- /** Initial files to populate (only used if DB is empty) */
56
+ /** Initial files to populate */
56
57
  initialFiles?: InitialFiles;
57
58
  }
58
59
 
59
60
  /**
60
- * In-memory filesystem with IndexedDB persistence.
61
- * All operations are fast (in-memory), persistence is manual via save().
61
+ * In-memory virtual filesystem for sandlot sandboxes.
62
+ *
63
+ * All operations are synchronous in-memory. Use `getFiles()` to export
64
+ * the current state for persistence, and `initialFiles` to restore.
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * // Create filesystem
69
+ * const fs = Filesystem.create({ initialFiles: { '/src/index.ts': 'export const x = 1;' } });
70
+ *
71
+ * // Use filesystem
72
+ * await fs.writeFile('/src/app.ts', 'console.log("hello")');
73
+ *
74
+ * // Export for persistence
75
+ * const files = fs.getFiles();
76
+ * localStorage.setItem('my-project', JSON.stringify(files));
77
+ *
78
+ * // Later, restore
79
+ * const saved = JSON.parse(localStorage.getItem('my-project'));
80
+ * const fs2 = Filesystem.create({ initialFiles: saved });
81
+ * ```
62
82
  */
63
- export class IndexedDbFs implements IFileSystem {
83
+ export class Filesystem implements IFileSystem {
64
84
  private entries: Map<string, FsEntry>;
65
- private db: IDBDatabase | null = null;
66
- private dbName: string;
67
85
  private maxSizeBytes: number;
68
- private dirty = false;
69
86
 
70
87
  private constructor(
71
88
  entries: Map<string, FsEntry>,
72
- db: IDBDatabase | null,
73
- dbName: string,
74
89
  maxSizeBytes: number
75
90
  ) {
76
91
  this.entries = entries;
77
- this.db = db;
78
- this.dbName = dbName;
79
92
  this.maxSizeBytes = maxSizeBytes;
80
93
  }
81
94
 
82
95
  /**
83
- * Create and initialize a new IndexedDbFs instance
96
+ * Create a new Filesystem instance
84
97
  */
85
- static async create(options: IndexedDbFsOptions = {}): Promise<IndexedDbFs> {
86
- const dbName = options.dbName ?? "sandlot-fs";
87
- const maxSizeBytes = options.maxSizeBytes ?? DEFAULT_MAX_SIZE_BYTES;
88
-
89
- const db = await IndexedDbFs.openDatabase(dbName);
90
- const entries = await IndexedDbFs.loadEntries(db);
91
-
92
- // If empty and initialFiles provided, populate
93
- if (entries.size === 0) {
94
- // Always ensure root exists
95
- entries.set("/", {
96
- type: "directory",
97
- mode: DEFAULT_DIR_MODE,
98
- mtime: new Date(),
99
- });
100
-
101
- if (options.initialFiles) {
102
- for (const [path, value] of Object.entries(options.initialFiles)) {
103
- const normalizedPath = IndexedDbFs.normalizePath(path);
104
- const init = IndexedDbFs.parseFileInit(value);
105
-
106
- // Ensure parent directories exist
107
- IndexedDbFs.ensureParentDirs(entries, normalizedPath);
108
-
109
- entries.set(normalizedPath, {
110
- type: "file",
111
- content: init.content,
112
- mode: init.mode ?? DEFAULT_FILE_MODE,
113
- mtime: init.mtime ?? new Date(),
114
- });
115
- }
116
- }
117
- }
118
-
119
- const fs = new IndexedDbFs(entries, db, dbName, maxSizeBytes);
120
- return fs;
121
- }
122
-
123
- /**
124
- * Create an in-memory only instance (no IndexedDB)
125
- */
126
- static createInMemory(options: Omit<IndexedDbFsOptions, "dbName"> = {}): IndexedDbFs {
98
+ static create(options: FilesystemOptions = {}): Filesystem {
127
99
  const maxSizeBytes = options.maxSizeBytes ?? DEFAULT_MAX_SIZE_BYTES;
128
100
  const entries = new Map<string, FsEntry>();
129
101
 
@@ -136,11 +108,11 @@ export class IndexedDbFs implements IFileSystem {
136
108
 
137
109
  if (options.initialFiles) {
138
110
  for (const [path, value] of Object.entries(options.initialFiles)) {
139
- const normalizedPath = IndexedDbFs.normalizePath(path);
140
- const init = IndexedDbFs.parseFileInit(value);
111
+ const normalizedPath = Filesystem.normalizePath(path);
112
+ const init = Filesystem.parseFileInit(value);
141
113
 
142
114
  // Ensure parent directories exist
143
- IndexedDbFs.ensureParentDirs(entries, normalizedPath);
115
+ Filesystem.ensureParentDirs(entries, normalizedPath);
144
116
 
145
117
  entries.set(normalizedPath, {
146
118
  type: "file",
@@ -151,52 +123,50 @@ export class IndexedDbFs implements IFileSystem {
151
123
  }
152
124
  }
153
125
 
154
- return new IndexedDbFs(entries, null, "", maxSizeBytes);
126
+ return new Filesystem(entries, maxSizeBytes);
155
127
  }
156
128
 
157
- // ============ Persistence Methods ============
129
+ // ============ State Export ============
158
130
 
159
131
  /**
160
- * Save all entries to IndexedDB
161
- * @returns true if saved, false if no db or not dirty
132
+ * Get all files as a serializable object.
133
+ *
134
+ * Returns a Record<string, string> that can be JSON-serialized and
135
+ * used as `initialFiles` when creating a new filesystem.
136
+ *
137
+ * Note: Only includes files, not directories (directories are
138
+ * automatically created from file paths). Binary files are
139
+ * base64-encoded with a `data:` prefix.
140
+ *
141
+ * @example
142
+ * ```ts
143
+ * const files = fs.getFiles();
144
+ * // { '/src/index.ts': 'export const x = 1;', '/package.json': '{"name":"app"}' }
145
+ *
146
+ * // Persist however you want
147
+ * localStorage.setItem('project', JSON.stringify(files));
148
+ *
149
+ * // Restore later
150
+ * const saved = JSON.parse(localStorage.getItem('project'));
151
+ * const fs2 = Filesystem.create({ initialFiles: saved });
152
+ * ```
162
153
  */
163
- async save(): Promise<boolean> {
164
- if (!this.db || !this.dirty) {
165
- return false;
166
- }
167
-
168
- const tx = this.db.transaction("entries", "readwrite");
169
- const store = tx.objectStore("entries");
170
-
171
- // Clear and rewrite all entries
172
- await this.promisifyRequest(store.clear());
173
-
154
+ getFiles(): Record<string, string> {
155
+ const files: Record<string, string> = {};
156
+
174
157
  for (const [path, entry] of this.entries) {
175
- store.put({ path, entry: this.serializeEntry(entry) });
176
- }
177
-
178
- await this.promisifyTransaction(tx);
179
- this.dirty = false;
180
- return true;
181
- }
182
-
183
- /**
184
- * Reload entries from IndexedDB, discarding unsaved changes
185
- */
186
- async reload(): Promise<void> {
187
- if (!this.db) {
188
- return;
158
+ if (entry.type === "file") {
159
+ if (typeof entry.content === "string") {
160
+ files[path] = entry.content;
161
+ } else {
162
+ // Binary content - base64 encode with data URI prefix
163
+ const base64 = this.encodeBase64(entry.content);
164
+ files[path] = `data:application/octet-stream;base64,${base64}`;
165
+ }
166
+ }
189
167
  }
190
-
191
- this.entries = await IndexedDbFs.loadEntries(this.db);
192
- this.dirty = false;
193
- }
194
-
195
- /**
196
- * Check if there are unsaved changes
197
- */
198
- isDirty(): boolean {
199
- return this.dirty;
168
+
169
+ return files;
200
170
  }
201
171
 
202
172
  /**
@@ -217,16 +187,6 @@ export class IndexedDbFs implements IFileSystem {
217
187
  return size;
218
188
  }
219
189
 
220
- /**
221
- * Close the database connection
222
- */
223
- close(): void {
224
- if (this.db) {
225
- this.db.close();
226
- this.db = null;
227
- }
228
- }
229
-
230
190
  // ============ IFileSystem Implementation ============
231
191
 
232
192
  async readFile(
@@ -276,7 +236,7 @@ export class IndexedDbFs implements IFileSystem {
276
236
  async writeFile(
277
237
  path: string,
278
238
  content: FileContent,
279
- options?: WriteFileOptions | BufferEncoding
239
+ _options?: WriteFileOptions | BufferEncoding
280
240
  ): Promise<void> {
281
241
  const normalizedPath = this.normalizePath(path);
282
242
  this.checkSizeLimit(content);
@@ -293,7 +253,6 @@ export class IndexedDbFs implements IFileSystem {
293
253
  mode: existing?.mode ?? DEFAULT_FILE_MODE,
294
254
  mtime: new Date(),
295
255
  });
296
- this.dirty = true;
297
256
  }
298
257
 
299
258
  async appendFile(
@@ -372,7 +331,6 @@ export class IndexedDbFs implements IFileSystem {
372
331
  mode: DEFAULT_DIR_MODE,
373
332
  mtime: new Date(),
374
333
  });
375
- this.dirty = true;
376
334
  }
377
335
 
378
336
  async readdir(path: string): Promise<string[]> {
@@ -461,7 +419,6 @@ export class IndexedDbFs implements IFileSystem {
461
419
  }
462
420
 
463
421
  this.entries.delete(normalizedPath);
464
- this.dirty = true;
465
422
  }
466
423
 
467
424
  async cp(src: string, dest: string, options?: CpOptions): Promise<void> {
@@ -494,8 +451,6 @@ export class IndexedDbFs implements IFileSystem {
494
451
  this.ensureParentDirs(destPath);
495
452
  this.entries.set(destPath, this.cloneEntry(entry));
496
453
  }
497
-
498
- this.dirty = true;
499
454
  }
500
455
 
501
456
  async mv(src: string, dest: string): Promise<void> {
@@ -529,8 +484,6 @@ export class IndexedDbFs implements IFileSystem {
529
484
  this.entries.delete(srcPath);
530
485
  this.entries.set(destPath, entry);
531
486
  }
532
-
533
- this.dirty = true;
534
487
  }
535
488
 
536
489
  resolvePath(base: string, path: string): string {
@@ -568,7 +521,6 @@ export class IndexedDbFs implements IFileSystem {
568
521
 
569
522
  entry.mode = mode;
570
523
  entry.mtime = new Date();
571
- this.dirty = true;
572
524
  }
573
525
 
574
526
  async symlink(target: string, linkPath: string): Promise<void> {
@@ -586,7 +538,6 @@ export class IndexedDbFs implements IFileSystem {
586
538
  mode: DEFAULT_SYMLINK_MODE,
587
539
  mtime: new Date(),
588
540
  });
589
- this.dirty = true;
590
541
  }
591
542
 
592
543
  async link(existingPath: string, newPath: string): Promise<void> {
@@ -613,7 +564,6 @@ export class IndexedDbFs implements IFileSystem {
613
564
  mode: entry.mode,
614
565
  mtime: new Date(),
615
566
  });
616
- this.dirty = true;
617
567
  }
618
568
 
619
569
  async readlink(path: string): Promise<string> {
@@ -677,13 +627,12 @@ export class IndexedDbFs implements IFileSystem {
677
627
 
678
628
  // Update mtime (atime is ignored as per interface docs, kept for API compatibility)
679
629
  entry.mtime = mtime;
680
- this.dirty = true;
681
630
  }
682
631
 
683
632
  // ============ Private Helpers ============
684
633
 
685
634
  private normalizePath(path: string): string {
686
- return IndexedDbFs.normalizePath(path);
635
+ return Filesystem.normalizePath(path);
687
636
  }
688
637
 
689
638
  private static normalizePath(path: string): string {
@@ -715,8 +664,7 @@ export class IndexedDbFs implements IFileSystem {
715
664
  }
716
665
 
717
666
  private ensureParentDirs(path: string): void {
718
- IndexedDbFs.ensureParentDirs(this.entries, path);
719
- this.dirty = true;
667
+ Filesystem.ensureParentDirs(this.entries, path);
720
668
  }
721
669
 
722
670
  private static ensureParentDirs(entries: Map<string, FsEntry>, path: string): void {
@@ -815,11 +763,7 @@ export class IndexedDbFs implements IFileSystem {
815
763
  return new TextDecoder("utf-8").decode(buffer);
816
764
  }
817
765
  if (encoding === "base64") {
818
- let binary = "";
819
- for (let i = 0; i < buffer.byteLength; i++) {
820
- binary += String.fromCharCode(buffer[i]!);
821
- }
822
- return btoa(binary);
766
+ return this.encodeBase64(buffer);
823
767
  }
824
768
  if (encoding === "hex") {
825
769
  return Array.from(buffer)
@@ -830,6 +774,14 @@ export class IndexedDbFs implements IFileSystem {
830
774
  return new TextDecoder("utf-8").decode(buffer);
831
775
  }
832
776
 
777
+ private encodeBase64(buffer: Uint8Array): string {
778
+ let binary = "";
779
+ for (let i = 0; i < buffer.byteLength; i++) {
780
+ binary += String.fromCharCode(buffer[i]!);
781
+ }
782
+ return btoa(binary);
783
+ }
784
+
833
785
  private concatBuffers(a: Uint8Array, b: Uint8Array): Uint8Array {
834
786
  const result = new Uint8Array(a.byteLength + b.byteLength);
835
787
  result.set(a, 0);
@@ -843,93 +795,15 @@ export class IndexedDbFs implements IFileSystem {
843
795
  }
844
796
  return value;
845
797
  }
846
-
847
- // ============ IndexedDB Helpers ============
848
-
849
- private static openDatabase(dbName: string): Promise<IDBDatabase> {
850
- return new Promise((resolve, reject) => {
851
- const request = indexedDB.open(dbName, 1);
852
-
853
- request.onerror = () => reject(request.error);
854
- request.onsuccess = () => resolve(request.result);
855
-
856
- request.onupgradeneeded = (event) => {
857
- const db = (event.target as IDBOpenDBRequest).result;
858
- if (!db.objectStoreNames.contains("entries")) {
859
- db.createObjectStore("entries", { keyPath: "path" });
860
- }
861
- };
862
- });
863
- }
864
-
865
- private static async loadEntries(db: IDBDatabase): Promise<Map<string, FsEntry>> {
866
- const tx = db.transaction("entries", "readonly");
867
- const store = tx.objectStore("entries");
868
-
869
- return new Promise((resolve, reject) => {
870
- const request = store.getAll();
871
- request.onerror = () => reject(request.error);
872
- request.onsuccess = () => {
873
- const entries = new Map<string, FsEntry>();
874
- for (const record of request.result) {
875
- entries.set(record.path, IndexedDbFs.deserializeEntry(record.entry));
876
- }
877
- resolve(entries);
878
- };
879
- });
880
- }
881
-
882
- private serializeEntry(entry: FsEntry): object {
883
- if (entry.type === "file" && entry.content instanceof Uint8Array) {
884
- return {
885
- ...entry,
886
- content: Array.from(entry.content),
887
- contentType: "uint8array",
888
- mtime: entry.mtime.toISOString(),
889
- };
890
- }
891
- return {
892
- ...entry,
893
- mtime: entry.mtime.toISOString(),
894
- };
895
- }
896
-
897
- private static deserializeEntry(data: any): FsEntry {
898
- const mtime = new Date(data.mtime);
899
-
900
- if (data.type === "file") {
901
- let content = data.content;
902
- if (data.contentType === "uint8array" && Array.isArray(content)) {
903
- content = new Uint8Array(content);
904
- }
905
- return { type: "file", content, mode: data.mode, mtime };
906
- }
907
- if (data.type === "symlink") {
908
- return { type: "symlink", target: data.target, mode: data.mode, mtime };
909
- }
910
- return { type: "directory", mode: data.mode, mtime };
911
- }
912
-
913
- private promisifyRequest<T>(request: IDBRequest<T>): Promise<T> {
914
- return new Promise((resolve, reject) => {
915
- request.onerror = () => reject(request.error);
916
- request.onsuccess = () => resolve(request.result);
917
- });
918
- }
919
-
920
- private promisifyTransaction(tx: IDBTransaction): Promise<void> {
921
- return new Promise((resolve, reject) => {
922
- tx.onerror = () => reject(tx.error);
923
- tx.oncomplete = () => resolve();
924
- });
925
- }
926
798
  }
927
799
 
928
800
  /**
929
- * Factory function matching the FileSystemFactory type
801
+ * Create an in-memory filesystem.
802
+ *
803
+ * @param initialFiles - Optional initial files to populate the filesystem
804
+ * @returns A new Filesystem instance
930
805
  */
931
- export function createIndexedDbFs(initialFiles?: InitialFiles): IFileSystem {
932
- // For sync factory usage, return in-memory version
933
- // Use IndexedDbFs.create() for async with persistence
934
- return IndexedDbFs.createInMemory({ initialFiles });
806
+ export function createFilesystem(options?: FilesystemOptions): Filesystem {
807
+ return Filesystem.create(options);
935
808
  }
809
+
package/src/index.ts CHANGED
@@ -1,3 +1,20 @@
1
+ // =============================================================================
2
+ // Browser polyfills - inject before anything else loads
3
+ // =============================================================================
4
+
5
+ // Some dependencies (like just-bash) reference Node.js globals.
6
+ // Provide shims so they work in the browser without user configuration.
7
+ if (typeof window !== "undefined" && typeof globalThis.process === "undefined") {
8
+ (globalThis as Record<string, unknown>).process = {
9
+ env: {},
10
+ platform: "browser",
11
+ version: "v20.0.0",
12
+ browser: true,
13
+ cwd: () => "/",
14
+ nextTick: (fn: () => void) => setTimeout(fn, 0),
15
+ };
16
+ }
17
+
1
18
  // =============================================================================
2
19
  // CORE API - Most users only need these
3
20
  // =============================================================================
@@ -8,19 +25,23 @@
8
25
 
9
26
  export {
10
27
  createSandbox,
11
- createInMemorySandbox,
12
28
  type Sandbox,
13
29
  type SandboxOptions,
30
+ type SandboxState,
31
+ type SandboxBashOptions,
14
32
  } from "./sandbox";
15
33
 
34
+ // -----------------------------------------------------------------------------
35
+ // Builder (recommended for agent workflows)
36
+ // -----------------------------------------------------------------------------
37
+
16
38
  export {
17
- SandboxManager,
18
- createSandboxManager,
19
- type ManagedSandbox,
20
- type ManagedSandboxOptions,
21
- type SandboxManagerStats,
22
- type SandboxManagerOptions,
23
- } from "./sandbox-manager";
39
+ createBuilder,
40
+ type BuildResult,
41
+ type CreateBuilderOptions,
42
+ type BuildCallOptions,
43
+ type BuilderFn,
44
+ } from "./builder";
24
45
 
25
46
  // -----------------------------------------------------------------------------
26
47
  // Module Loading (use after build to get exports)
@@ -51,6 +72,7 @@ export {
51
72
  // -----------------------------------------------------------------------------
52
73
 
53
74
  export type { BundleResult } from "./bundler";
75
+ export type { BuildOutput, ValidateFn } from "./commands/types";
54
76
  export type { TypecheckResult, Diagnostic } from "./typechecker";
55
77
  export type { PackageManifest, InstallResult } from "./packages";
56
78
 
@@ -114,9 +136,9 @@ export {
114
136
  // -----------------------------------------------------------------------------
115
137
 
116
138
  export {
117
- IndexedDbFs,
118
- createIndexedDbFs,
119
- type IndexedDbFsOptions,
139
+ Filesystem,
140
+ createFilesystem,
141
+ type FilesystemOptions,
120
142
  } from "./fs";
121
143
 
122
144
  export type { IFileSystem, FsEntry } from "just-bash/browser";
@@ -146,4 +168,4 @@ export {
146
168
  type RunContext,
147
169
  type RunOptions,
148
170
  type RunResult,
149
- } from "./commands";
171
+ } from "./commands/index";
package/src/internal.ts CHANGED
@@ -112,5 +112,8 @@ export { formatEsbuildMessages } from "./commands";
112
112
  // BuildEmitter (for custom build event handling)
113
113
  // =============================================================================
114
114
 
115
- // Note: BuildEmitter is currently defined inline in sandbox.ts and sandbox-manager.ts
116
- // If needed externally, it should be extracted to a separate module.
115
+ /**
116
+ * Build event emitter for sandbox environments.
117
+ * Use this for custom build event handling in advanced use cases.
118
+ */
119
+ export { BuildEmitter } from "./build-emitter";