seabox 0.1.0-beta.2 → 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/README.md +353 -110
- package/bin/seabox-rebuild.mjs +95 -0
- package/bin/seabox.mjs +135 -0
- package/lib/{blob.js → blob.mjs} +12 -18
- package/lib/bootstrap.cjs +753 -0
- package/lib/build-cache.mjs +199 -0
- package/lib/build.mjs +75 -0
- package/lib/config.mjs +234 -0
- package/lib/{crypto-assets.js → crypto-assets.mjs} +12 -47
- package/lib/entry-bundler.mjs +64 -0
- package/lib/{fetch-node.js → fetch-node.mjs} +10 -16
- package/lib/index.mjs +26 -0
- package/lib/inject.mjs +111 -0
- package/lib/{manifest.js → manifest.mjs} +5 -11
- package/lib/multi-target-builder.mjs +620 -0
- package/lib/native-scanner.mjs +210 -0
- package/lib/{obfuscate.js → obfuscate.mjs} +9 -31
- package/lib/require-shim.mjs +111 -0
- package/lib/rolldown-bundler.mjs +415 -0
- package/lib/{unsign.js → unsign.cjs} +7 -32
- package/package.json +10 -5
- package/bin/seabox-rebuild.js +0 -395
- package/bin/seabox.js +0 -81
- package/lib/bindings.js +0 -31
- package/lib/bootstrap.js +0 -697
- package/lib/build.js +0 -283
- package/lib/bytenode-hack.js +0 -56
- package/lib/config.js +0 -118
- package/lib/index.js +0 -27
- package/lib/inject.js +0 -81
- package/lib/scanner.js +0 -153
package/lib/build.js
DELETED
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* build.js
|
|
3
|
-
* Main orchestrator for SEA build pipeline.
|
|
4
|
-
* Usage: node scripts/sea/build.js [--config <path>] [--verbose]
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const fs = require('fs');
|
|
8
|
-
const path = require('path');
|
|
9
|
-
const { loadConfig, parseTarget } = require('./config');
|
|
10
|
-
const { scanAssets, groupAssets } = require('./scanner');
|
|
11
|
-
const { generateManifest, serializeManifest } = require('./manifest');
|
|
12
|
-
const { createSeaConfig, writeSeaConfigJson, generateBlob } = require('./blob');
|
|
13
|
-
const { fetchNodeBinary } = require('./fetch-node');
|
|
14
|
-
const { injectBlob } = require('./inject');
|
|
15
|
-
const { generateEncryptionKey, encryptAssets, keyToObfuscatedCode } = require('./crypto-assets');
|
|
16
|
-
const { obfuscateBootstrap } = require('./obfuscate');
|
|
17
|
-
const { execSync } = require('child_process');
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Main build orchestrator.
|
|
21
|
-
* @param {Object} options - Build options
|
|
22
|
-
* @param {string} [options.configPath] - Path to config file
|
|
23
|
-
* @param {boolean} [options.verbose] - Enable verbose logging
|
|
24
|
-
* @param {boolean} [options.debug] - Keep temporary build files
|
|
25
|
-
* @param {string} [options.projectRoot] - Project root directory
|
|
26
|
-
*/
|
|
27
|
-
async function build(options = {}) {
|
|
28
|
-
// Support both options object and legacy process.argv parsing
|
|
29
|
-
let configPath = options.configPath;
|
|
30
|
-
let verbose = options.verbose;
|
|
31
|
-
let debug = options.debug;
|
|
32
|
-
let projectRoot = options.projectRoot || process.cwd();
|
|
33
|
-
|
|
34
|
-
// Fallback to process.argv if called without options
|
|
35
|
-
if (!options.configPath && !options.verbose && !options.debug) {
|
|
36
|
-
const args = process.argv.slice(2);
|
|
37
|
-
configPath = args.includes('--config') ? args[args.indexOf('--config') + 1] : null;
|
|
38
|
-
verbose = args.includes('--verbose');
|
|
39
|
-
debug = args.includes('--debug');
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (!verbose) {
|
|
43
|
-
console.log('Building SEA...');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const config = loadConfig(configPath, projectRoot);
|
|
47
|
-
|
|
48
|
-
if (verbose) {
|
|
49
|
-
console.log('\n[1/8] Loading configuration');
|
|
50
|
-
console.log('Config:', JSON.stringify(config, null, 2));
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const target = config.targets[0];
|
|
54
|
-
const { nodeVersion, platform, arch } = parseTarget(target);
|
|
55
|
-
|
|
56
|
-
if (verbose) {
|
|
57
|
-
console.log(`Target: Node ${nodeVersion} on ${platform}-${arch}`);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (config.rebuild) {
|
|
61
|
-
if (verbose) console.log('\n[1b] Rebuilding native modules');
|
|
62
|
-
const rebuildScript = path.join(__dirname, '..', 'bin', 'seabox-rebuild.js');
|
|
63
|
-
try {
|
|
64
|
-
execSync(`node "${rebuildScript}" --target ${target} "${projectRoot}"`, {
|
|
65
|
-
stdio: verbose ? 'inherit' : 'ignore'
|
|
66
|
-
});
|
|
67
|
-
if (verbose) console.log('✓ Native modules rebuilt');
|
|
68
|
-
} catch (error) {
|
|
69
|
-
console.error('✗ Native module rebuild failed:', error.message);
|
|
70
|
-
throw new Error('Native module rebuild failed. See output above for details.');
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const assets = await scanAssets(
|
|
75
|
-
config.assets,
|
|
76
|
-
config.binaries || [],
|
|
77
|
-
config.exclude || [],
|
|
78
|
-
projectRoot
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
const { binaries, nonBinaries } = groupAssets(assets);
|
|
82
|
-
|
|
83
|
-
if (verbose) {
|
|
84
|
-
console.log(`\n[2/8] Scanning assets`);
|
|
85
|
-
console.log(`Found ${assets.length} assets (${binaries.length} binaries, ${nonBinaries.length} non-binaries)`);
|
|
86
|
-
console.log('Binaries:', binaries.map(b => b.assetKey));
|
|
87
|
-
const nonBinariesList = assets.filter(a => !a.isBinary);
|
|
88
|
-
console.log('Non-binary assets:', nonBinariesList.map(a => a.assetKey));
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
let encryptionKey = null;
|
|
92
|
-
let encryptedAssetKeys = new Set();
|
|
93
|
-
|
|
94
|
-
if (config.encryptAssets) {
|
|
95
|
-
if (verbose) console.log('\n[2b] Encrypting assets');
|
|
96
|
-
encryptionKey = generateEncryptionKey();
|
|
97
|
-
|
|
98
|
-
const excludeFromEncryption = [
|
|
99
|
-
'sea-manifest.json',
|
|
100
|
-
...(config.encryptExclude || [])
|
|
101
|
-
];
|
|
102
|
-
|
|
103
|
-
const encryptedMap = encryptAssets(assets, encryptionKey, excludeFromEncryption);
|
|
104
|
-
|
|
105
|
-
for (const asset of assets) {
|
|
106
|
-
if (encryptedMap.has(asset.assetKey)) {
|
|
107
|
-
asset.content = encryptedMap.get(asset.assetKey);
|
|
108
|
-
asset.encrypted = true;
|
|
109
|
-
encryptedAssetKeys.add(asset.assetKey);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (verbose) {
|
|
114
|
-
console.log(`Encrypted ${encryptedAssetKeys.size} assets`);
|
|
115
|
-
console.log('Encrypted assets:', Array.from(encryptedAssetKeys));
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (verbose) console.log('\n[3/8] Generating manifest');
|
|
120
|
-
const manifest = generateManifest(assets, config, platform, arch);
|
|
121
|
-
const manifestJson = serializeManifest(manifest);
|
|
122
|
-
|
|
123
|
-
const manifestAsset = {
|
|
124
|
-
sourcePath: null,
|
|
125
|
-
assetKey: 'sea-manifest.json',
|
|
126
|
-
isBinary: false,
|
|
127
|
-
content: Buffer.from(manifestJson, 'utf8')
|
|
128
|
-
};
|
|
129
|
-
assets.push(manifestAsset);
|
|
130
|
-
|
|
131
|
-
if (verbose) console.log('\n[4/8] Preparing bootstrap');
|
|
132
|
-
const bootstrapPath = path.join(__dirname, 'bootstrap.js');
|
|
133
|
-
const bootstrapContent = fs.readFileSync(bootstrapPath, 'utf8');
|
|
134
|
-
|
|
135
|
-
let augmentedBootstrap = bootstrapContent;
|
|
136
|
-
if (config.encryptAssets && encryptionKey && encryptedAssetKeys.size > 0) {
|
|
137
|
-
const keyCode = keyToObfuscatedCode(encryptionKey);
|
|
138
|
-
const encryptedKeysJson = JSON.stringify(Array.from(encryptedAssetKeys));
|
|
139
|
-
|
|
140
|
-
const encryptionSetup = `
|
|
141
|
-
const SEA_ENCRYPTION_KEY = ${keyCode};
|
|
142
|
-
const SEA_ENCRYPTED_ASSETS = new Set(${encryptedKeysJson});
|
|
143
|
-
`;
|
|
144
|
-
|
|
145
|
-
augmentedBootstrap = bootstrapContent.replace(
|
|
146
|
-
" 'use strict';",
|
|
147
|
-
" 'use strict';\n" + encryptionSetup
|
|
148
|
-
);
|
|
149
|
-
|
|
150
|
-
if (verbose) console.log('Injecting encryption key and obfuscating bootstrap');
|
|
151
|
-
augmentedBootstrap = obfuscateBootstrap(augmentedBootstrap);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const entryPath = path.resolve(projectRoot, config.entry);
|
|
155
|
-
let entryContent = fs.readFileSync(entryPath, 'utf8');
|
|
156
|
-
|
|
157
|
-
// Strip shebang if present (e.g., #!/usr/bin/env node) - handle both Unix and Windows line endings
|
|
158
|
-
entryContent = entryContent.replace(/^#!.*(\r?\n)/, '');
|
|
159
|
-
|
|
160
|
-
// When using snapshot, wrap the application in v8.startupSnapshot.setDeserializeMainFunction
|
|
161
|
-
let bundledEntry;
|
|
162
|
-
if (config.useSnapshot) {
|
|
163
|
-
// Snapshot mode: Bootstrap runs at build time to set up deserialization callback
|
|
164
|
-
// Application code runs at runtime inside the callback
|
|
165
|
-
|
|
166
|
-
bundledEntry = `${augmentedBootstrap}\n\n`;
|
|
167
|
-
bundledEntry += `(function() {\n`;
|
|
168
|
-
bundledEntry += ` const v8 = require('v8');\n`;
|
|
169
|
-
bundledEntry += ` if (v8.startupSnapshot && v8.startupSnapshot.isBuildingSnapshot()) {\n`;
|
|
170
|
-
bundledEntry += ` v8.startupSnapshot.setDeserializeMainFunction(() => {\n`;
|
|
171
|
-
bundledEntry += entryContent + '\n';
|
|
172
|
-
bundledEntry += ` });\n`;
|
|
173
|
-
bundledEntry += ` } else {\n`;
|
|
174
|
-
bundledEntry += entryContent + '\n';
|
|
175
|
-
bundledEntry += ` }\n`;
|
|
176
|
-
bundledEntry += `})();\n`;
|
|
177
|
-
} else {
|
|
178
|
-
const requireOverride = `
|
|
179
|
-
(function() {
|
|
180
|
-
const originalRequire = require;
|
|
181
|
-
const __seaNativeModuleMap = typeof global.__seaNativeModuleMap !== 'undefined' ? global.__seaNativeModuleMap : {};
|
|
182
|
-
const __seaCacheDir = typeof global.__seaCacheDir !== 'undefined' ? global.__seaCacheDir : '';
|
|
183
|
-
|
|
184
|
-
require = function(id) {
|
|
185
|
-
if (typeof id === 'string' && id.endsWith('.node')) {
|
|
186
|
-
const path = originalRequire('path');
|
|
187
|
-
const basename = path.basename(id);
|
|
188
|
-
|
|
189
|
-
if (__seaNativeModuleMap[basename]) {
|
|
190
|
-
const exports = {};
|
|
191
|
-
process.dlopen({ exports }, __seaNativeModuleMap[basename]);
|
|
192
|
-
return exports;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if (path.isAbsolute(id) && id.startsWith(__seaCacheDir)) {
|
|
196
|
-
const exports = {};
|
|
197
|
-
process.dlopen({ exports }, id);
|
|
198
|
-
return exports;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (id === 'bindings') {
|
|
203
|
-
return function(name) {
|
|
204
|
-
if (!name.endsWith('.node')) {
|
|
205
|
-
name += '.node';
|
|
206
|
-
}
|
|
207
|
-
if (__seaNativeModuleMap[name]) {
|
|
208
|
-
const exports = {};
|
|
209
|
-
process.dlopen({ exports }, __seaNativeModuleMap[name]);
|
|
210
|
-
return exports;
|
|
211
|
-
}
|
|
212
|
-
throw new Error('Could not load native module "' + name + '" - not found in SEA cache');
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return originalRequire.call(this, id);
|
|
217
|
-
};
|
|
218
|
-
})();
|
|
219
|
-
`;
|
|
220
|
-
bundledEntry = `${augmentedBootstrap}\n\n${requireOverride}\n${entryContent}`;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const bundledEntryPath = path.join(projectRoot, 'out', '_sea-entry.js');
|
|
224
|
-
|
|
225
|
-
fs.mkdirSync(path.dirname(bundledEntryPath), { recursive: true });
|
|
226
|
-
fs.writeFileSync(bundledEntryPath, bundledEntry, 'utf8');
|
|
227
|
-
|
|
228
|
-
if (verbose) console.log(`Bundled entry: ${bundledEntryPath}`);
|
|
229
|
-
|
|
230
|
-
if (verbose) console.log('\n[5/8] Creating SEA blob configuration');
|
|
231
|
-
const blobOutputPath = path.join(projectRoot, config.outputPath, 'sea-blob.bin');
|
|
232
|
-
const seaConfig = createSeaConfig(bundledEntryPath, blobOutputPath, assets, config);
|
|
233
|
-
|
|
234
|
-
const seaConfigPath = path.join(projectRoot, config.outputPath, 'sea-config.json');
|
|
235
|
-
const tempDir = path.join(projectRoot, config.outputPath, '.sea-temp');
|
|
236
|
-
fs.mkdirSync(path.dirname(seaConfigPath), { recursive: true });
|
|
237
|
-
fs.mkdirSync(tempDir, { recursive: true });
|
|
238
|
-
writeSeaConfigJson(seaConfig, seaConfigPath, assets, tempDir);
|
|
239
|
-
|
|
240
|
-
if (verbose) {
|
|
241
|
-
console.log(`SEA config: ${seaConfigPath}`);
|
|
242
|
-
console.log('\n[6/8] Generating SEA blob');
|
|
243
|
-
}
|
|
244
|
-
await generateBlob(seaConfigPath);
|
|
245
|
-
|
|
246
|
-
if (verbose) console.log('\n[7/8] Fetching Node.js binary');
|
|
247
|
-
const cacheDir = path.join(projectRoot, 'node_modules', '.cache', 'sea-node-binaries');
|
|
248
|
-
const nodeBinary = await fetchNodeBinary(nodeVersion, platform, arch, cacheDir);
|
|
249
|
-
|
|
250
|
-
if (verbose) console.log('\n[8/8] Injecting SEA blob into Node binary');
|
|
251
|
-
const outputExe = path.join(projectRoot, config.outputPath, config.output);
|
|
252
|
-
await injectBlob(nodeBinary, blobOutputPath, outputExe, platform, verbose);
|
|
253
|
-
|
|
254
|
-
if (!debug) {
|
|
255
|
-
if (verbose) console.log('\nCleaning up temporary files');
|
|
256
|
-
const filesToClean = [seaConfigPath, blobOutputPath];
|
|
257
|
-
for (const file of filesToClean) {
|
|
258
|
-
if (fs.existsSync(file)) {
|
|
259
|
-
fs.unlinkSync(file);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
if (fs.existsSync(tempDir)) {
|
|
263
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const sizeMB = (fs.statSync(outputExe).size / 1024 / 1024).toFixed(2);
|
|
268
|
-
console.log(`\n✓ Build complete: ${config.output} (${sizeMB} MB)`);
|
|
269
|
-
if (verbose) console.log(`Output path: ${outputExe}`);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Run if called directly
|
|
273
|
-
if (require.main === module) {
|
|
274
|
-
build().catch(error => {
|
|
275
|
-
console.error('Build failed:', error.message);
|
|
276
|
-
if (process.argv.includes('--verbose')) {
|
|
277
|
-
console.error(error.stack);
|
|
278
|
-
}
|
|
279
|
-
process.exit(1);
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
module.exports = { build };
|
package/lib/bytenode-hack.js
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/*MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2018 Osama Abbas
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.*/
|
|
22
|
-
|
|
23
|
-
module.exports.fixBytecode = function (bytecodeBuffer) {
|
|
24
|
-
if (!isBuffer(bytecodeBuffer)) {
|
|
25
|
-
throw new Error('bytecodeBuffer must be a buffer object.');
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const dummyBytecode = compileCode('"ಠ_ಠ"');
|
|
29
|
-
const version = parseFloat(process.version.slice(1, 5));
|
|
30
|
-
|
|
31
|
-
if (process.version.startsWith('v8.8') || process.version.startsWith('v8.9')) {
|
|
32
|
-
// Node is v8.8.x or v8.9.x
|
|
33
|
-
dummyBytecode.subarray(16, 20).copy(bytecodeBuffer, 16);
|
|
34
|
-
dummyBytecode.subarray(20, 24).copy(bytecodeBuffer, 20);
|
|
35
|
-
} else if (version >= 12 && version <= 23) {
|
|
36
|
-
dummyBytecode.subarray(12, 16).copy(bytecodeBuffer, 12);
|
|
37
|
-
} else {
|
|
38
|
-
dummyBytecode.subarray(12, 16).copy(bytecodeBuffer, 12);
|
|
39
|
-
dummyBytecode.subarray(16, 20).copy(bytecodeBuffer, 16);
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
module.exports.readSourceHash = function (bytecodeBuffer) {
|
|
44
|
-
if (!isBuffer(bytecodeBuffer)) {
|
|
45
|
-
throw new Error('bytecodeBuffer must be a buffer object.');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (process.version.startsWith('v8.8') || process.version.startsWith('v8.9')) {
|
|
49
|
-
// Node is v8.8.x or v8.9.x
|
|
50
|
-
// eslint-disable-next-line no-return-assign
|
|
51
|
-
return bytecodeBuffer.subarray(12, 16).reduce((sum, number, power) => sum += number * Math.pow(256, power), 0);
|
|
52
|
-
} else {
|
|
53
|
-
// eslint-disable-next-line no-return-assign
|
|
54
|
-
return bytecodeBuffer.subarray(8, 12).reduce((sum, number, power) => sum += number * Math.pow(256, power), 0);
|
|
55
|
-
}
|
|
56
|
-
};
|
package/lib/config.js
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* config.js
|
|
3
|
-
* Load and validate SEA configuration from package.json or standalone config file.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const fs = require('fs');
|
|
7
|
-
const path = require('path');
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @typedef {Object} SeaConfig
|
|
11
|
-
* @property {string} entry - Path to the bundled application entry script
|
|
12
|
-
* @property {string[]} assets - Array of glob patterns or paths to include in SEA blob (supports '!' prefix for exclusions)
|
|
13
|
-
* @property {string[]} targets - Array of target specifiers (e.g., "node24.11.0-win-x64")
|
|
14
|
-
* @property {string} output - Output executable filename
|
|
15
|
-
* @property {string} outputPath - Directory for the final executable
|
|
16
|
-
* @property {string[]} [binaries] - Array of binary filename patterns to extract at runtime
|
|
17
|
-
* @property {boolean} [disableExperimentalSEAWarning] - Suppress SEA experimental warning
|
|
18
|
-
* @property {boolean} [useSnapshot] - Enable V8 snapshot
|
|
19
|
-
* @property {boolean} [useCodeCache] - Enable V8 code cache
|
|
20
|
-
* @property {string[]} [exclude] - (Legacy) Glob patterns to exclude from assets - use '!' prefix in assets instead
|
|
21
|
-
* @property {boolean} [encryptAssets] - Enable asset encryption (default: false)
|
|
22
|
-
* @property {string[]} [encryptExclude] - Asset patterns to exclude from encryption
|
|
23
|
-
* @property {boolean} [rebuild] - Automatically rebuild native modules for target platform (default: false)
|
|
24
|
-
* @property {boolean} [verbose] - Enable diagnostic logging
|
|
25
|
-
* @property {string} [cacheLocation] - Cache directory for extracted binaries (default: './.sea-cache', supports env vars like '%LOCALAPPDATA%\\path')
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Load SEA configuration from package.json or a standalone file.
|
|
30
|
-
* @param {string} [configPath] - Optional path to a standalone config file
|
|
31
|
-
* @param {string} [projectRoot] - Project root directory (defaults to cwd)
|
|
32
|
-
* @returns {SeaConfig}
|
|
33
|
-
*/
|
|
34
|
-
function loadConfig(configPath, projectRoot = process.cwd()) {
|
|
35
|
-
let config;
|
|
36
|
-
|
|
37
|
-
if (configPath) {
|
|
38
|
-
// Load from standalone config file
|
|
39
|
-
const fullPath = path.resolve(projectRoot, configPath);
|
|
40
|
-
if (!fs.existsSync(fullPath)) {
|
|
41
|
-
throw new Error(`Config file not found: ${fullPath}`);
|
|
42
|
-
}
|
|
43
|
-
config = JSON.parse(fs.readFileSync(fullPath, 'utf8'));
|
|
44
|
-
} else {
|
|
45
|
-
// Load from package.json
|
|
46
|
-
const pkgPath = path.join(projectRoot, 'package.json');
|
|
47
|
-
if (!fs.existsSync(pkgPath)) {
|
|
48
|
-
throw new Error(`package.json not found in: ${projectRoot}`);
|
|
49
|
-
}
|
|
50
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
51
|
-
|
|
52
|
-
// Check for 'sea' field first, fall back to 'pkg' for migration compatibility
|
|
53
|
-
config = pkg.seabox || pkg.pkg;
|
|
54
|
-
|
|
55
|
-
if (!config) {
|
|
56
|
-
throw new Error('No "seabox" or "pkg" configuration found in package.json');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Attach package metadata for manifest
|
|
60
|
-
config._packageName = pkg.name;
|
|
61
|
-
config._packageVersion = pkg.version;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
validateConfig(config);
|
|
65
|
-
return config;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Validate required configuration fields.
|
|
70
|
-
* @param {SeaConfig} config
|
|
71
|
-
*/
|
|
72
|
-
function validateConfig(config) {
|
|
73
|
-
const required = ['entry', 'assets', 'targets', 'output', 'outputPath'];
|
|
74
|
-
const missing = required.filter(field => !config[field]);
|
|
75
|
-
|
|
76
|
-
if (missing.length > 0) {
|
|
77
|
-
throw new Error(`Missing required SEA config fields: ${missing.join(', ')}`);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (!Array.isArray(config.assets) || config.assets.length === 0) {
|
|
81
|
-
throw new Error('SEA config "assets" must be a non-empty array');
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (!Array.isArray(config.targets) || config.targets.length === 0) {
|
|
85
|
-
throw new Error('SEA config "targets" must be a non-empty array');
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Validate target format (e.g., "node24.11.0-win-x64")
|
|
89
|
-
const targetPattern = /^node\d+\.\d+\.\d+-\w+-\w+$/;
|
|
90
|
-
config.targets.forEach(target => {
|
|
91
|
-
if (!targetPattern.test(target)) {
|
|
92
|
-
throw new Error(`Invalid target format: "${target}". Expected format: "nodeX.Y.Z-platform-arch"`);
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Parse a target string into components.
|
|
99
|
-
* @param {string} target - e.g., "node24.11.0-win-x64"
|
|
100
|
-
* @returns {{nodeVersion: string, platform: string, arch: string}}
|
|
101
|
-
*/
|
|
102
|
-
function parseTarget(target) {
|
|
103
|
-
const match = target.match(/^node(\d+\.\d+\.\d+)-(\w+)-(\w+)$/);
|
|
104
|
-
if (!match) {
|
|
105
|
-
throw new Error(`Cannot parse target: ${target}`);
|
|
106
|
-
}
|
|
107
|
-
return {
|
|
108
|
-
nodeVersion: match[1],
|
|
109
|
-
platform: match[2],
|
|
110
|
-
arch: match[3]
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
module.exports = {
|
|
115
|
-
loadConfig,
|
|
116
|
-
validateConfig,
|
|
117
|
-
parseTarget
|
|
118
|
-
};
|
package/lib/index.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* index.js
|
|
3
|
-
* Main entry point for seabox
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const { build } = require('./build');
|
|
7
|
-
const { loadConfig, parseTarget } = require('./config');
|
|
8
|
-
const { scanAssets, groupAssets } = require('./scanner');
|
|
9
|
-
const { generateManifest, serializeManifest } = require('./manifest');
|
|
10
|
-
const { createSeaConfig, writeSeaConfigJson, generateBlob } = require('./blob');
|
|
11
|
-
const { fetchNodeBinary } = require('./fetch-node');
|
|
12
|
-
const { injectBlob } = require('./inject');
|
|
13
|
-
|
|
14
|
-
module.exports = {
|
|
15
|
-
build,
|
|
16
|
-
loadConfig,
|
|
17
|
-
parseTarget,
|
|
18
|
-
scanAssets,
|
|
19
|
-
groupAssets,
|
|
20
|
-
generateManifest,
|
|
21
|
-
serializeManifest,
|
|
22
|
-
createSeaConfig,
|
|
23
|
-
writeSeaConfigJson,
|
|
24
|
-
generateBlob,
|
|
25
|
-
fetchNodeBinary,
|
|
26
|
-
injectBlob
|
|
27
|
-
};
|
package/lib/inject.js
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* inject.js
|
|
3
|
-
* Inject SEA blob into Node binary using postject.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const fs = require('fs');
|
|
7
|
-
const path = require('path');
|
|
8
|
-
const { execFile } = require('child_process');
|
|
9
|
-
const { promisify } = require('util');
|
|
10
|
-
const { removeSignature } = require('./unsign');
|
|
11
|
-
|
|
12
|
-
const execFileAsync = promisify(execFile);
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Inject a SEA blob into a Node.js binary using postject.
|
|
16
|
-
* @param {string} nodeBinaryPath - Path to the source Node binary
|
|
17
|
-
* @param {string} blobPath - Path to the SEA blob file
|
|
18
|
-
* @param {string} outputPath - Path for the output executable
|
|
19
|
-
* @param {string} platform - Target platform (win32, linux, darwin)
|
|
20
|
-
* @returns {Promise<void>}
|
|
21
|
-
*/
|
|
22
|
-
async function injectBlob(nodeBinaryPath, blobPath, outputPath, platform, verbose) {
|
|
23
|
-
// Copy node binary to output location
|
|
24
|
-
fs.copyFileSync(nodeBinaryPath, outputPath);
|
|
25
|
-
|
|
26
|
-
// Remove existing signature before postject injection
|
|
27
|
-
// The downloaded Node.js binary is signed, and postject will corrupt this signature
|
|
28
|
-
await removeSignature(outputPath, platform);
|
|
29
|
-
|
|
30
|
-
// Prepare postject command
|
|
31
|
-
const sentinel = 'NODE_SEA_BLOB';
|
|
32
|
-
const sentinelFuse = 'NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2';
|
|
33
|
-
|
|
34
|
-
const args = [
|
|
35
|
-
outputPath,
|
|
36
|
-
sentinel,
|
|
37
|
-
blobPath,
|
|
38
|
-
'--sentinel-fuse', sentinelFuse
|
|
39
|
-
];
|
|
40
|
-
|
|
41
|
-
// Platform-specific postject options
|
|
42
|
-
if (platform === 'darwin') {
|
|
43
|
-
args.push('--macho-segment-name', 'NODE_SEA');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
console.log(`Injecting SEA blob into: ${outputPath}`);
|
|
47
|
-
|
|
48
|
-
// Use cmd.exe on Windows to run npx
|
|
49
|
-
const isWindows = process.platform === 'win32';
|
|
50
|
-
const command = isWindows ? 'cmd.exe' : 'npx';
|
|
51
|
-
const cmdArgs = isWindows
|
|
52
|
-
? ['/c', 'npx', 'postject', ...args]
|
|
53
|
-
: ['postject', ...args];
|
|
54
|
-
|
|
55
|
-
//console.log(`Command: ${command} ${cmdArgs.join(' ')}`);
|
|
56
|
-
|
|
57
|
-
try {
|
|
58
|
-
const { stdout, stderr } = await execFileAsync(command, cmdArgs);
|
|
59
|
-
if (stdout && verbose) console.log(stdout);
|
|
60
|
-
if (stderr && verbose) console.error(stderr);
|
|
61
|
-
console.log('✓ SEA blob injected successfully');
|
|
62
|
-
} catch (error) {
|
|
63
|
-
throw new Error(`Postject injection failed: ${error.message}`);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
//console.log('\nNote: Executable is now ready for signing with your certificate');
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Resolve the postject executable path.
|
|
71
|
-
* @returns {string}
|
|
72
|
-
*/
|
|
73
|
-
function resolvePostject() {
|
|
74
|
-
// Use npx to run postject
|
|
75
|
-
return 'npx';
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
module.exports = {
|
|
79
|
-
injectBlob,
|
|
80
|
-
resolvePostject
|
|
81
|
-
};
|