seabox 0.2.0 → 0.4.0
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 +8 -6
- package/bin/seabox-rebuild.mjs +34 -15
- package/lib/build-cache.mjs +11 -4
- package/lib/fetch-node.mjs +1 -1
- package/lib/multi-target-builder.mjs +33 -24
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -148,17 +148,19 @@ seabox automates the entire SEA build process:
|
|
|
148
148
|
- **Config globs**: Patterns specified in `assets` array
|
|
149
149
|
- **Libraries**: Platform-specific shared libraries (DLLs/SOs)
|
|
150
150
|
|
|
151
|
-
3. **
|
|
151
|
+
3. **Binary Preparation** - Downloads target Node.js binary
|
|
152
152
|
|
|
153
|
-
4. **
|
|
153
|
+
4. **Native Module Rebuilding** - Rebuilds native modules using the downloaded Node.js binary, ensuring ABI compatibility
|
|
154
154
|
|
|
155
|
-
5. **
|
|
155
|
+
5. **Bootstrap Injection** - Adds runtime code for asset loading and native module extraction
|
|
156
156
|
|
|
157
|
-
6. **
|
|
157
|
+
6. **SEA Blob Creation** - Packages everything using the target Node.js binary
|
|
158
158
|
|
|
159
|
-
7. **
|
|
159
|
+
7. **Signature Removal** - Removes code signature from the binary before injection
|
|
160
160
|
|
|
161
|
-
8. **
|
|
161
|
+
8. **Injection** - Uses `postject` to inject the blob into the Node.js binary
|
|
162
|
+
|
|
163
|
+
9. **Output** - Produces standalone executable(s) ready for distribution
|
|
162
164
|
|
|
163
165
|
### Automatic Asset Detection
|
|
164
166
|
|
package/bin/seabox-rebuild.mjs
CHANGED
|
@@ -16,15 +16,16 @@ const __dirname = path.dirname(__filename);
|
|
|
16
16
|
/**
|
|
17
17
|
* Rebuild a native module for a specific target
|
|
18
18
|
* @param {string} modulePath - Path to the native module
|
|
19
|
+
* @param {string} nodeVersion - Target Node.js version (e.g., "24.13.0")
|
|
19
20
|
* @param {string} platform - Target platform (win32, linux, darwin)
|
|
20
21
|
* @param {string} arch - Target architecture (x64, arm64)
|
|
21
22
|
* @param {boolean} verbose - Enable verbose logging
|
|
22
23
|
*/
|
|
23
|
-
function rebuildNativeModule(modulePath, platform, arch, verbose = false) {
|
|
24
|
+
function rebuildNativeModule(modulePath, nodeVersion, platform, arch, verbose = false) {
|
|
24
25
|
diag.setVerbose(verbose);
|
|
25
26
|
|
|
26
27
|
diag.verbose(`Rebuilding native module: ${modulePath}`);
|
|
27
|
-
diag.verbose(`Target: ${platform}-${arch}`);
|
|
28
|
+
diag.verbose(`Target: Node.js ${nodeVersion} ${platform}-${arch}`);
|
|
28
29
|
|
|
29
30
|
const packageJsonPath = path.join(modulePath, 'package.json');
|
|
30
31
|
if (!fs.existsSync(packageJsonPath)) {
|
|
@@ -42,42 +43,60 @@ function rebuildNativeModule(modulePath, platform, arch, verbose = false) {
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
try {
|
|
45
|
-
// Use node-gyp to rebuild for the target platform
|
|
46
|
-
|
|
46
|
+
// Use node-gyp to rebuild for the target Node.js version, platform, and architecture
|
|
47
|
+
// --target specifies the Node.js version (critical for NODE_MODULE_VERSION)
|
|
48
|
+
// --dist-url ensures node-gyp downloads headers from the correct location
|
|
49
|
+
const cmd = `npx node-gyp rebuild --target=${nodeVersion} --arch=${arch} --dist-url=https://nodejs.org/dist`;
|
|
47
50
|
|
|
48
|
-
diag.verbose(`
|
|
51
|
+
diag.verbose(`Building for Node.js ${nodeVersion} (${platform}-${arch})`, 2);
|
|
52
|
+
diag.verbose(`Command: ${cmd}`, 2);
|
|
49
53
|
|
|
50
54
|
execSync(cmd, {
|
|
51
55
|
cwd: modulePath,
|
|
52
|
-
stdio:
|
|
56
|
+
stdio: 'pipe', // Hide output unless error occurs
|
|
53
57
|
env: {
|
|
54
58
|
...process.env,
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
npm_config_target: nodeVersion,
|
|
60
|
+
npm_config_arch: arch,
|
|
61
|
+
npm_config_target_arch: arch,
|
|
62
|
+
npm_config_disturl: 'https://nodejs.org/dist'
|
|
57
63
|
}
|
|
58
64
|
});
|
|
59
65
|
|
|
60
|
-
diag.verbose(`Successfully
|
|
66
|
+
diag.verbose(`Successfully built for Node.js ${nodeVersion}`, 2);
|
|
61
67
|
} catch (error) {
|
|
68
|
+
// Show node-gyp output on error
|
|
69
|
+
if (error.stdout) console.error(error.stdout.toString());
|
|
70
|
+
if (error.stderr) console.error(error.stderr.toString());
|
|
62
71
|
diag.verbose(`Failed to rebuild ${moduleName}: ${error.message}`);
|
|
63
72
|
throw error;
|
|
64
73
|
}
|
|
65
74
|
}
|
|
66
75
|
|
|
67
|
-
// CLI entry point
|
|
68
|
-
|
|
76
|
+
// CLI entry point - compare normalized paths for cross-platform compatibility
|
|
77
|
+
const isMainModule = (() => {
|
|
78
|
+
try {
|
|
79
|
+
const scriptPath = fileURLToPath(import.meta.url);
|
|
80
|
+
const argPath = path.resolve(process.argv[1]);
|
|
81
|
+
return scriptPath === argPath;
|
|
82
|
+
} catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
})();
|
|
86
|
+
|
|
87
|
+
if (isMainModule) {
|
|
69
88
|
const args = process.argv.slice(2);
|
|
70
89
|
|
|
71
|
-
if (args.length <
|
|
72
|
-
diag.error('Usage: seabox-rebuild <module-path> <platform> <arch> [--verbose]');
|
|
90
|
+
if (args.length < 4) {
|
|
91
|
+
diag.error('Usage: seabox-rebuild <module-path> <node-version> <platform> <arch> [--verbose]');
|
|
73
92
|
process.exit(1);
|
|
74
93
|
}
|
|
75
94
|
|
|
76
|
-
const [modulePath, platform, arch] = args;
|
|
95
|
+
const [modulePath, nodeVersion, platform, arch] = args;
|
|
77
96
|
const verbose = args.includes('--verbose') || args.includes('-v');
|
|
78
97
|
|
|
79
98
|
try {
|
|
80
|
-
rebuildNativeModule(modulePath, platform, arch, verbose);
|
|
99
|
+
rebuildNativeModule(modulePath, nodeVersion, platform, arch, verbose);
|
|
81
100
|
process.exit(0);
|
|
82
101
|
} catch (error) {
|
|
83
102
|
diag.error(`Rebuild failed: ${error.message}`);
|
package/lib/build-cache.mjs
CHANGED
|
@@ -110,13 +110,15 @@ export class BuildCache {
|
|
|
110
110
|
/**
|
|
111
111
|
* Get cached native module build
|
|
112
112
|
* @param {string} moduleRoot - Module root path
|
|
113
|
-
* @param {string} target - Build target
|
|
113
|
+
* @param {string} target - Build target (e.g., node24.11.0-win32-x64)
|
|
114
114
|
* @returns {string|null} - Path to cached .node file
|
|
115
115
|
*/
|
|
116
116
|
getCachedNativeBuild(moduleRoot, target) {
|
|
117
|
+
// Include the FULL target string (with Node.js version) in cache key
|
|
118
|
+
// This ensures different Node.js versions have separate cache entries
|
|
117
119
|
const cacheKey = crypto
|
|
118
120
|
.createHash('sha256')
|
|
119
|
-
.update(moduleRoot
|
|
121
|
+
.update(`${moduleRoot}||${target}`)
|
|
120
122
|
.digest('hex')
|
|
121
123
|
.substring(0, 16);
|
|
122
124
|
|
|
@@ -132,6 +134,9 @@ export class BuildCache {
|
|
|
132
134
|
if (cacheStats.mtime > sourceStats.mtime) {
|
|
133
135
|
return cachePath;
|
|
134
136
|
}
|
|
137
|
+
} else {
|
|
138
|
+
// If no binding.gyp, still return cached binary (e.g., prebuild scenario)
|
|
139
|
+
return cachePath;
|
|
135
140
|
}
|
|
136
141
|
}
|
|
137
142
|
|
|
@@ -141,13 +146,15 @@ export class BuildCache {
|
|
|
141
146
|
/**
|
|
142
147
|
* Cache a native module build
|
|
143
148
|
* @param {string} moduleRoot - Module root path
|
|
144
|
-
* @param {string} target - Build target
|
|
149
|
+
* @param {string} target - Build target (e.g., node24.11.0-win32-x64)
|
|
145
150
|
* @param {string} builtBinaryPath - Path to built .node file
|
|
146
151
|
*/
|
|
147
152
|
cacheNativeBuild(moduleRoot, target, builtBinaryPath) {
|
|
153
|
+
// Use the FULL target string (with Node.js version) in cache key
|
|
154
|
+
// This ensures different Node.js versions have separate cache entries
|
|
148
155
|
const cacheKey = crypto
|
|
149
156
|
.createHash('sha256')
|
|
150
|
-
.update(moduleRoot
|
|
157
|
+
.update(`${moduleRoot}||${target}`)
|
|
151
158
|
.digest('hex')
|
|
152
159
|
.substring(0, 16);
|
|
153
160
|
|
package/lib/fetch-node.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import https from 'https';
|
|
|
9
9
|
import { pipeline } from 'stream';
|
|
10
10
|
import { promisify } from 'util';
|
|
11
11
|
import AdmZip from 'adm-zip';
|
|
12
|
-
import tar from 'tar';
|
|
12
|
+
import * as tar from 'tar';
|
|
13
13
|
import * as diag from './diagnostics.mjs';
|
|
14
14
|
|
|
15
15
|
const pipelineAsync = promisify(pipeline);
|
|
@@ -135,26 +135,33 @@ export class MultiTargetBuilder {
|
|
|
135
135
|
|
|
136
136
|
diag.subheader(`[Build ${buildNumber}] Target: ${target}`);
|
|
137
137
|
|
|
138
|
-
// Step 1:
|
|
138
|
+
// Step 1: Fetch Node binary FIRST - needed for all subsequent operations
|
|
139
|
+
diag.buildStep(buildNumber, 1, 'Fetching Node.js binary...');
|
|
140
|
+
const cacheDir = path.join(this.projectRoot, 'node_modules', '.cache', 'sea-node-binaries');
|
|
141
|
+
const nodeBinary = await fetchNodeBinary(nodeVersion, platform, arch, cacheDir);
|
|
142
|
+
diag.success('Node binary ready');
|
|
143
|
+
|
|
144
|
+
// Step 2: Rebuild native modules using the TARGET Node binary
|
|
139
145
|
const rebuiltModules = await this.rebuildNativeModulesForTarget(
|
|
140
146
|
nativeModules,
|
|
141
147
|
target,
|
|
148
|
+
nodeBinary,
|
|
142
149
|
buildNumber
|
|
143
150
|
);
|
|
144
151
|
|
|
145
|
-
// Step
|
|
152
|
+
// Step 3: Collect config assets (manual globs)
|
|
146
153
|
const configAssets = await this.collectConfigAssets(
|
|
147
154
|
this.config.assets || [],
|
|
148
155
|
buildNumber
|
|
149
156
|
);
|
|
150
157
|
|
|
151
|
-
// Step
|
|
158
|
+
// Step 4: Collect auto-detected assets (from path.join(__dirname, ...))
|
|
152
159
|
const autoAssets = await this.collectDetectedAssets(
|
|
153
160
|
detectedAssets,
|
|
154
161
|
buildNumber
|
|
155
162
|
);
|
|
156
163
|
|
|
157
|
-
// Step
|
|
164
|
+
// Step 5: Collect platform-specific libraries (DLLs/SOs)
|
|
158
165
|
const platformLibraries = await this.collectPlatformLibraries(
|
|
159
166
|
outputConfig.libraries,
|
|
160
167
|
platform,
|
|
@@ -162,13 +169,13 @@ export class MultiTargetBuilder {
|
|
|
162
169
|
buildNumber
|
|
163
170
|
);
|
|
164
171
|
|
|
165
|
-
// Step
|
|
172
|
+
// Step 6: Prepare bundled entry with bootstrap
|
|
166
173
|
const finalEntryPath = await this.prepareFinalEntry(
|
|
167
174
|
bundledEntryPath,
|
|
168
175
|
buildNumber
|
|
169
176
|
);
|
|
170
177
|
|
|
171
|
-
// Step
|
|
178
|
+
// Step 7: Combine all assets (dedupe by assetKey)
|
|
172
179
|
const assetMap = new Map();
|
|
173
180
|
|
|
174
181
|
// Add in order of priority (later overwrites earlier)
|
|
@@ -213,11 +220,12 @@ export class MultiTargetBuilder {
|
|
|
213
220
|
}
|
|
214
221
|
}
|
|
215
222
|
|
|
216
|
-
// Step
|
|
223
|
+
// Step 8: Generate SEA using the target Node binary
|
|
217
224
|
await this.generateSEAForTarget({
|
|
218
225
|
assets: allAssets,
|
|
219
226
|
entryPath: finalEntryPath,
|
|
220
227
|
target,
|
|
228
|
+
nodeBinary,
|
|
221
229
|
outputPath,
|
|
222
230
|
executableName,
|
|
223
231
|
platform,
|
|
@@ -244,12 +252,12 @@ export class MultiTargetBuilder {
|
|
|
244
252
|
/**
|
|
245
253
|
* Rebuild native modules for specific target
|
|
246
254
|
*/
|
|
247
|
-
async rebuildNativeModulesForTarget(nativeModules, target, buildNumber) {
|
|
255
|
+
async rebuildNativeModulesForTarget(nativeModules, target, nodeBinary, buildNumber) {
|
|
248
256
|
if (nativeModules.size === 0) {
|
|
249
257
|
return [];
|
|
250
258
|
}
|
|
251
259
|
|
|
252
|
-
diag.buildStep(buildNumber,
|
|
260
|
+
diag.buildStep(buildNumber, 2, `Rebuilding ${nativeModules.size} native module(s)...`);
|
|
253
261
|
|
|
254
262
|
const rebuiltAssets = [];
|
|
255
263
|
const { platform, arch } = parseTarget(target);
|
|
@@ -287,9 +295,9 @@ export class MultiTargetBuilder {
|
|
|
287
295
|
}
|
|
288
296
|
|
|
289
297
|
// Rebuild the module
|
|
290
|
-
diag.verbose(`Rebuilding: ${moduleName}`, 2);
|
|
298
|
+
diag.verbose(`Rebuilding: ${moduleName} for Node.js ${parseTarget(target).nodeVersion}`, 2);
|
|
291
299
|
|
|
292
|
-
await this.rebuildNativeModule(moduleInfo.packageRoot, target);
|
|
300
|
+
await this.rebuildNativeModule(moduleInfo.packageRoot, target, nodeBinary);
|
|
293
301
|
|
|
294
302
|
// Find the built binary
|
|
295
303
|
const builtPath = await this.findBuiltBinary(moduleInfo, target);
|
|
@@ -317,18 +325,22 @@ export class MultiTargetBuilder {
|
|
|
317
325
|
}
|
|
318
326
|
|
|
319
327
|
/**
|
|
320
|
-
* Rebuild a single native module
|
|
328
|
+
* Rebuild a single native module using the target Node binary
|
|
321
329
|
*/
|
|
322
|
-
async rebuildNativeModule(packageRoot, target) {
|
|
330
|
+
async rebuildNativeModule(packageRoot, target, nodeBinary) {
|
|
323
331
|
const rebuildScript = path.join(__dirname, '..', 'bin', 'seabox-rebuild.mjs');
|
|
324
332
|
|
|
325
333
|
if (!fs.existsSync(rebuildScript)) {
|
|
326
334
|
throw new Error('seabox-rebuild.mjs not found');
|
|
327
335
|
}
|
|
328
336
|
|
|
337
|
+
const { nodeVersion, platform, arch } = parseTarget(target);
|
|
338
|
+
|
|
329
339
|
try {
|
|
330
|
-
|
|
331
|
-
|
|
340
|
+
// Use the TARGET Node.js binary (not system node) to run the rebuild script
|
|
341
|
+
// This ensures all operations use the exact Node version we'll embed in the SEA
|
|
342
|
+
execSync(`"${nodeBinary}" "${rebuildScript}" "${packageRoot}" ${nodeVersion} ${platform} ${arch}${this.verbose ? ' --verbose' : ''}`, {
|
|
343
|
+
stdio: 'inherit', // Show node-gyp output including header downloads
|
|
332
344
|
cwd: this.projectRoot
|
|
333
345
|
});
|
|
334
346
|
} catch (err) {
|
|
@@ -576,7 +588,8 @@ const SEA_ENCRYPTED_ASSETS = new Set([]);
|
|
|
576
588
|
arch,
|
|
577
589
|
nodeVersion,
|
|
578
590
|
rcedit,
|
|
579
|
-
buildNumber
|
|
591
|
+
buildNumber,
|
|
592
|
+
nodeBinary
|
|
580
593
|
} = options;
|
|
581
594
|
|
|
582
595
|
diag.buildStep(buildNumber, 4, 'Generating SEA blob...');
|
|
@@ -613,16 +626,12 @@ const SEA_ENCRYPTED_ASSETS = new Set([]);
|
|
|
613
626
|
const seaConfigPath = path.join(tempDir, 'sea-config.json');
|
|
614
627
|
writeSeaConfigJson(seaConfig, seaConfigPath, allAssets, tempDir);
|
|
615
628
|
|
|
616
|
-
// Generate blob
|
|
617
|
-
|
|
629
|
+
// Generate blob using the TARGET Node.js binary (already fetched)
|
|
630
|
+
// This is critical for snapshots - they must be built with the target Node version
|
|
631
|
+
diag.buildStep(buildNumber, 5, 'Generating SEA blob...');
|
|
632
|
+
await generateBlob(seaConfigPath, nodeBinary);
|
|
618
633
|
diag.success('SEA blob generated');
|
|
619
634
|
|
|
620
|
-
// Fetch Node binary
|
|
621
|
-
diag.buildStep(buildNumber, 5, 'Fetching Node.js binary...');
|
|
622
|
-
const cacheDir = path.join(this.projectRoot, 'node_modules', '.cache', 'sea-node-binaries');
|
|
623
|
-
const nodeBinary = await fetchNodeBinary(nodeVersion, platform, arch, cacheDir);
|
|
624
|
-
diag.success('Node binary ready');
|
|
625
|
-
|
|
626
635
|
// Inject blob
|
|
627
636
|
diag.buildStep(buildNumber, 6, 'Injecting blob into executable...');
|
|
628
637
|
const outputExe = path.join(outputDir, executableName);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "seabox",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Node.js Single Executable Application (SEA) builder tool with native and library extraction",
|
|
5
5
|
"main": "lib/index.mjs",
|
|
6
6
|
"type": "module",
|
|
@@ -37,11 +37,11 @@
|
|
|
37
37
|
"adm-zip": "^0.5.16",
|
|
38
38
|
"glob": "^10.0.0",
|
|
39
39
|
"postject": "^1.0.0-alpha.6",
|
|
40
|
-
"rolldown": "^1.0.0-
|
|
41
|
-
"tar": "^
|
|
40
|
+
"rolldown": "^1.0.0-rc.2",
|
|
41
|
+
"tar": "^7.5.7"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
|
-
"rcedit": "^4
|
|
44
|
+
"rcedit": "^4 || ^5"
|
|
45
45
|
},
|
|
46
46
|
"peerDependenciesMeta": {
|
|
47
47
|
"rcedit": {
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"chai": "^6.2.1",
|
|
53
53
|
"javascript-obfuscator": "^4.1.1",
|
|
54
|
-
"mocha": "^11.
|
|
54
|
+
"mocha": "^11.3.0",
|
|
55
55
|
"node-api-headers": "^1.2.0",
|
|
56
56
|
"rimraf": "^6.0.1"
|
|
57
57
|
},
|