seabox 0.1.2 → 0.2.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/.mocharc.json +6 -6
- package/LICENSE.MD +21 -21
- package/README.md +310 -310
- package/bin/seabox-rebuild.mjs +88 -88
- package/bin/seabox.mjs +150 -147
- package/lib/blob.mjs +104 -104
- package/lib/bootstrap.cjs +756 -756
- package/lib/build-cache.mjs +199 -199
- package/lib/build.mjs +77 -77
- package/lib/config.mjs +243 -243
- package/lib/crypto-assets.mjs +125 -125
- package/lib/diagnostics.mjs +203 -203
- package/lib/entry-bundler.mjs +64 -64
- package/lib/fetch-node.mjs +172 -172
- package/lib/index.mjs +26 -26
- package/lib/inject.mjs +106 -106
- package/lib/manifest.mjs +100 -100
- package/lib/multi-target-builder.mjs +697 -697
- package/lib/native-scanner.mjs +203 -203
- package/lib/obfuscate.mjs +51 -51
- package/lib/require-shim.mjs +113 -113
- package/lib/rolldown-bundler.mjs +411 -411
- package/lib/unsign.cjs +197 -169
- package/package.json +61 -61
|
@@ -1,697 +1,697 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* multi-target-builder.mjs
|
|
3
|
-
* Orchestrate parallel builds for multiple target platforms.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import fs from 'fs';
|
|
7
|
-
import path from 'path';
|
|
8
|
-
import { execSync } from 'child_process';
|
|
9
|
-
import crypto from 'crypto';
|
|
10
|
-
import { fileURLToPath } from 'url';
|
|
11
|
-
import Module from 'module';
|
|
12
|
-
|
|
13
|
-
import { bundleWithRollup } from './rolldown-bundler.mjs';
|
|
14
|
-
import { BuildCache } from './build-cache.mjs';
|
|
15
|
-
import { parseTarget } from './config.mjs';
|
|
16
|
-
import { generateManifest, serializeManifest } from './manifest.mjs';
|
|
17
|
-
import { createSeaConfig, writeSeaConfigJson, generateBlob } from './blob.mjs';
|
|
18
|
-
import { fetchNodeBinary } from './fetch-node.mjs';
|
|
19
|
-
import { injectBlob } from './inject.mjs';
|
|
20
|
-
import { generateEncryptionKey, encryptAssets, keyToObfuscatedCode } from './crypto-assets.mjs';
|
|
21
|
-
import { obfuscateBootstrap } from './obfuscate.mjs';
|
|
22
|
-
import { bundleEntry } from './entry-bundler.mjs';
|
|
23
|
-
import * as diag from './diagnostics.mjs';
|
|
24
|
-
|
|
25
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
26
|
-
const __dirname = path.dirname(__filename);
|
|
27
|
-
const require = Module.createRequire(import.meta.url);
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Multi-target build orchestrator
|
|
31
|
-
*/
|
|
32
|
-
export class MultiTargetBuilder {
|
|
33
|
-
/**
|
|
34
|
-
* @param {import('./config.mjs').SeaboxConfigV2} config - Build configuration
|
|
35
|
-
* @param {string} projectRoot - Project root directory
|
|
36
|
-
*/
|
|
37
|
-
constructor(config, projectRoot = process.cwd()) {
|
|
38
|
-
this.config = config;
|
|
39
|
-
this.projectRoot = projectRoot;
|
|
40
|
-
this.cache = new BuildCache(path.join(projectRoot, 'node_modules', '.cache', '.seabox-cache'));
|
|
41
|
-
this.verbose = config.verbose || false;
|
|
42
|
-
|
|
43
|
-
// Set verbose mode for diagnostics
|
|
44
|
-
diag.setVerbose(this.verbose);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Build all configured targets
|
|
49
|
-
* @returns {Promise<Array<{target: string, path: string}>>}
|
|
50
|
-
*/
|
|
51
|
-
async buildAll() {
|
|
52
|
-
diag.header('Seabox Multi-Target Build');
|
|
53
|
-
|
|
54
|
-
// Step 1: Bundle entry once (platform-agnostic JavaScript)
|
|
55
|
-
const { bundledPath, nativeModules, detectedAssets } = await this.bundleEntry();
|
|
56
|
-
|
|
57
|
-
if (this.verbose && detectedAssets.size > 0) {
|
|
58
|
-
diag.separator();
|
|
59
|
-
diag.info('Auto-detected assets:');
|
|
60
|
-
for (const assetPath of detectedAssets) {
|
|
61
|
-
diag.listItem(assetPath, 1);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (this.verbose && nativeModules.size > 0) {
|
|
66
|
-
diag.separator();
|
|
67
|
-
diag.info('Native modules detected:');
|
|
68
|
-
for (const [name, info] of nativeModules) {
|
|
69
|
-
diag.listItem(`${name}: ${info.packageRoot}`, 1);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Step 2: Build all targets (can be parallelized)
|
|
74
|
-
diag.separator();
|
|
75
|
-
diag.info(`Building ${this.config.outputs.length} target(s)...`);
|
|
76
|
-
diag.separator();
|
|
77
|
-
|
|
78
|
-
const buildPromises = this.config.outputs.map((output, index) =>
|
|
79
|
-
this.buildTarget(output, bundledPath, nativeModules, detectedAssets, index + 1)
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
const results = await Promise.all(buildPromises);
|
|
83
|
-
|
|
84
|
-
diag.summary('All builds completed successfully!');
|
|
85
|
-
|
|
86
|
-
return results;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Bundle the entry file with Rollup
|
|
91
|
-
* @returns {Promise<{bundledPath: string, nativeModules: Map}>}
|
|
92
|
-
*/
|
|
93
|
-
async bundleEntry() {
|
|
94
|
-
diag.step(1, 6, 'Bundling application with Rollup...');
|
|
95
|
-
|
|
96
|
-
const entryPath = path.resolve(this.projectRoot, this.config.entry);
|
|
97
|
-
|
|
98
|
-
if (!fs.existsSync(entryPath)) {
|
|
99
|
-
throw new Error(`Entry file not found: ${entryPath}`);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const bundledPath = path.join(this.projectRoot, 'out', '_sea-entry.js');
|
|
103
|
-
|
|
104
|
-
// Create output directory
|
|
105
|
-
fs.mkdirSync(path.dirname(bundledPath), { recursive: true });
|
|
106
|
-
|
|
107
|
-
const result = await bundleWithRollup(
|
|
108
|
-
entryPath,
|
|
109
|
-
bundledPath,
|
|
110
|
-
{ ...this.config, _projectRoot: this.projectRoot },
|
|
111
|
-
process.platform, // Use current platform for bundling (JavaScript is platform-agnostic)
|
|
112
|
-
process.arch,
|
|
113
|
-
this.verbose
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
diag.success(`Bundle created: ${bundledPath}`);
|
|
117
|
-
|
|
118
|
-
return result;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Build a single target
|
|
125
|
-
* @param {import('./config.mjs').OutputTarget} outputConfig - Target configuration
|
|
126
|
-
* @param {string} bundledEntryPath - Path to bundled entry
|
|
127
|
-
* @param {Map} nativeModules - Detected native modules
|
|
128
|
-
* @param {Set<string>} detectedAssets - Auto-detected assets from bundler
|
|
129
|
-
* @param {number} buildNumber - Build number for display
|
|
130
|
-
* @returns {Promise<{target: string, path: string}>}
|
|
131
|
-
*/
|
|
132
|
-
async buildTarget(outputConfig, bundledEntryPath, nativeModules, detectedAssets, buildNumber) {
|
|
133
|
-
const { target, path: outputPath, output: executableName } = outputConfig;
|
|
134
|
-
const { nodeVersion, platform, arch } = parseTarget(target);
|
|
135
|
-
|
|
136
|
-
diag.subheader(`[Build ${buildNumber}] Target: ${target}`);
|
|
137
|
-
|
|
138
|
-
// Step 1: Rebuild native modules for this target
|
|
139
|
-
const rebuiltModules = await this.rebuildNativeModulesForTarget(
|
|
140
|
-
nativeModules,
|
|
141
|
-
target,
|
|
142
|
-
buildNumber
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
// Step 2: Collect config assets (manual globs)
|
|
146
|
-
const configAssets = await this.collectConfigAssets(
|
|
147
|
-
this.config.assets || [],
|
|
148
|
-
buildNumber
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
// Step 3: Collect auto-detected assets (from path.join(__dirname, ...))
|
|
152
|
-
const autoAssets = await this.collectDetectedAssets(
|
|
153
|
-
detectedAssets,
|
|
154
|
-
buildNumber
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
// Step 4: Collect platform-specific libraries (DLLs/SOs)
|
|
158
|
-
const platformLibraries = await this.collectPlatformLibraries(
|
|
159
|
-
outputConfig.libraries,
|
|
160
|
-
platform,
|
|
161
|
-
arch,
|
|
162
|
-
buildNumber
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
// Step 5: Prepare bundled entry with bootstrap
|
|
166
|
-
const finalEntryPath = await this.prepareFinalEntry(
|
|
167
|
-
bundledEntryPath,
|
|
168
|
-
buildNumber
|
|
169
|
-
);
|
|
170
|
-
|
|
171
|
-
// Step 6: Combine all assets (dedupe by assetKey)
|
|
172
|
-
const assetMap = new Map();
|
|
173
|
-
|
|
174
|
-
// Add in order of priority (later overwrites earlier)
|
|
175
|
-
for (const asset of [...rebuiltModules, ...configAssets, ...autoAssets, ...platformLibraries]) {
|
|
176
|
-
assetMap.set(asset.assetKey, asset);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const allAssets = Array.from(assetMap.values());
|
|
180
|
-
|
|
181
|
-
// Verbose logging: List all embedded assets
|
|
182
|
-
if (this.verbose) {
|
|
183
|
-
diag.separator();
|
|
184
|
-
diag.verbose(`Embedded Assets (${allAssets.length} total):`, 1);
|
|
185
|
-
|
|
186
|
-
const nativeAssets = allAssets.filter(a => a.assetKey.includes('.node'));
|
|
187
|
-
const libraryAssets = allAssets.filter(a => a.isBinary && !a.assetKey.includes('.node'));
|
|
188
|
-
const regularAssets = allAssets.filter(a => !a.isBinary);
|
|
189
|
-
|
|
190
|
-
if (nativeAssets.length > 0) {
|
|
191
|
-
diag.verbose(`Native Modules (${nativeAssets.length}):`, 1);
|
|
192
|
-
for (const asset of nativeAssets) {
|
|
193
|
-
const size = diag.formatSize(fs.statSync(asset.sourcePath).size);
|
|
194
|
-
const displayName = path.basename(asset.assetKey);
|
|
195
|
-
diag.verbose(`- ${displayName} (${size})`, 2);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (libraryAssets.length > 0) {
|
|
200
|
-
diag.verbose(`Platform Libraries (${libraryAssets.length}):`, 1);
|
|
201
|
-
for (const asset of libraryAssets) {
|
|
202
|
-
const size = diag.formatSize(fs.statSync(asset.sourcePath).size);
|
|
203
|
-
diag.verbose(`- ${asset.assetKey} (${size})`, 2);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (regularAssets.length > 0) {
|
|
208
|
-
diag.verbose(`Regular Assets (${regularAssets.length}):`, 1);
|
|
209
|
-
for (const asset of regularAssets) {
|
|
210
|
-
const size = diag.formatSize(fs.statSync(asset.sourcePath).size);
|
|
211
|
-
diag.verbose(`- ${asset.assetKey} (${size})`, 2);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Step 7: Generate SEA
|
|
217
|
-
await this.generateSEAForTarget({
|
|
218
|
-
assets: allAssets,
|
|
219
|
-
entryPath: finalEntryPath,
|
|
220
|
-
target,
|
|
221
|
-
outputPath,
|
|
222
|
-
executableName,
|
|
223
|
-
platform,
|
|
224
|
-
arch,
|
|
225
|
-
nodeVersion,
|
|
226
|
-
rcedit: outputConfig.rcedit,
|
|
227
|
-
buildNumber
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
const finalPath = path.join(outputPath, executableName);
|
|
231
|
-
diag.success(`Build complete: ${finalPath}`);
|
|
232
|
-
|
|
233
|
-
// Apply custom signing if configured
|
|
234
|
-
if (this.config.sign) {
|
|
235
|
-
await this.applyCustomSigning(finalPath, target, platform, arch, nodeVersion, buildNumber);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
return {
|
|
239
|
-
target,
|
|
240
|
-
path: finalPath
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Rebuild native modules for specific target
|
|
246
|
-
*/
|
|
247
|
-
async rebuildNativeModulesForTarget(nativeModules, target, buildNumber) {
|
|
248
|
-
if (nativeModules.size === 0) {
|
|
249
|
-
return [];
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
diag.buildStep(buildNumber, 1, `Rebuilding ${nativeModules.size} native module(s)...`);
|
|
253
|
-
|
|
254
|
-
const rebuiltAssets = [];
|
|
255
|
-
const { platform, arch } = parseTarget(target);
|
|
256
|
-
|
|
257
|
-
for (const [moduleKey, moduleInfo] of nativeModules) {
|
|
258
|
-
const moduleName = moduleInfo.moduleName || path.basename(moduleKey, '.node');
|
|
259
|
-
|
|
260
|
-
try {
|
|
261
|
-
// Skip rebuilding if this is already a prebuild for the target platform
|
|
262
|
-
if (moduleInfo.isPrebuild) {
|
|
263
|
-
diag.verbose(`Using prebuild: ${moduleName}`, 2);
|
|
264
|
-
|
|
265
|
-
rebuiltAssets.push({
|
|
266
|
-
sourcePath: moduleInfo.binaryPath,
|
|
267
|
-
assetKey: moduleInfo.assetKey, // Use original assetKey from bundler
|
|
268
|
-
isBinary: true,
|
|
269
|
-
hash: await this.computeHash(moduleInfo.binaryPath)
|
|
270
|
-
});
|
|
271
|
-
continue;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Check cache first
|
|
275
|
-
const cachedBuild = this.cache.getCachedNativeBuild(moduleInfo.packageRoot, target);
|
|
276
|
-
|
|
277
|
-
if (cachedBuild) {
|
|
278
|
-
diag.verbose(`Using cached build: ${moduleName}`, 2);
|
|
279
|
-
|
|
280
|
-
rebuiltAssets.push({
|
|
281
|
-
sourcePath: cachedBuild,
|
|
282
|
-
assetKey: moduleInfo.assetKey, // Use original assetKey from bundler
|
|
283
|
-
isBinary: true,
|
|
284
|
-
hash: await this.computeHash(cachedBuild)
|
|
285
|
-
});
|
|
286
|
-
continue;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// Rebuild the module
|
|
290
|
-
diag.verbose(`Rebuilding: ${moduleName}`, 2);
|
|
291
|
-
|
|
292
|
-
await this.rebuildNativeModule(moduleInfo.packageRoot, target);
|
|
293
|
-
|
|
294
|
-
// Find the built binary
|
|
295
|
-
const builtPath = await this.findBuiltBinary(moduleInfo, target);
|
|
296
|
-
|
|
297
|
-
if (builtPath) {
|
|
298
|
-
// Cache the build
|
|
299
|
-
this.cache.cacheNativeBuild(moduleInfo.packageRoot, target, builtPath);
|
|
300
|
-
|
|
301
|
-
rebuiltAssets.push({
|
|
302
|
-
sourcePath: builtPath,
|
|
303
|
-
assetKey: moduleInfo.assetKey, // Use original assetKey from bundler
|
|
304
|
-
isBinary: true,
|
|
305
|
-
hash: await this.computeHash(builtPath)
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
diag.verbose(`Built: ${moduleName} -> ${builtPath}`, 2);
|
|
309
|
-
}
|
|
310
|
-
} catch (err) {
|
|
311
|
-
diag.warn(`Failed to rebuild ${moduleName}: ${err.message}`, 2);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
diag.success('Native modules processed');
|
|
316
|
-
return rebuiltAssets;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* Rebuild a single native module
|
|
321
|
-
*/
|
|
322
|
-
async rebuildNativeModule(packageRoot, target) {
|
|
323
|
-
const rebuildScript = path.join(__dirname, '..', 'bin', 'seabox-rebuild.mjs');
|
|
324
|
-
|
|
325
|
-
if (!fs.existsSync(rebuildScript)) {
|
|
326
|
-
throw new Error('seabox-rebuild.mjs not found');
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
try {
|
|
330
|
-
execSync(`node "${rebuildScript}" --target ${target} "${packageRoot}"`, {
|
|
331
|
-
stdio: this.verbose ? 'inherit' : 'pipe',
|
|
332
|
-
cwd: this.projectRoot
|
|
333
|
-
});
|
|
334
|
-
} catch (err) {
|
|
335
|
-
throw new Error(`Rebuild failed: ${err.message}`);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/**
|
|
340
|
-
* Find the built .node binary
|
|
341
|
-
*/
|
|
342
|
-
async findBuiltBinary(moduleInfo, target) {
|
|
343
|
-
// Try common build locations
|
|
344
|
-
const searchPaths = [
|
|
345
|
-
path.join(moduleInfo.packageRoot, 'build/Release'),
|
|
346
|
-
path.join(moduleInfo.packageRoot, 'build/Debug'),
|
|
347
|
-
moduleInfo.buildPath
|
|
348
|
-
];
|
|
349
|
-
|
|
350
|
-
for (const searchPath of searchPaths) {
|
|
351
|
-
if (fs.existsSync(searchPath)) {
|
|
352
|
-
const files = fs.readdirSync(searchPath);
|
|
353
|
-
const nodeFile = files.find(f => f.endsWith('.node'));
|
|
354
|
-
|
|
355
|
-
if (nodeFile) {
|
|
356
|
-
return path.join(searchPath, nodeFile);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
return null;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* Collect platform-specific libraries (DLLs, SOs, DYLIBs) that need filesystem extraction
|
|
366
|
-
* Only includes libraries explicitly listed in config patterns - no automatic discovery.
|
|
367
|
-
* Libraries referenced in code (e.g., path.join(__dirname, './lib/foo.dll')) are
|
|
368
|
-
* already captured by the bundler's asset detection.
|
|
369
|
-
*
|
|
370
|
-
* @param {string[]} libraryPatterns - Explicit glob patterns for libraries from config
|
|
371
|
-
* @param {string} platform - Target platform
|
|
372
|
-
* @param {string} arch - Target architecture
|
|
373
|
-
* @param {number} buildNumber - Build number for display
|
|
374
|
-
*/
|
|
375
|
-
async collectPlatformLibraries(libraryPatterns, platform, arch, buildNumber) {
|
|
376
|
-
// Only process explicitly configured library patterns
|
|
377
|
-
// Do NOT use default patterns - this prevents automatic inclusion of unrelated DLLs
|
|
378
|
-
if (!libraryPatterns || libraryPatterns.length === 0) {
|
|
379
|
-
return [];
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
diag.buildStep(buildNumber, 4, `Collecting platform libraries (${platform})...`);
|
|
383
|
-
|
|
384
|
-
const { glob } = await import('glob');
|
|
385
|
-
const libraries = [];
|
|
386
|
-
|
|
387
|
-
for (const pattern of libraryPatterns) {
|
|
388
|
-
const matches = await glob(pattern, {
|
|
389
|
-
cwd: this.projectRoot,
|
|
390
|
-
nodir: true,
|
|
391
|
-
absolute: false,
|
|
392
|
-
ignore: [
|
|
393
|
-
'**/node_modules/**',
|
|
394
|
-
'**/.git/**',
|
|
395
|
-
'**/dist/**',
|
|
396
|
-
'**/build/**',
|
|
397
|
-
'**/out/**',
|
|
398
|
-
'**/bin/**',
|
|
399
|
-
'**/obj/**',
|
|
400
|
-
'**/tools/**',
|
|
401
|
-
'**/.seabox-cache/**'
|
|
402
|
-
]
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
for (const match of matches) {
|
|
406
|
-
const sourcePath = path.resolve(this.projectRoot, match);
|
|
407
|
-
|
|
408
|
-
libraries.push({
|
|
409
|
-
sourcePath,
|
|
410
|
-
assetKey: match.replace(/\\/g, '/'),
|
|
411
|
-
isBinary: true,
|
|
412
|
-
hash: await this.computeHash(sourcePath)
|
|
413
|
-
});
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
if (libraries.length > 0) {
|
|
418
|
-
diag.success(`Collected ${libraries.length} library file(s)`);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
return libraries;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
/**
|
|
425
|
-
* Collect assets from config globs
|
|
426
|
-
* @param {string[]} assetPatterns - Glob patterns for assets
|
|
427
|
-
* @param {number} buildNumber - Build number for display
|
|
428
|
-
*/
|
|
429
|
-
async collectConfigAssets(assetPatterns, buildNumber) {
|
|
430
|
-
if (!assetPatterns || assetPatterns.length === 0) {
|
|
431
|
-
return [];
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
diag.buildStep(buildNumber, 2, 'Collecting config assets...');
|
|
435
|
-
|
|
436
|
-
const { glob } = await import('glob');
|
|
437
|
-
const assets = [];
|
|
438
|
-
|
|
439
|
-
for (const pattern of assetPatterns) {
|
|
440
|
-
const matches = await glob(pattern, {
|
|
441
|
-
cwd: this.projectRoot,
|
|
442
|
-
nodir: true,
|
|
443
|
-
absolute: false,
|
|
444
|
-
ignore: [
|
|
445
|
-
'**/node_modules/**',
|
|
446
|
-
'**/.git/**',
|
|
447
|
-
'**/dist/**',
|
|
448
|
-
'**/build/**',
|
|
449
|
-
'**/out/**',
|
|
450
|
-
'**/bin/**',
|
|
451
|
-
'**/obj/**',
|
|
452
|
-
'**/tools/**',
|
|
453
|
-
'**/.seabox-cache/**'
|
|
454
|
-
]
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
for (const match of matches) {
|
|
458
|
-
const sourcePath = path.resolve(this.projectRoot, match);
|
|
459
|
-
|
|
460
|
-
assets.push({
|
|
461
|
-
sourcePath,
|
|
462
|
-
assetKey: match.replace(/\\/g, '/'),
|
|
463
|
-
isBinary: this.isBinaryFile(sourcePath),
|
|
464
|
-
hash: await this.computeHash(sourcePath)
|
|
465
|
-
});
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
if (assets.length > 0) {
|
|
470
|
-
diag.success(`Collected ${assets.length} config asset(s)`);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
return assets;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
/**
|
|
477
|
-
* Collect auto-detected assets from bundler (path.join(__dirname, ...))
|
|
478
|
-
* @param {Set<string>} detectedAssets - Asset paths detected during bundling
|
|
479
|
-
* @param {number} buildNumber - Build number for display
|
|
480
|
-
*/
|
|
481
|
-
async collectDetectedAssets(detectedAssets, buildNumber) {
|
|
482
|
-
if (!detectedAssets || detectedAssets.size === 0) {
|
|
483
|
-
return [];
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
diag.buildStep(buildNumber, 3, 'Processing auto-detected assets...');
|
|
487
|
-
|
|
488
|
-
const assets = [];
|
|
489
|
-
|
|
490
|
-
for (const assetKey of detectedAssets) {
|
|
491
|
-
const sourcePath = path.resolve(this.projectRoot, assetKey);
|
|
492
|
-
|
|
493
|
-
// Verify file still exists
|
|
494
|
-
if (fs.existsSync(sourcePath)) {
|
|
495
|
-
assets.push({
|
|
496
|
-
sourcePath,
|
|
497
|
-
assetKey: assetKey,
|
|
498
|
-
isBinary: this.isBinaryFile(sourcePath),
|
|
499
|
-
hash: await this.computeHash(sourcePath)
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
if (assets.length > 0) {
|
|
505
|
-
diag.success(`Collected ${assets.length} auto-detected asset(s)`);
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
return assets;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
/**
|
|
512
|
-
* Check if a file is binary based on extension
|
|
513
|
-
*/
|
|
514
|
-
isBinaryFile(filePath) {
|
|
515
|
-
const binaryExtensions = ['.node', '.dll', '.so', '.dylib', '.exe', '.png', '.jpg', '.jpeg', '.gif', '.ico', '.woff', '.woff2', '.ttf', '.eot'];
|
|
516
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
517
|
-
return binaryExtensions.includes(ext);
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
/**
|
|
521
|
-
* Prepare final entry with bootstrap
|
|
522
|
-
*/
|
|
523
|
-
async prepareFinalEntry(bundledEntryPath, buildNumber) {
|
|
524
|
-
diag.buildStep(buildNumber, 3, 'Preparing bootstrap...');
|
|
525
|
-
|
|
526
|
-
const bootstrapPath = path.join(__dirname, 'bootstrap.cjs');
|
|
527
|
-
const bootstrapContent = fs.readFileSync(bootstrapPath, 'utf8');
|
|
528
|
-
|
|
529
|
-
let augmentedBootstrap = bootstrapContent;
|
|
530
|
-
|
|
531
|
-
// Handle encryption if enabled
|
|
532
|
-
if (this.config.encryptAssets) {
|
|
533
|
-
const encryptionKey = generateEncryptionKey();
|
|
534
|
-
const keyCode = keyToObfuscatedCode(encryptionKey);
|
|
535
|
-
|
|
536
|
-
const encryptionSetup = `
|
|
537
|
-
const SEA_ENCRYPTION_KEY = ${keyCode};
|
|
538
|
-
const SEA_ENCRYPTED_ASSETS = new Set([]);
|
|
539
|
-
`;
|
|
540
|
-
|
|
541
|
-
augmentedBootstrap = bootstrapContent.replace(
|
|
542
|
-
" 'use strict';",
|
|
543
|
-
" 'use strict';\n" + encryptionSetup
|
|
544
|
-
);
|
|
545
|
-
|
|
546
|
-
diag.verbose('Encryption enabled and bootstrap obfuscated', 2);
|
|
547
|
-
|
|
548
|
-
augmentedBootstrap = obfuscateBootstrap(augmentedBootstrap);
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
// Read bundled entry
|
|
552
|
-
const entryContent = fs.readFileSync(bundledEntryPath, 'utf8');
|
|
553
|
-
|
|
554
|
-
// Bundle with bootstrap
|
|
555
|
-
const finalEntry = bundleEntry(entryContent, augmentedBootstrap, this.config.useSnapshot, this.verbose);
|
|
556
|
-
|
|
557
|
-
// Write final entry
|
|
558
|
-
const finalPath = bundledEntryPath.replace('.js', '-final.js');
|
|
559
|
-
fs.writeFileSync(finalPath, finalEntry, 'utf8');
|
|
560
|
-
|
|
561
|
-
diag.success('Bootstrap prepared');
|
|
562
|
-
return finalPath;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
/**
|
|
566
|
-
* Generate SEA for a specific target
|
|
567
|
-
*/
|
|
568
|
-
async generateSEAForTarget(options) {
|
|
569
|
-
const {
|
|
570
|
-
assets,
|
|
571
|
-
entryPath,
|
|
572
|
-
target,
|
|
573
|
-
outputPath: outputDir,
|
|
574
|
-
executableName,
|
|
575
|
-
platform,
|
|
576
|
-
arch,
|
|
577
|
-
nodeVersion,
|
|
578
|
-
rcedit,
|
|
579
|
-
buildNumber
|
|
580
|
-
} = options;
|
|
581
|
-
|
|
582
|
-
diag.buildStep(buildNumber, 4, 'Generating SEA blob...');
|
|
583
|
-
|
|
584
|
-
// Generate manifest
|
|
585
|
-
const manifest = generateManifest(
|
|
586
|
-
assets,
|
|
587
|
-
{
|
|
588
|
-
_packageName: this.config._packageName || 'app',
|
|
589
|
-
_packageVersion: this.config._packageVersion || '1.0.0',
|
|
590
|
-
cacheLocation: this.config.cacheLocation
|
|
591
|
-
},
|
|
592
|
-
platform,
|
|
593
|
-
arch
|
|
594
|
-
);
|
|
595
|
-
|
|
596
|
-
const manifestJson = serializeManifest(manifest);
|
|
597
|
-
const manifestAsset = {
|
|
598
|
-
sourcePath: null,
|
|
599
|
-
assetKey: 'sea-manifest.json',
|
|
600
|
-
isBinary: false,
|
|
601
|
-
content: Buffer.from(manifestJson, 'utf8')
|
|
602
|
-
};
|
|
603
|
-
|
|
604
|
-
const allAssets = [...assets, manifestAsset];
|
|
605
|
-
|
|
606
|
-
// Create SEA config
|
|
607
|
-
const tempDir = path.join(this.projectRoot, 'out', '.sea-temp', target);
|
|
608
|
-
fs.mkdirSync(tempDir, { recursive: true });
|
|
609
|
-
|
|
610
|
-
const blobOutputPath = path.join(tempDir, 'sea-blob.blob');
|
|
611
|
-
const seaConfig = createSeaConfig(entryPath, blobOutputPath, allAssets, this.config);
|
|
612
|
-
|
|
613
|
-
const seaConfigPath = path.join(tempDir, 'sea-config.json');
|
|
614
|
-
writeSeaConfigJson(seaConfig, seaConfigPath, allAssets, tempDir);
|
|
615
|
-
|
|
616
|
-
// Generate blob
|
|
617
|
-
await generateBlob(seaConfigPath, process.execPath);
|
|
618
|
-
diag.success('SEA blob generated');
|
|
619
|
-
|
|
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
|
-
// Inject blob
|
|
627
|
-
diag.buildStep(buildNumber, 6, 'Injecting blob into executable...');
|
|
628
|
-
const outputExe = path.join(outputDir, executableName);
|
|
629
|
-
await injectBlob(nodeBinary, blobOutputPath, outputExe, platform, this.verbose, rcedit);
|
|
630
|
-
|
|
631
|
-
// Cleanup
|
|
632
|
-
if (!this.verbose) {
|
|
633
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
const size = diag.formatSize(fs.statSync(outputExe).size);
|
|
637
|
-
diag.success(`Injected (${size})`);
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
/**
|
|
641
|
-
* Compute SHA-256 hash of a file
|
|
642
|
-
*/
|
|
643
|
-
async computeHash(filePath) {
|
|
644
|
-
return new Promise((resolve, reject) => {
|
|
645
|
-
const hash = crypto.createHash('sha256');
|
|
646
|
-
const stream = fs.createReadStream(filePath);
|
|
647
|
-
stream.on('data', chunk => hash.update(chunk));
|
|
648
|
-
stream.on('end', () => resolve(hash.digest('hex')));
|
|
649
|
-
stream.on('error', reject);
|
|
650
|
-
});
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
/**
|
|
654
|
-
* Apply custom signing script
|
|
655
|
-
* @param {string} exePath - Path to executable
|
|
656
|
-
* @param {string} target - Build target
|
|
657
|
-
* @param {string} platform - Platform (win32, linux, darwin)
|
|
658
|
-
* @param {string} arch - Architecture (x64, arm64)
|
|
659
|
-
* @param {string} nodeVersion - Node version
|
|
660
|
-
* @param {number} buildNumber - Build number
|
|
661
|
-
*/
|
|
662
|
-
async applyCustomSigning(exePath, target, platform, arch, nodeVersion, buildNumber) {
|
|
663
|
-
diag.buildStep(buildNumber, 7, 'Applying custom signing...');
|
|
664
|
-
|
|
665
|
-
try {
|
|
666
|
-
const signScriptPath = path.resolve(this.projectRoot, this.config.sign);
|
|
667
|
-
|
|
668
|
-
if (!fs.existsSync(signScriptPath)) {
|
|
669
|
-
throw new Error(`Signing script not found: ${signScriptPath}`);
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
// Dynamic import of the signing script - convert to file:// URL for Windows
|
|
673
|
-
const { pathToFileURL } = await import('url');
|
|
674
|
-
const signModuleURL = pathToFileURL(signScriptPath).href;
|
|
675
|
-
const signModule = await import(signModuleURL);
|
|
676
|
-
const signFunction = signModule.default;
|
|
677
|
-
|
|
678
|
-
if (typeof signFunction !== 'function') {
|
|
679
|
-
throw new Error('Signing script must export a default function');
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
// Call the signing function with config
|
|
683
|
-
await signFunction({
|
|
684
|
-
exePath: path.resolve(exePath),
|
|
685
|
-
target,
|
|
686
|
-
platform,
|
|
687
|
-
arch,
|
|
688
|
-
nodeVersion,
|
|
689
|
-
projectRoot: this.projectRoot
|
|
690
|
-
});
|
|
691
|
-
|
|
692
|
-
diag.success('Custom signing applied');
|
|
693
|
-
} catch (error) {
|
|
694
|
-
throw new Error(`Signing failed: ${error.message}`);
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* multi-target-builder.mjs
|
|
3
|
+
* Orchestrate parallel builds for multiple target platforms.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { execSync } from 'child_process';
|
|
9
|
+
import crypto from 'crypto';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
import Module from 'module';
|
|
12
|
+
|
|
13
|
+
import { bundleWithRollup } from './rolldown-bundler.mjs';
|
|
14
|
+
import { BuildCache } from './build-cache.mjs';
|
|
15
|
+
import { parseTarget } from './config.mjs';
|
|
16
|
+
import { generateManifest, serializeManifest } from './manifest.mjs';
|
|
17
|
+
import { createSeaConfig, writeSeaConfigJson, generateBlob } from './blob.mjs';
|
|
18
|
+
import { fetchNodeBinary } from './fetch-node.mjs';
|
|
19
|
+
import { injectBlob } from './inject.mjs';
|
|
20
|
+
import { generateEncryptionKey, encryptAssets, keyToObfuscatedCode } from './crypto-assets.mjs';
|
|
21
|
+
import { obfuscateBootstrap } from './obfuscate.mjs';
|
|
22
|
+
import { bundleEntry } from './entry-bundler.mjs';
|
|
23
|
+
import * as diag from './diagnostics.mjs';
|
|
24
|
+
|
|
25
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
26
|
+
const __dirname = path.dirname(__filename);
|
|
27
|
+
const require = Module.createRequire(import.meta.url);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Multi-target build orchestrator
|
|
31
|
+
*/
|
|
32
|
+
export class MultiTargetBuilder {
|
|
33
|
+
/**
|
|
34
|
+
* @param {import('./config.mjs').SeaboxConfigV2} config - Build configuration
|
|
35
|
+
* @param {string} projectRoot - Project root directory
|
|
36
|
+
*/
|
|
37
|
+
constructor(config, projectRoot = process.cwd()) {
|
|
38
|
+
this.config = config;
|
|
39
|
+
this.projectRoot = projectRoot;
|
|
40
|
+
this.cache = new BuildCache(path.join(projectRoot, 'node_modules', '.cache', '.seabox-cache'));
|
|
41
|
+
this.verbose = config.verbose || false;
|
|
42
|
+
|
|
43
|
+
// Set verbose mode for diagnostics
|
|
44
|
+
diag.setVerbose(this.verbose);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Build all configured targets
|
|
49
|
+
* @returns {Promise<Array<{target: string, path: string}>>}
|
|
50
|
+
*/
|
|
51
|
+
async buildAll() {
|
|
52
|
+
diag.header('Seabox Multi-Target Build');
|
|
53
|
+
|
|
54
|
+
// Step 1: Bundle entry once (platform-agnostic JavaScript)
|
|
55
|
+
const { bundledPath, nativeModules, detectedAssets } = await this.bundleEntry();
|
|
56
|
+
|
|
57
|
+
if (this.verbose && detectedAssets.size > 0) {
|
|
58
|
+
diag.separator();
|
|
59
|
+
diag.info('Auto-detected assets:');
|
|
60
|
+
for (const assetPath of detectedAssets) {
|
|
61
|
+
diag.listItem(assetPath, 1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (this.verbose && nativeModules.size > 0) {
|
|
66
|
+
diag.separator();
|
|
67
|
+
diag.info('Native modules detected:');
|
|
68
|
+
for (const [name, info] of nativeModules) {
|
|
69
|
+
diag.listItem(`${name}: ${info.packageRoot}`, 1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Step 2: Build all targets (can be parallelized)
|
|
74
|
+
diag.separator();
|
|
75
|
+
diag.info(`Building ${this.config.outputs.length} target(s)...`);
|
|
76
|
+
diag.separator();
|
|
77
|
+
|
|
78
|
+
const buildPromises = this.config.outputs.map((output, index) =>
|
|
79
|
+
this.buildTarget(output, bundledPath, nativeModules, detectedAssets, index + 1)
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const results = await Promise.all(buildPromises);
|
|
83
|
+
|
|
84
|
+
diag.summary('All builds completed successfully!');
|
|
85
|
+
|
|
86
|
+
return results;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Bundle the entry file with Rollup
|
|
91
|
+
* @returns {Promise<{bundledPath: string, nativeModules: Map}>}
|
|
92
|
+
*/
|
|
93
|
+
async bundleEntry() {
|
|
94
|
+
diag.step(1, 6, 'Bundling application with Rollup...');
|
|
95
|
+
|
|
96
|
+
const entryPath = path.resolve(this.projectRoot, this.config.entry);
|
|
97
|
+
|
|
98
|
+
if (!fs.existsSync(entryPath)) {
|
|
99
|
+
throw new Error(`Entry file not found: ${entryPath}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const bundledPath = path.join(this.projectRoot, 'out', '_sea-entry.js');
|
|
103
|
+
|
|
104
|
+
// Create output directory
|
|
105
|
+
fs.mkdirSync(path.dirname(bundledPath), { recursive: true });
|
|
106
|
+
|
|
107
|
+
const result = await bundleWithRollup(
|
|
108
|
+
entryPath,
|
|
109
|
+
bundledPath,
|
|
110
|
+
{ ...this.config, _projectRoot: this.projectRoot },
|
|
111
|
+
process.platform, // Use current platform for bundling (JavaScript is platform-agnostic)
|
|
112
|
+
process.arch,
|
|
113
|
+
this.verbose
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
diag.success(`Bundle created: ${bundledPath}`);
|
|
117
|
+
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Build a single target
|
|
125
|
+
* @param {import('./config.mjs').OutputTarget} outputConfig - Target configuration
|
|
126
|
+
* @param {string} bundledEntryPath - Path to bundled entry
|
|
127
|
+
* @param {Map} nativeModules - Detected native modules
|
|
128
|
+
* @param {Set<string>} detectedAssets - Auto-detected assets from bundler
|
|
129
|
+
* @param {number} buildNumber - Build number for display
|
|
130
|
+
* @returns {Promise<{target: string, path: string}>}
|
|
131
|
+
*/
|
|
132
|
+
async buildTarget(outputConfig, bundledEntryPath, nativeModules, detectedAssets, buildNumber) {
|
|
133
|
+
const { target, path: outputPath, output: executableName } = outputConfig;
|
|
134
|
+
const { nodeVersion, platform, arch } = parseTarget(target);
|
|
135
|
+
|
|
136
|
+
diag.subheader(`[Build ${buildNumber}] Target: ${target}`);
|
|
137
|
+
|
|
138
|
+
// Step 1: Rebuild native modules for this target
|
|
139
|
+
const rebuiltModules = await this.rebuildNativeModulesForTarget(
|
|
140
|
+
nativeModules,
|
|
141
|
+
target,
|
|
142
|
+
buildNumber
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// Step 2: Collect config assets (manual globs)
|
|
146
|
+
const configAssets = await this.collectConfigAssets(
|
|
147
|
+
this.config.assets || [],
|
|
148
|
+
buildNumber
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// Step 3: Collect auto-detected assets (from path.join(__dirname, ...))
|
|
152
|
+
const autoAssets = await this.collectDetectedAssets(
|
|
153
|
+
detectedAssets,
|
|
154
|
+
buildNumber
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
// Step 4: Collect platform-specific libraries (DLLs/SOs)
|
|
158
|
+
const platformLibraries = await this.collectPlatformLibraries(
|
|
159
|
+
outputConfig.libraries,
|
|
160
|
+
platform,
|
|
161
|
+
arch,
|
|
162
|
+
buildNumber
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
// Step 5: Prepare bundled entry with bootstrap
|
|
166
|
+
const finalEntryPath = await this.prepareFinalEntry(
|
|
167
|
+
bundledEntryPath,
|
|
168
|
+
buildNumber
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
// Step 6: Combine all assets (dedupe by assetKey)
|
|
172
|
+
const assetMap = new Map();
|
|
173
|
+
|
|
174
|
+
// Add in order of priority (later overwrites earlier)
|
|
175
|
+
for (const asset of [...rebuiltModules, ...configAssets, ...autoAssets, ...platformLibraries]) {
|
|
176
|
+
assetMap.set(asset.assetKey, asset);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const allAssets = Array.from(assetMap.values());
|
|
180
|
+
|
|
181
|
+
// Verbose logging: List all embedded assets
|
|
182
|
+
if (this.verbose) {
|
|
183
|
+
diag.separator();
|
|
184
|
+
diag.verbose(`Embedded Assets (${allAssets.length} total):`, 1);
|
|
185
|
+
|
|
186
|
+
const nativeAssets = allAssets.filter(a => a.assetKey.includes('.node'));
|
|
187
|
+
const libraryAssets = allAssets.filter(a => a.isBinary && !a.assetKey.includes('.node'));
|
|
188
|
+
const regularAssets = allAssets.filter(a => !a.isBinary);
|
|
189
|
+
|
|
190
|
+
if (nativeAssets.length > 0) {
|
|
191
|
+
diag.verbose(`Native Modules (${nativeAssets.length}):`, 1);
|
|
192
|
+
for (const asset of nativeAssets) {
|
|
193
|
+
const size = diag.formatSize(fs.statSync(asset.sourcePath).size);
|
|
194
|
+
const displayName = path.basename(asset.assetKey);
|
|
195
|
+
diag.verbose(`- ${displayName} (${size})`, 2);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (libraryAssets.length > 0) {
|
|
200
|
+
diag.verbose(`Platform Libraries (${libraryAssets.length}):`, 1);
|
|
201
|
+
for (const asset of libraryAssets) {
|
|
202
|
+
const size = diag.formatSize(fs.statSync(asset.sourcePath).size);
|
|
203
|
+
diag.verbose(`- ${asset.assetKey} (${size})`, 2);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (regularAssets.length > 0) {
|
|
208
|
+
diag.verbose(`Regular Assets (${regularAssets.length}):`, 1);
|
|
209
|
+
for (const asset of regularAssets) {
|
|
210
|
+
const size = diag.formatSize(fs.statSync(asset.sourcePath).size);
|
|
211
|
+
diag.verbose(`- ${asset.assetKey} (${size})`, 2);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Step 7: Generate SEA
|
|
217
|
+
await this.generateSEAForTarget({
|
|
218
|
+
assets: allAssets,
|
|
219
|
+
entryPath: finalEntryPath,
|
|
220
|
+
target,
|
|
221
|
+
outputPath,
|
|
222
|
+
executableName,
|
|
223
|
+
platform,
|
|
224
|
+
arch,
|
|
225
|
+
nodeVersion,
|
|
226
|
+
rcedit: outputConfig.rcedit,
|
|
227
|
+
buildNumber
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const finalPath = path.join(outputPath, executableName);
|
|
231
|
+
diag.success(`Build complete: ${finalPath}`);
|
|
232
|
+
|
|
233
|
+
// Apply custom signing if configured
|
|
234
|
+
if (this.config.sign) {
|
|
235
|
+
await this.applyCustomSigning(finalPath, target, platform, arch, nodeVersion, buildNumber);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
target,
|
|
240
|
+
path: finalPath
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Rebuild native modules for specific target
|
|
246
|
+
*/
|
|
247
|
+
async rebuildNativeModulesForTarget(nativeModules, target, buildNumber) {
|
|
248
|
+
if (nativeModules.size === 0) {
|
|
249
|
+
return [];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
diag.buildStep(buildNumber, 1, `Rebuilding ${nativeModules.size} native module(s)...`);
|
|
253
|
+
|
|
254
|
+
const rebuiltAssets = [];
|
|
255
|
+
const { platform, arch } = parseTarget(target);
|
|
256
|
+
|
|
257
|
+
for (const [moduleKey, moduleInfo] of nativeModules) {
|
|
258
|
+
const moduleName = moduleInfo.moduleName || path.basename(moduleKey, '.node');
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
// Skip rebuilding if this is already a prebuild for the target platform
|
|
262
|
+
if (moduleInfo.isPrebuild) {
|
|
263
|
+
diag.verbose(`Using prebuild: ${moduleName}`, 2);
|
|
264
|
+
|
|
265
|
+
rebuiltAssets.push({
|
|
266
|
+
sourcePath: moduleInfo.binaryPath,
|
|
267
|
+
assetKey: moduleInfo.assetKey, // Use original assetKey from bundler
|
|
268
|
+
isBinary: true,
|
|
269
|
+
hash: await this.computeHash(moduleInfo.binaryPath)
|
|
270
|
+
});
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Check cache first
|
|
275
|
+
const cachedBuild = this.cache.getCachedNativeBuild(moduleInfo.packageRoot, target);
|
|
276
|
+
|
|
277
|
+
if (cachedBuild) {
|
|
278
|
+
diag.verbose(`Using cached build: ${moduleName}`, 2);
|
|
279
|
+
|
|
280
|
+
rebuiltAssets.push({
|
|
281
|
+
sourcePath: cachedBuild,
|
|
282
|
+
assetKey: moduleInfo.assetKey, // Use original assetKey from bundler
|
|
283
|
+
isBinary: true,
|
|
284
|
+
hash: await this.computeHash(cachedBuild)
|
|
285
|
+
});
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Rebuild the module
|
|
290
|
+
diag.verbose(`Rebuilding: ${moduleName}`, 2);
|
|
291
|
+
|
|
292
|
+
await this.rebuildNativeModule(moduleInfo.packageRoot, target);
|
|
293
|
+
|
|
294
|
+
// Find the built binary
|
|
295
|
+
const builtPath = await this.findBuiltBinary(moduleInfo, target);
|
|
296
|
+
|
|
297
|
+
if (builtPath) {
|
|
298
|
+
// Cache the build
|
|
299
|
+
this.cache.cacheNativeBuild(moduleInfo.packageRoot, target, builtPath);
|
|
300
|
+
|
|
301
|
+
rebuiltAssets.push({
|
|
302
|
+
sourcePath: builtPath,
|
|
303
|
+
assetKey: moduleInfo.assetKey, // Use original assetKey from bundler
|
|
304
|
+
isBinary: true,
|
|
305
|
+
hash: await this.computeHash(builtPath)
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
diag.verbose(`Built: ${moduleName} -> ${builtPath}`, 2);
|
|
309
|
+
}
|
|
310
|
+
} catch (err) {
|
|
311
|
+
diag.warn(`Failed to rebuild ${moduleName}: ${err.message}`, 2);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
diag.success('Native modules processed');
|
|
316
|
+
return rebuiltAssets;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Rebuild a single native module
|
|
321
|
+
*/
|
|
322
|
+
async rebuildNativeModule(packageRoot, target) {
|
|
323
|
+
const rebuildScript = path.join(__dirname, '..', 'bin', 'seabox-rebuild.mjs');
|
|
324
|
+
|
|
325
|
+
if (!fs.existsSync(rebuildScript)) {
|
|
326
|
+
throw new Error('seabox-rebuild.mjs not found');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
try {
|
|
330
|
+
execSync(`node "${rebuildScript}" --target ${target} "${packageRoot}"`, {
|
|
331
|
+
stdio: this.verbose ? 'inherit' : 'pipe',
|
|
332
|
+
cwd: this.projectRoot
|
|
333
|
+
});
|
|
334
|
+
} catch (err) {
|
|
335
|
+
throw new Error(`Rebuild failed: ${err.message}`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Find the built .node binary
|
|
341
|
+
*/
|
|
342
|
+
async findBuiltBinary(moduleInfo, target) {
|
|
343
|
+
// Try common build locations
|
|
344
|
+
const searchPaths = [
|
|
345
|
+
path.join(moduleInfo.packageRoot, 'build/Release'),
|
|
346
|
+
path.join(moduleInfo.packageRoot, 'build/Debug'),
|
|
347
|
+
moduleInfo.buildPath
|
|
348
|
+
];
|
|
349
|
+
|
|
350
|
+
for (const searchPath of searchPaths) {
|
|
351
|
+
if (fs.existsSync(searchPath)) {
|
|
352
|
+
const files = fs.readdirSync(searchPath);
|
|
353
|
+
const nodeFile = files.find(f => f.endsWith('.node'));
|
|
354
|
+
|
|
355
|
+
if (nodeFile) {
|
|
356
|
+
return path.join(searchPath, nodeFile);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Collect platform-specific libraries (DLLs, SOs, DYLIBs) that need filesystem extraction
|
|
366
|
+
* Only includes libraries explicitly listed in config patterns - no automatic discovery.
|
|
367
|
+
* Libraries referenced in code (e.g., path.join(__dirname, './lib/foo.dll')) are
|
|
368
|
+
* already captured by the bundler's asset detection.
|
|
369
|
+
*
|
|
370
|
+
* @param {string[]} libraryPatterns - Explicit glob patterns for libraries from config
|
|
371
|
+
* @param {string} platform - Target platform
|
|
372
|
+
* @param {string} arch - Target architecture
|
|
373
|
+
* @param {number} buildNumber - Build number for display
|
|
374
|
+
*/
|
|
375
|
+
async collectPlatformLibraries(libraryPatterns, platform, arch, buildNumber) {
|
|
376
|
+
// Only process explicitly configured library patterns
|
|
377
|
+
// Do NOT use default patterns - this prevents automatic inclusion of unrelated DLLs
|
|
378
|
+
if (!libraryPatterns || libraryPatterns.length === 0) {
|
|
379
|
+
return [];
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
diag.buildStep(buildNumber, 4, `Collecting platform libraries (${platform})...`);
|
|
383
|
+
|
|
384
|
+
const { glob } = await import('glob');
|
|
385
|
+
const libraries = [];
|
|
386
|
+
|
|
387
|
+
for (const pattern of libraryPatterns) {
|
|
388
|
+
const matches = await glob(pattern, {
|
|
389
|
+
cwd: this.projectRoot,
|
|
390
|
+
nodir: true,
|
|
391
|
+
absolute: false,
|
|
392
|
+
ignore: [
|
|
393
|
+
'**/node_modules/**',
|
|
394
|
+
'**/.git/**',
|
|
395
|
+
'**/dist/**',
|
|
396
|
+
'**/build/**',
|
|
397
|
+
'**/out/**',
|
|
398
|
+
'**/bin/**',
|
|
399
|
+
'**/obj/**',
|
|
400
|
+
'**/tools/**',
|
|
401
|
+
'**/.seabox-cache/**'
|
|
402
|
+
]
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
for (const match of matches) {
|
|
406
|
+
const sourcePath = path.resolve(this.projectRoot, match);
|
|
407
|
+
|
|
408
|
+
libraries.push({
|
|
409
|
+
sourcePath,
|
|
410
|
+
assetKey: match.replace(/\\/g, '/'),
|
|
411
|
+
isBinary: true,
|
|
412
|
+
hash: await this.computeHash(sourcePath)
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (libraries.length > 0) {
|
|
418
|
+
diag.success(`Collected ${libraries.length} library file(s)`);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return libraries;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Collect assets from config globs
|
|
426
|
+
* @param {string[]} assetPatterns - Glob patterns for assets
|
|
427
|
+
* @param {number} buildNumber - Build number for display
|
|
428
|
+
*/
|
|
429
|
+
async collectConfigAssets(assetPatterns, buildNumber) {
|
|
430
|
+
if (!assetPatterns || assetPatterns.length === 0) {
|
|
431
|
+
return [];
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
diag.buildStep(buildNumber, 2, 'Collecting config assets...');
|
|
435
|
+
|
|
436
|
+
const { glob } = await import('glob');
|
|
437
|
+
const assets = [];
|
|
438
|
+
|
|
439
|
+
for (const pattern of assetPatterns) {
|
|
440
|
+
const matches = await glob(pattern, {
|
|
441
|
+
cwd: this.projectRoot,
|
|
442
|
+
nodir: true,
|
|
443
|
+
absolute: false,
|
|
444
|
+
ignore: [
|
|
445
|
+
'**/node_modules/**',
|
|
446
|
+
'**/.git/**',
|
|
447
|
+
'**/dist/**',
|
|
448
|
+
'**/build/**',
|
|
449
|
+
'**/out/**',
|
|
450
|
+
'**/bin/**',
|
|
451
|
+
'**/obj/**',
|
|
452
|
+
'**/tools/**',
|
|
453
|
+
'**/.seabox-cache/**'
|
|
454
|
+
]
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
for (const match of matches) {
|
|
458
|
+
const sourcePath = path.resolve(this.projectRoot, match);
|
|
459
|
+
|
|
460
|
+
assets.push({
|
|
461
|
+
sourcePath,
|
|
462
|
+
assetKey: match.replace(/\\/g, '/'),
|
|
463
|
+
isBinary: this.isBinaryFile(sourcePath),
|
|
464
|
+
hash: await this.computeHash(sourcePath)
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (assets.length > 0) {
|
|
470
|
+
diag.success(`Collected ${assets.length} config asset(s)`);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return assets;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Collect auto-detected assets from bundler (path.join(__dirname, ...))
|
|
478
|
+
* @param {Set<string>} detectedAssets - Asset paths detected during bundling
|
|
479
|
+
* @param {number} buildNumber - Build number for display
|
|
480
|
+
*/
|
|
481
|
+
async collectDetectedAssets(detectedAssets, buildNumber) {
|
|
482
|
+
if (!detectedAssets || detectedAssets.size === 0) {
|
|
483
|
+
return [];
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
diag.buildStep(buildNumber, 3, 'Processing auto-detected assets...');
|
|
487
|
+
|
|
488
|
+
const assets = [];
|
|
489
|
+
|
|
490
|
+
for (const assetKey of detectedAssets) {
|
|
491
|
+
const sourcePath = path.resolve(this.projectRoot, assetKey);
|
|
492
|
+
|
|
493
|
+
// Verify file still exists
|
|
494
|
+
if (fs.existsSync(sourcePath)) {
|
|
495
|
+
assets.push({
|
|
496
|
+
sourcePath,
|
|
497
|
+
assetKey: assetKey,
|
|
498
|
+
isBinary: this.isBinaryFile(sourcePath),
|
|
499
|
+
hash: await this.computeHash(sourcePath)
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (assets.length > 0) {
|
|
505
|
+
diag.success(`Collected ${assets.length} auto-detected asset(s)`);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
return assets;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Check if a file is binary based on extension
|
|
513
|
+
*/
|
|
514
|
+
isBinaryFile(filePath) {
|
|
515
|
+
const binaryExtensions = ['.node', '.dll', '.so', '.dylib', '.exe', '.png', '.jpg', '.jpeg', '.gif', '.ico', '.woff', '.woff2', '.ttf', '.eot'];
|
|
516
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
517
|
+
return binaryExtensions.includes(ext);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Prepare final entry with bootstrap
|
|
522
|
+
*/
|
|
523
|
+
async prepareFinalEntry(bundledEntryPath, buildNumber) {
|
|
524
|
+
diag.buildStep(buildNumber, 3, 'Preparing bootstrap...');
|
|
525
|
+
|
|
526
|
+
const bootstrapPath = path.join(__dirname, 'bootstrap.cjs');
|
|
527
|
+
const bootstrapContent = fs.readFileSync(bootstrapPath, 'utf8');
|
|
528
|
+
|
|
529
|
+
let augmentedBootstrap = bootstrapContent;
|
|
530
|
+
|
|
531
|
+
// Handle encryption if enabled
|
|
532
|
+
if (this.config.encryptAssets) {
|
|
533
|
+
const encryptionKey = generateEncryptionKey();
|
|
534
|
+
const keyCode = keyToObfuscatedCode(encryptionKey);
|
|
535
|
+
|
|
536
|
+
const encryptionSetup = `
|
|
537
|
+
const SEA_ENCRYPTION_KEY = ${keyCode};
|
|
538
|
+
const SEA_ENCRYPTED_ASSETS = new Set([]);
|
|
539
|
+
`;
|
|
540
|
+
|
|
541
|
+
augmentedBootstrap = bootstrapContent.replace(
|
|
542
|
+
" 'use strict';",
|
|
543
|
+
" 'use strict';\n" + encryptionSetup
|
|
544
|
+
);
|
|
545
|
+
|
|
546
|
+
diag.verbose('Encryption enabled and bootstrap obfuscated', 2);
|
|
547
|
+
|
|
548
|
+
augmentedBootstrap = obfuscateBootstrap(augmentedBootstrap);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Read bundled entry
|
|
552
|
+
const entryContent = fs.readFileSync(bundledEntryPath, 'utf8');
|
|
553
|
+
|
|
554
|
+
// Bundle with bootstrap
|
|
555
|
+
const finalEntry = bundleEntry(entryContent, augmentedBootstrap, this.config.useSnapshot, this.verbose);
|
|
556
|
+
|
|
557
|
+
// Write final entry
|
|
558
|
+
const finalPath = bundledEntryPath.replace('.js', '-final.js');
|
|
559
|
+
fs.writeFileSync(finalPath, finalEntry, 'utf8');
|
|
560
|
+
|
|
561
|
+
diag.success('Bootstrap prepared');
|
|
562
|
+
return finalPath;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Generate SEA for a specific target
|
|
567
|
+
*/
|
|
568
|
+
async generateSEAForTarget(options) {
|
|
569
|
+
const {
|
|
570
|
+
assets,
|
|
571
|
+
entryPath,
|
|
572
|
+
target,
|
|
573
|
+
outputPath: outputDir,
|
|
574
|
+
executableName,
|
|
575
|
+
platform,
|
|
576
|
+
arch,
|
|
577
|
+
nodeVersion,
|
|
578
|
+
rcedit,
|
|
579
|
+
buildNumber
|
|
580
|
+
} = options;
|
|
581
|
+
|
|
582
|
+
diag.buildStep(buildNumber, 4, 'Generating SEA blob...');
|
|
583
|
+
|
|
584
|
+
// Generate manifest
|
|
585
|
+
const manifest = generateManifest(
|
|
586
|
+
assets,
|
|
587
|
+
{
|
|
588
|
+
_packageName: this.config._packageName || 'app',
|
|
589
|
+
_packageVersion: this.config._packageVersion || '1.0.0',
|
|
590
|
+
cacheLocation: this.config.cacheLocation
|
|
591
|
+
},
|
|
592
|
+
platform,
|
|
593
|
+
arch
|
|
594
|
+
);
|
|
595
|
+
|
|
596
|
+
const manifestJson = serializeManifest(manifest);
|
|
597
|
+
const manifestAsset = {
|
|
598
|
+
sourcePath: null,
|
|
599
|
+
assetKey: 'sea-manifest.json',
|
|
600
|
+
isBinary: false,
|
|
601
|
+
content: Buffer.from(manifestJson, 'utf8')
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
const allAssets = [...assets, manifestAsset];
|
|
605
|
+
|
|
606
|
+
// Create SEA config
|
|
607
|
+
const tempDir = path.join(this.projectRoot, 'out', '.sea-temp', target);
|
|
608
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
609
|
+
|
|
610
|
+
const blobOutputPath = path.join(tempDir, 'sea-blob.blob');
|
|
611
|
+
const seaConfig = createSeaConfig(entryPath, blobOutputPath, allAssets, this.config);
|
|
612
|
+
|
|
613
|
+
const seaConfigPath = path.join(tempDir, 'sea-config.json');
|
|
614
|
+
writeSeaConfigJson(seaConfig, seaConfigPath, allAssets, tempDir);
|
|
615
|
+
|
|
616
|
+
// Generate blob
|
|
617
|
+
await generateBlob(seaConfigPath, process.execPath);
|
|
618
|
+
diag.success('SEA blob generated');
|
|
619
|
+
|
|
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
|
+
// Inject blob
|
|
627
|
+
diag.buildStep(buildNumber, 6, 'Injecting blob into executable...');
|
|
628
|
+
const outputExe = path.join(outputDir, executableName);
|
|
629
|
+
await injectBlob(nodeBinary, blobOutputPath, outputExe, platform, this.verbose, rcedit);
|
|
630
|
+
|
|
631
|
+
// Cleanup
|
|
632
|
+
if (!this.verbose) {
|
|
633
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const size = diag.formatSize(fs.statSync(outputExe).size);
|
|
637
|
+
diag.success(`Injected (${size})`);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Compute SHA-256 hash of a file
|
|
642
|
+
*/
|
|
643
|
+
async computeHash(filePath) {
|
|
644
|
+
return new Promise((resolve, reject) => {
|
|
645
|
+
const hash = crypto.createHash('sha256');
|
|
646
|
+
const stream = fs.createReadStream(filePath);
|
|
647
|
+
stream.on('data', chunk => hash.update(chunk));
|
|
648
|
+
stream.on('end', () => resolve(hash.digest('hex')));
|
|
649
|
+
stream.on('error', reject);
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Apply custom signing script
|
|
655
|
+
* @param {string} exePath - Path to executable
|
|
656
|
+
* @param {string} target - Build target
|
|
657
|
+
* @param {string} platform - Platform (win32, linux, darwin)
|
|
658
|
+
* @param {string} arch - Architecture (x64, arm64)
|
|
659
|
+
* @param {string} nodeVersion - Node version
|
|
660
|
+
* @param {number} buildNumber - Build number
|
|
661
|
+
*/
|
|
662
|
+
async applyCustomSigning(exePath, target, platform, arch, nodeVersion, buildNumber) {
|
|
663
|
+
diag.buildStep(buildNumber, 7, 'Applying custom signing...');
|
|
664
|
+
|
|
665
|
+
try {
|
|
666
|
+
const signScriptPath = path.resolve(this.projectRoot, this.config.sign);
|
|
667
|
+
|
|
668
|
+
if (!fs.existsSync(signScriptPath)) {
|
|
669
|
+
throw new Error(`Signing script not found: ${signScriptPath}`);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Dynamic import of the signing script - convert to file:// URL for Windows
|
|
673
|
+
const { pathToFileURL } = await import('url');
|
|
674
|
+
const signModuleURL = pathToFileURL(signScriptPath).href;
|
|
675
|
+
const signModule = await import(signModuleURL);
|
|
676
|
+
const signFunction = signModule.default;
|
|
677
|
+
|
|
678
|
+
if (typeof signFunction !== 'function') {
|
|
679
|
+
throw new Error('Signing script must export a default function');
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Call the signing function with config
|
|
683
|
+
await signFunction({
|
|
684
|
+
exePath: path.resolve(exePath),
|
|
685
|
+
target,
|
|
686
|
+
platform,
|
|
687
|
+
arch,
|
|
688
|
+
nodeVersion,
|
|
689
|
+
projectRoot: this.projectRoot
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
diag.success('Custom signing applied');
|
|
693
|
+
} catch (error) {
|
|
694
|
+
throw new Error(`Signing failed: ${error.message}`);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|