seabox 0.1.0-beta.3 → 0.1.0-beta.4

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/lib/bootstrap.js DELETED
@@ -1,697 +0,0 @@
1
- /**
2
- * bootstrap.js
3
- * Runtime template for SEA applications.
4
- * Handles binary extraction and module resolution override.
5
- *
6
- * This file is prepended to the application entry bundle.
7
- */
8
-
9
- (function() {
10
- 'use strict';
11
-
12
- const fs = require('fs');
13
- const path = require('path');
14
- const crypto = require('crypto');
15
- const Module = require('module');
16
-
17
- // PHASE 1: Override fs methods IMMEDIATELY (before app code captures them)
18
- // We'll populate the redirect maps in Phase 2 after extraction
19
- const assetPathMapGlobal = {};
20
- let cacheDirGlobal = '';
21
-
22
- const originalExistsSync = fs.existsSync;
23
- const originalReadFileSync = fs.readFileSync;
24
- const originalStatSync = fs.statSync;
25
- const originalLstatSync = fs.lstatSync;
26
- const originalRealpathSync = fs.realpathSync;
27
-
28
- /**
29
- * Check if a requested path should be redirected to cache.
30
- * @param {string} requestedPath
31
- * @returns {string|null} - Redirected path or null
32
- */
33
- function getRedirectedPath(requestedPath) {
34
- if (!requestedPath) return null;
35
-
36
- // Normalize the requested path
37
- const normalized = path.normalize(requestedPath);
38
-
39
- // Check direct match against asset keys
40
- for (const [assetKey, extractedPath] of Object.entries(assetPathMapGlobal)) {
41
- // Try exact match
42
- if (normalized.endsWith(assetKey)) {
43
- return extractedPath;
44
- }
45
-
46
- // Try basename match
47
- const assetBasename = path.basename(assetKey);
48
- if (path.basename(normalized) === assetBasename) {
49
- return extractedPath;
50
- }
51
-
52
- // Try matching the path components
53
- if (normalized.includes(assetKey.replace(/\//g, path.sep))) {
54
- return extractedPath;
55
- }
56
- }
57
-
58
- return null;
59
- }
60
-
61
- // Override existsSync immediately
62
- fs.existsSync = function(filePath) {
63
- if (!filePath) return originalExistsSync(filePath);
64
-
65
- // If this path should be redirected, check if the REDIRECTED path exists
66
- const redirected = getRedirectedPath(filePath);
67
- if (redirected) {
68
- return originalExistsSync(redirected);
69
- }
70
-
71
- // For .node files, ONLY allow cache paths for our managed binaries
72
- if (filePath.endsWith('.node') && cacheDirGlobal) {
73
- const basename = path.basename(filePath);
74
-
75
- // Check if this is one of our managed binaries
76
- for (const assetKey of Object.keys(assetPathMapGlobal)) {
77
- if (path.basename(assetKey) === basename) {
78
- // This is one of our binaries
79
- // Only return true if the path is in the cache directory
80
- if (filePath.startsWith(cacheDirGlobal)) {
81
- return originalExistsSync(filePath);
82
- } else {
83
- // Block all non-cache paths for our binaries
84
- return false;
85
- }
86
- }
87
- }
88
- }
89
-
90
- return originalExistsSync(filePath);
91
- };
92
-
93
- // Override readFileSync immediately
94
- fs.readFileSync = function(filePath, options) {
95
- const redirected = getRedirectedPath(filePath);
96
- if (redirected) {
97
- return originalReadFileSync(redirected, options);
98
- }
99
- return originalReadFileSync(filePath, options);
100
- };
101
-
102
- // Override statSync immediately
103
- fs.statSync = function(filePath, options) {
104
- const redirected = getRedirectedPath(filePath);
105
- if (redirected) {
106
- return originalStatSync(redirected, options);
107
- }
108
- return originalStatSync(filePath, options);
109
- };
110
-
111
- // Override lstatSync immediately
112
- fs.lstatSync = function(filePath, options) {
113
- const redirected = getRedirectedPath(filePath);
114
- if (redirected) {
115
- return originalLstatSync(redirected, options);
116
- }
117
- return originalLstatSync(filePath, options);
118
- };
119
-
120
- // Override realpathSync immediately
121
- fs.realpathSync = function(filePath, options) {
122
- const redirected = getRedirectedPath(filePath);
123
- if (redirected) {
124
- return redirected; // Return the redirected path as the "real" path
125
- }
126
- return originalRealpathSync(filePath, options);
127
- };
128
-
129
- // SEA API - check if we're in an SEA by trying to load the sea module
130
- let sea = null;
131
- let isSEA = false;
132
- try {
133
- sea = require('node:sea');
134
- isSEA = true;
135
- console.log('[SEA Bootstrap] Running in SEA context');
136
- } catch (err) {
137
- // Not in SEA context - this is fine for development
138
- console.log('[SEA Bootstrap] Not in SEA context (normal mode)');
139
- }
140
-
141
- /**
142
- * Decrypt an encrypted asset.
143
- * @param {Buffer} encryptedData - Encrypted data with IV and auth tag prepended
144
- * @param {Buffer} key - 32-byte encryption key
145
- * @returns {Buffer} - Decrypted data
146
- */
147
- function decryptAsset(encryptedData, key) {
148
- // Extract IV, auth tag, and encrypted content
149
- const iv = encryptedData.slice(0, 16);
150
- const authTag = encryptedData.slice(16, 32);
151
- const encrypted = encryptedData.slice(32);
152
-
153
- // Create decipher
154
- const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
155
- decipher.setAuthTag(authTag);
156
-
157
- // Decrypt the data
158
- const decrypted = Buffer.concat([
159
- decipher.update(encrypted),
160
- decipher.final()
161
- ]);
162
-
163
- return decrypted;
164
- }
165
-
166
- /**
167
- * Get an asset from the SEA, decrypting if necessary.
168
- * @param {string} assetKey - Asset key
169
- * @param {string} [encoding] - Optional encoding (e.g., 'utf8')
170
- * @returns {Buffer|string} - Asset content
171
- */
172
- function getAsset(assetKey, encoding) {
173
- if (!sea) {
174
- throw new Error('Cannot get asset: not in SEA context');
175
- }
176
-
177
- // Check if this asset is encrypted
178
- const isEncrypted = typeof SEA_ENCRYPTED_ASSETS !== 'undefined' && SEA_ENCRYPTED_ASSETS.has(assetKey);
179
-
180
- let content;
181
- if (isEncrypted && typeof SEA_ENCRYPTION_KEY !== 'undefined') {
182
- // Get raw encrypted data
183
- const encryptedData = sea.getRawAsset(assetKey);
184
- const buffer = Buffer.isBuffer(encryptedData) ? encryptedData : Buffer.from(encryptedData);
185
-
186
- // Decrypt it
187
- content = decryptAsset(buffer, SEA_ENCRYPTION_KEY);
188
- } else {
189
- // Get unencrypted asset
190
- content = sea.getAsset(assetKey, encoding);
191
- if (!encoding && !Buffer.isBuffer(content)) {
192
- content = Buffer.from(content);
193
- }
194
- }
195
-
196
- // Apply encoding if requested
197
- if (encoding && Buffer.isBuffer(content)) {
198
- return content.toString(encoding);
199
- }
200
-
201
- return content;
202
- }
203
-
204
-
205
- /**
206
- * Resolve environment variables in a path string.
207
- * Supports %VAR% on Windows and $VAR or ${VAR} on Unix-like systems.
208
- * @param {string} pathStr - Path string that may contain environment variables
209
- * @returns {string} - Path with environment variables expanded
210
- */
211
- function resolveEnvVars(pathStr) {
212
- if (!pathStr) return pathStr;
213
-
214
- // Replace Windows-style %VAR%
215
- let resolved = pathStr.replace(/%([^%]+)%/g, (match, varName) => {
216
- return process.env[varName] || match;
217
- });
218
-
219
- // Replace Unix-style $VAR and ${VAR}
220
- resolved = resolved.replace(/\$\{([^}]+)\}/g, (match, varName) => {
221
- return process.env[varName] || match;
222
- });
223
- resolved = resolved.replace(/\$([A-Za-z_][A-Za-z0-9_]*)/g, (match, varName) => {
224
- return process.env[varName] || match;
225
- });
226
-
227
- return resolved;
228
- }
229
-
230
- /**
231
- * Get the extraction cache directory for this application.
232
- * @param {string} appName
233
- * @param {string} appVersion
234
- * @param {string} platform
235
- * @param {string} arch
236
- * @param {string} [configuredCacheLocation] - Optional configured cache location from manifest
237
- * @returns {string}
238
- */
239
- function getExtractionCacheDir(appName, appVersion, platform, arch, configuredCacheLocation) {
240
- // Priority: SEACACHE env var > configured location > default
241
- if(process.env.SEACACHE) return process.env.SEACACHE;
242
-
243
- if (configuredCacheLocation) {
244
- // Resolve environment variables in the configured path
245
- const resolvedBase = resolveEnvVars(configuredCacheLocation);
246
- // Make relative paths absolute (relative to cwd)
247
- const absoluteBase = path.isAbsolute(resolvedBase) ? resolvedBase : path.resolve(process.cwd(), resolvedBase);
248
- return path.join(absoluteBase, appName, `${appVersion}-${platform}-${arch}`);
249
- }
250
-
251
- // Default behavior
252
- const defaultBase = './.sea-cache';
253
- const absoluteBase = path.resolve(process.cwd(), defaultBase);
254
- return path.join(absoluteBase, appName, `${appVersion}-${platform}-${arch}`);
255
- }
256
-
257
- /**
258
- * Extract a binary asset to the cache directory.
259
- * @param {string} assetKey
260
- * @param {string} targetPath
261
- * @param {string} expectedHash
262
- * @returns {boolean} - True if extracted or already valid
263
- */
264
- function extractBinary(assetKey, targetPath, expectedHash) {
265
- // Check if already extracted and valid
266
- if (fs.existsSync(targetPath)) {
267
- const existingHash = hashFile(targetPath);
268
- if (existingHash === expectedHash) {
269
- return true; // Already valid
270
- }
271
- // Hash mismatch, re-extract
272
- fs.unlinkSync(targetPath);
273
- }
274
-
275
- // Extract from SEA blob (binaries are never encrypted)
276
- const assetBuffer = sea.getRawAsset(assetKey);
277
- if (!assetBuffer) {
278
- throw new Error(`Asset not found in SEA blob: ${assetKey}`);
279
- }
280
-
281
- // Convert ArrayBuffer to Buffer if needed
282
- const buffer = Buffer.isBuffer(assetBuffer)
283
- ? assetBuffer
284
- : Buffer.from(assetBuffer);
285
-
286
- // Verify hash before writing
287
- const actualHash = crypto.createHash('sha256').update(buffer).digest('hex');
288
- if (actualHash !== expectedHash) {
289
- throw new Error(`Integrity check failed for ${assetKey}: expected ${expectedHash}, got ${actualHash}`);
290
- }
291
-
292
- // Write to cache
293
- const dir = path.dirname(targetPath);
294
- if (!fs.existsSync(dir)) {
295
- fs.mkdirSync(dir, { recursive: true });
296
- }
297
-
298
- fs.writeFileSync(targetPath, buffer);
299
-
300
- // Set executable permissions on Unix-like systems
301
- if (process.platform !== 'win32') {
302
- fs.chmodSync(targetPath, 0o755);
303
- }
304
-
305
- return true;
306
- }
307
-
308
- /**
309
- * Compute SHA-256 hash of a file.
310
- * @param {string} filePath
311
- * @returns {string}
312
- */
313
- function hashFile(filePath) {
314
- const content = fs.readFileSync(filePath);
315
- return crypto.createHash('sha256').update(content).digest('hex');
316
- }
317
-
318
- /**
319
- * Override fs module methods to intercept file access for extracted binaries.
320
- * This provides a generic solution that works with any native module loading pattern
321
- * (bindings package, direct require, etc.)
322
- * @param {string} cacheDir - Cache directory where binaries are extracted
323
- * @param {Object.<string, string>} assetPathMap - Map of asset key -> extracted path
324
- */
325
- /**
326
- * Override Module._resolveFilename and Module._load to redirect native module loads to extracted cache.
327
- * @param {Object.<string, string>} nativeModuleMap - Map of module name -> extracted path
328
- */
329
- function overrideModuleResolution(nativeModuleMap) {
330
- const originalResolveFilename = Module._resolveFilename;
331
- const originalLoad = Module._load;
332
-
333
- // Override _resolveFilename for normal resolution
334
- Module._resolveFilename = function (request, parent, isMain, options) {
335
- // Normalize the request path
336
- const normalized = path.normalize(request);
337
-
338
- // Check direct match
339
- if (nativeModuleMap[request]) {
340
- return nativeModuleMap[request];
341
- }
342
-
343
- // Check for basename match
344
- const basename = path.basename(request);
345
- if (nativeModuleMap[basename]) {
346
- return nativeModuleMap[basename];
347
- }
348
-
349
- // Check if the request includes any of our native modules
350
- for (const [moduleName, extractedPath] of Object.entries(nativeModuleMap)) {
351
- if (request.endsWith(moduleName) || request.includes(moduleName) ||
352
- normalized.includes(moduleName.replace(/\//g, path.sep))) {
353
- return extractedPath;
354
- }
355
- }
356
-
357
- // Fall back to original resolution
358
- return originalResolveFilename.call(this, request, parent, isMain, options);
359
- };
360
-
361
- // Override _load to intercept at load time (catches SEA embedderRequire)
362
- Module._load = function (request, parent, isMain) {
363
- // Normalize the request path
364
- const normalized = path.normalize(request);
365
-
366
- // Check direct match
367
- if (nativeModuleMap[request]) {
368
- return originalLoad.call(this, nativeModuleMap[request], parent, isMain);
369
- }
370
-
371
- // Check for basename match
372
- const basename = path.basename(request);
373
- if (nativeModuleMap[basename]) {
374
- return originalLoad.call(this, nativeModuleMap[basename], parent, isMain);
375
- }
376
-
377
- // Check if the request includes any of our native modules
378
- for (const [moduleName, extractedPath] of Object.entries(nativeModuleMap)) {
379
- if (request.endsWith(moduleName) || request.includes(moduleName) ||
380
- normalized.includes(moduleName.replace(/\//g, path.sep))) {
381
- return originalLoad.call(this, extractedPath, parent, isMain);
382
- }
383
- }
384
-
385
- // Fall back to original load
386
- return originalLoad.call(this, request, parent, isMain);
387
- };
388
- }
389
-
390
- /**
391
- * Bootstrap the SEA application.
392
- * Extracts binaries and sets up module resolution.
393
- */
394
- function bootstrap() {
395
- if (!sea) {
396
- throw new Error('This script must run in a SEA (Single Executable Application) context');
397
- }
398
-
399
- // Patch sea.getAsset to handle decryption transparently
400
- if (typeof SEA_ENCRYPTED_ASSETS !== 'undefined' && typeof SEA_ENCRYPTION_KEY !== 'undefined') {
401
- const originalGetAsset = sea.getAsset.bind(sea);
402
- const originalGetRawAsset = sea.getRawAsset.bind(sea);
403
-
404
- sea.getAsset = function(assetKey, encoding) {
405
- const isEncrypted = SEA_ENCRYPTED_ASSETS.has(assetKey);
406
-
407
- if (isEncrypted) {
408
- // Get raw encrypted data
409
- const encryptedData = originalGetRawAsset(assetKey);
410
- const buffer = Buffer.isBuffer(encryptedData) ? encryptedData : Buffer.from(encryptedData);
411
-
412
- // Decrypt it
413
- const decrypted = decryptAsset(buffer, SEA_ENCRYPTION_KEY);
414
-
415
- // Apply encoding if requested
416
- if (encoding) {
417
- return decrypted.toString(encoding);
418
- }
419
- return decrypted;
420
- } else {
421
- // Not encrypted, use original method
422
- return originalGetAsset(assetKey, encoding);
423
- }
424
- };
425
- }
426
-
427
- // Load the manifest from SEA assets (manifest is never encrypted)
428
- const manifestJson = sea.getAsset('sea-manifest.json', 'utf8');
429
- if (!manifestJson) {
430
- throw new Error('SEA manifest not found in blob');
431
- }
432
-
433
- const manifest = JSON.parse(manifestJson);
434
- const verbose = process.env.SEA_VERBOSE === 'true';
435
-
436
- if (verbose) {
437
- console.log('SEA Bootstrap:');
438
- console.log(` App: ${manifest.appName} v${manifest.appVersion}`);
439
- console.log(` Platform: ${manifest.platform}-${manifest.arch}`);
440
- console.log(` Binaries to extract: ${manifest.binaries.length}`);
441
- if (manifest.cacheLocation) {
442
- console.log(` Configured cache location: ${manifest.cacheLocation}`);
443
- }
444
- }
445
-
446
- // Filter binaries for current platform
447
- const platformBinaries = manifest.binaries.filter(b =>
448
- (b.platform === '*' || b.platform === process.platform) &&
449
- (b.arch === '*' || b.arch === process.arch)
450
- );
451
-
452
- // Sort by extraction order
453
- platformBinaries.sort((a, b) => a.order - b.order);
454
-
455
- const cacheDir = getExtractionCacheDir(
456
- manifest.appName,
457
- manifest.appVersion,
458
- manifest.platform,
459
- manifest.arch,
460
- manifest.cacheLocation
461
- );
462
-
463
- const nativeModuleMap = {};
464
-
465
- // Extract binaries
466
- for (const binary of platformBinaries) {
467
- const targetPath = path.join(cacheDir, binary.fileName);
468
-
469
- if (verbose) {
470
- console.log(` Extracting: ${binary.assetKey} -> ${targetPath}`);
471
- }
472
-
473
- extractBinary(binary.assetKey, targetPath, binary.hash);
474
-
475
- // Map the module name to extracted path
476
- nativeModuleMap[binary.fileName] = targetPath;
477
-
478
- // Also map without extension for easier resolution
479
- const nameWithoutExt = path.basename(binary.fileName, path.extname(binary.fileName));
480
- nativeModuleMap[nameWithoutExt] = targetPath;
481
-
482
- // PHASE 2: Populate global asset map for fs overrides
483
- assetPathMapGlobal[binary.assetKey] = targetPath;
484
- }
485
-
486
- // PHASE 2: Set global cache directory
487
- cacheDirGlobal = cacheDir;
488
-
489
- if (verbose) {
490
- console.log('✓ Binary extraction complete');
491
- console.log(` Cache directory: ${cacheDir}`);
492
- console.log(` Asset mappings: ${Object.keys(assetPathMapGlobal).length}`);
493
- }
494
-
495
- // On Windows, add the cache directory to DLL search path
496
- if (process.platform === 'win32') {
497
- // Add to PATH so that native addons can find dependent DLLs
498
- process.env.PATH = `${cacheDir};${process.env.PATH}`;
499
-
500
- if (verbose) {
501
- console.log(` Added to PATH: ${cacheDir}`);
502
- }
503
-
504
- // Also try to add DLL directory using SetDllDirectory (if available)
505
- try {
506
- // Windows-specific: preload all DLLs to ensure they're in the process
507
- platformBinaries
508
- .filter(b => b.fileName.endsWith('.dll'))
509
- .forEach(b => {
510
- const dllPath = path.join(cacheDir, b.fileName);
511
- if (verbose) {
512
- console.log(` Preloading DLL: ${dllPath}`);
513
- }
514
- // Use process.dlopen to preload the DLL
515
- try {
516
- process.dlopen({ exports: {} }, dllPath);
517
- } catch (err) {
518
- // DLL might not be a valid node addon, which is fine
519
- if (verbose) {
520
- console.log(` (Non-addon DLL, will be loaded by OS loader)`);
521
- }
522
- }
523
- });
524
- } catch (err) {
525
- console.warn('Warning: Could not preload DLLs:', err.message);
526
- }
527
- }
528
-
529
- // Set environment variable for cache directory (used by applications)
530
- process.env.SEA_CACHE_DIR = cacheDir;
531
-
532
- // Export native module map and cache dir to global for require override in app code
533
- global.__seaNativeModuleMap = nativeModuleMap;
534
- global.__seaCacheDir = cacheDir;
535
-
536
- // Add cache directory to module paths so bindings can find extracted .node files
537
- if (!Module.globalPaths.includes(cacheDir)) {
538
- Module.globalPaths.unshift(cacheDir);
539
- }
540
-
541
- // Override module resolution
542
- overrideModuleResolution(nativeModuleMap);
543
-
544
- // Provide a 'bindings' module shim for SEA compatibility
545
- // This gets injected into the module cache so require('bindings') works
546
- const bindingsShim = function(name) {
547
- // Ensure .node extension
548
- if (!name.endsWith('.node')) {
549
- name += '.node';
550
- }
551
-
552
- if (verbose) {
553
- console.log(`[bindings shim] Loading native module: ${name}`);
554
- }
555
-
556
- // Try to load from native module map
557
- if (nativeModuleMap[name]) {
558
- const exports = {};
559
- process.dlopen({ exports }, nativeModuleMap[name]);
560
- return exports;
561
- }
562
-
563
- // Try basename without .node
564
- const baseName = path.basename(name, '.node');
565
- if (nativeModuleMap[baseName]) {
566
- const exports = {};
567
- process.dlopen({ exports }, nativeModuleMap[baseName]);
568
- return exports;
569
- }
570
-
571
- throw new Error(`Could not load native module "${name}" - not found in SEA cache`);
572
- };
573
-
574
- // Inject bindings into module cache
575
- Module._cache['bindings'] = {
576
- id: 'bindings',
577
- exports: bindingsShim,
578
- loaded: true
579
- };
580
-
581
- if (verbose) {
582
- console.log('[SEA] Injected bindings shim into module cache');
583
- }
584
-
585
- // CRITICAL: Override the GLOBAL require function (not just Module.prototype.require)
586
- // In SEA context, the bundled app code uses the global require which we CAN override
587
- const originalGlobalRequire = global.require;
588
-
589
- if (verbose) {
590
- console.log('[SEA] About to override global.require');
591
- console.log('[SEA] Original global.require type:', typeof originalGlobalRequire);
592
- console.log('[SEA] Original global.require name:', originalGlobalRequire?.name);
593
- }
594
-
595
- global.require = function seaRequire(id) {
596
- // Check if this is a .node file
597
- if (typeof id === 'string' && id.endsWith('.node')) {
598
- if (verbose) {
599
- console.log(`[SEA require] Intercepted .node require: ${id}`);
600
- }
601
-
602
- // Get the basename
603
- const basename = path.basename(id);
604
-
605
- // Check if we have this in our native module map
606
- if (nativeModuleMap[basename]) {
607
- if (verbose) {
608
- console.log(`[SEA require] Loading from cache via dlopen: ${nativeModuleMap[basename]}`);
609
- }
610
- const exports = {};
611
- process.dlopen({ exports }, nativeModuleMap[basename]);
612
- return exports;
613
- }
614
-
615
- // Check if absolute path is in cache
616
- if (path.isAbsolute(id) && id.startsWith(cacheDir)) {
617
- if (verbose) {
618
- console.log(`[SEA require] Loading cache path via dlopen: ${id}`);
619
- }
620
- const exports = {};
621
- process.dlopen({ exports }, id);
622
- return exports;
623
- }
624
-
625
- // Check asset map for path matching
626
- for (const [assetKey, extractedPath] of Object.entries(assetPathMapGlobal)) {
627
- if (id.includes(assetKey) || basename === path.basename(assetKey)) {
628
- if (verbose) {
629
- console.log(`[SEA require] Loading from asset map via dlopen: ${extractedPath}`);
630
- }
631
- const exports = {};
632
- process.dlopen({ exports }, extractedPath);
633
- return exports;
634
- }
635
- }
636
- }
637
-
638
- // For all other requires, use the original
639
- return originalGlobalRequire.call(this, id);
640
- };
641
-
642
- if (verbose) {
643
- console.log('[SEA] global.require has been overridden');
644
- console.log('[SEA] New global.require type:', typeof global.require);
645
- console.log('[SEA] New global.require name:', global.require.name);
646
- }
647
-
648
- // Also override Module.prototype.require for completeness
649
- const originalModuleRequire = Module.prototype.require;
650
- Module.prototype.require = function(id) {
651
- // Delegate to our global require override
652
- return global.require(id);
653
- };
654
- }
655
-
656
- // Run bootstrap if in SEA context
657
- if (isSEA && sea) {
658
- // Check if we're building a snapshot
659
- let isBuildingSnapshot = false;
660
- try {
661
- const v8 = require('v8');
662
- isBuildingSnapshot = v8.startupSnapshot && v8.startupSnapshot.isBuildingSnapshot && v8.startupSnapshot.isBuildingSnapshot();
663
- } catch (err) {
664
- // v8.startupSnapshot not available
665
- }
666
-
667
- if (isBuildingSnapshot) {
668
- console.log('[SEA Bootstrap] Snapshot build mode - setting up deserialization callback');
669
- // During snapshot build: set up callback to run bootstrap at runtime
670
- const v8 = require('v8');
671
- const originalCallback = v8.startupSnapshot.setDeserializeMainFunction;
672
-
673
- // Intercept setDeserializeMainFunction to add bootstrap before app code
674
- v8.startupSnapshot.setDeserializeMainFunction = function(callback) {
675
- originalCallback.call(this, () => {
676
- console.log('[SEA Bootstrap] Running at deserialization');
677
- bootstrap();
678
- // Then run the application callback
679
- callback();
680
- });
681
- };
682
- } else {
683
- // Normal runtime: run bootstrap immediately
684
- bootstrap();
685
- }
686
- } else {
687
- console.log('[SEA Bootstrap] Skipping extraction (not in SEA)');
688
- }
689
-
690
- })(); // End bootstrap IIFE
691
-
692
- // Export for testing (only accessible if loaded as module)
693
- if (typeof module !== 'undefined' && module.exports) {
694
- module.exports = {
695
- // Empty exports - bootstrap runs automatically
696
- };
697
- }