electrobun 0.0.19-beta.97 → 0.1.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 +1 -1
- package/dist/api/browser/webviewtag.ts +54 -2
- package/dist/api/bun/ElectrobunConfig.ts +171 -0
- package/dist/api/bun/core/BrowserWindow.ts +4 -0
- package/dist/api/bun/core/Tray.ts +14 -0
- package/dist/api/bun/core/Updater.ts +4 -3
- package/dist/api/bun/index.ts +2 -0
- package/dist/api/bun/proc/native.ts +107 -5
- package/dist/main.js +5 -4
- package/package.json +4 -2
- package/src/cli/index.ts +565 -148
- package/templates/hello-world/bun.lock +164 -2
- package/templates/hello-world/electrobun.config.ts +28 -0
- package/templates/hello-world/src/bun/index.ts +2 -2
- package/templates/hello-world/src/mainview/index.html +5 -6
- package/templates/hello-world/src/mainview/index.ts +1 -5
- package/templates/interactive-playground/README.md +26 -0
- package/templates/interactive-playground/assets/tray-icon.png +0 -0
- package/templates/interactive-playground/electrobun.config.ts +36 -0
- package/templates/interactive-playground/package-lock.json +36 -0
- package/templates/interactive-playground/package.json +15 -0
- package/templates/interactive-playground/src/bun/demos/files.ts +70 -0
- package/templates/interactive-playground/src/bun/demos/menus.ts +139 -0
- package/templates/interactive-playground/src/bun/demos/rpc.ts +83 -0
- package/templates/interactive-playground/src/bun/demos/system.ts +72 -0
- package/templates/interactive-playground/src/bun/demos/updates.ts +105 -0
- package/templates/interactive-playground/src/bun/demos/windows.ts +90 -0
- package/templates/interactive-playground/src/bun/index.ts +124 -0
- package/templates/interactive-playground/src/bun/types/rpc.ts +109 -0
- package/templates/interactive-playground/src/mainview/components/EventLog.ts +107 -0
- package/templates/interactive-playground/src/mainview/components/Sidebar.ts +65 -0
- package/templates/interactive-playground/src/mainview/components/Toast.ts +57 -0
- package/templates/interactive-playground/src/mainview/demos/FileDemo.ts +211 -0
- package/templates/interactive-playground/src/mainview/demos/MenuDemo.ts +102 -0
- package/templates/interactive-playground/src/mainview/demos/RPCDemo.ts +229 -0
- package/templates/interactive-playground/src/mainview/demos/TrayDemo.ts +132 -0
- package/templates/interactive-playground/src/mainview/demos/WebViewDemo.ts +411 -0
- package/templates/interactive-playground/src/mainview/demos/WindowDemo.ts +207 -0
- package/templates/interactive-playground/src/mainview/index.css +538 -0
- package/templates/interactive-playground/src/mainview/index.html +103 -0
- package/templates/interactive-playground/src/mainview/index.ts +238 -0
- package/templates/multitab-browser/README.md +34 -0
- package/templates/multitab-browser/bun.lock +224 -0
- package/templates/multitab-browser/electrobun.config.ts +32 -0
- package/templates/multitab-browser/package-lock.json +20 -0
- package/templates/multitab-browser/package.json +12 -0
- package/templates/multitab-browser/src/bun/index.ts +144 -0
- package/templates/multitab-browser/src/bun/tabManager.ts +200 -0
- package/templates/multitab-browser/src/bun/types/rpc.ts +78 -0
- package/templates/multitab-browser/src/mainview/index.css +487 -0
- package/templates/multitab-browser/src/mainview/index.html +94 -0
- package/templates/multitab-browser/src/mainview/index.ts +634 -0
- package/templates/photo-booth/README.md +108 -0
- package/templates/photo-booth/bun.lock +239 -0
- package/templates/photo-booth/electrobun.config.ts +28 -0
- package/templates/photo-booth/package.json +16 -0
- package/templates/photo-booth/src/bun/index.ts +92 -0
- package/templates/photo-booth/src/mainview/index.css +465 -0
- package/templates/photo-booth/src/mainview/index.html +124 -0
- package/templates/photo-booth/src/mainview/index.ts +499 -0
- package/tests/bun.lock +14 -0
- package/tests/electrobun.config.ts +45 -0
- package/tests/package-lock.json +36 -0
- package/tests/package.json +13 -0
- package/tests/src/bun/index.ts +100 -0
- package/tests/src/bun/test-runner.ts +508 -0
- package/tests/src/mainview/index.html +110 -0
- package/tests/src/mainview/index.ts +458 -0
- package/tests/src/mainview/styles/main.css +451 -0
- package/tests/src/testviews/tray-test.html +57 -0
- package/tests/src/testviews/webview-mask.html +114 -0
- package/tests/src/testviews/webview-navigation.html +36 -0
- package/tests/src/testviews/window-create.html +17 -0
- package/tests/src/testviews/window-events.html +29 -0
- package/tests/src/testviews/window-focus.html +37 -0
- package/tests/src/webviewtag/index.ts +11 -0
- package/templates/hello-world/electrobun.config +0 -18
package/src/cli/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { join, dirname, basename } from "path";
|
|
1
|
+
import { join, dirname, basename, relative } from "path";
|
|
2
2
|
import {
|
|
3
3
|
existsSync,
|
|
4
4
|
readFileSync,
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
copyFileSync,
|
|
16
16
|
} from "fs";
|
|
17
17
|
import { execSync } from "child_process";
|
|
18
|
+
import * as readline from "readline";
|
|
18
19
|
import tar from "tar";
|
|
19
20
|
import archiver from "archiver";
|
|
20
21
|
import { ZstdInit } from "@oneidentity/zstd-js/wasm";
|
|
@@ -29,8 +30,12 @@ const MAX_CHUNK_SIZE = 1024 * 2;
|
|
|
29
30
|
|
|
30
31
|
// this when run as an npm script this will be where the folder where package.json is.
|
|
31
32
|
const projectRoot = process.cwd();
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
|
|
34
|
+
// Find TypeScript ESM config file
|
|
35
|
+
function findConfigFile(): string | null {
|
|
36
|
+
const configFile = join(projectRoot, 'electrobun.config.ts');
|
|
37
|
+
return existsSync(configFile) ? configFile : null;
|
|
38
|
+
}
|
|
34
39
|
|
|
35
40
|
// Note: cli args can be called via npm bun /path/to/electorbun/binary arg1 arg2
|
|
36
41
|
const indexOfElectrobun = process.argv.findIndex((arg) =>
|
|
@@ -191,7 +196,8 @@ async function ensureCoreDependencies(targetOS?: 'macos' | 'win' | 'linux', targ
|
|
|
191
196
|
// Use Windows native tar.exe on Windows due to npm tar library issues
|
|
192
197
|
if (OS === 'win') {
|
|
193
198
|
console.log('Using Windows native tar.exe for reliable extraction...');
|
|
194
|
-
|
|
199
|
+
const relativeTempFile = relative(platformDistPath, tempFile);
|
|
200
|
+
execSync(`tar -xf "${relativeTempFile}"`, {
|
|
195
201
|
stdio: 'inherit',
|
|
196
202
|
cwd: platformDistPath
|
|
197
203
|
});
|
|
@@ -244,6 +250,9 @@ async function ensureCoreDependencies(targetOS?: 'macos' | 'win' | 'linux', targ
|
|
|
244
250
|
console.error('This suggests the tarball structure is different than expected');
|
|
245
251
|
}
|
|
246
252
|
|
|
253
|
+
// Note: We no longer need to remove or re-add signatures from downloaded binaries
|
|
254
|
+
// The CI-added adhoc signatures are actually required for macOS to run the binaries
|
|
255
|
+
|
|
247
256
|
// For development: if main.js doesn't exist in shared dist/, copy from platform-specific download as fallback
|
|
248
257
|
const sharedDistPath = join(ELECTROBUN_DEP_PATH, 'dist');
|
|
249
258
|
const extractedMainJs = join(platformDistPath, 'main.js');
|
|
@@ -297,53 +306,164 @@ async function ensureCEFDependencies(targetOS?: 'macos' | 'win' | 'linux', targe
|
|
|
297
306
|
const archName = platformArch;
|
|
298
307
|
const cefTarballUrl = `https://github.com/blackboardsh/electrobun/releases/download/${version}/electrobun-cef-${platformName}-${archName}.tar.gz`;
|
|
299
308
|
|
|
300
|
-
|
|
309
|
+
// Helper function to download with retry logic
|
|
310
|
+
async function downloadWithRetry(url: string, filePath: string, maxRetries = 3): Promise<void> {
|
|
311
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
312
|
+
try {
|
|
313
|
+
console.log(`Downloading CEF (attempt ${attempt}/${maxRetries}) from: ${url}`);
|
|
314
|
+
|
|
315
|
+
const response = await fetch(url);
|
|
316
|
+
if (!response.ok) {
|
|
317
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Get content length for progress tracking
|
|
321
|
+
const contentLength = response.headers.get('content-length');
|
|
322
|
+
const totalSize = contentLength ? parseInt(contentLength, 10) : 0;
|
|
323
|
+
|
|
324
|
+
// Create temp file with unique name to avoid conflicts
|
|
325
|
+
const fileStream = createWriteStream(filePath);
|
|
326
|
+
let downloadedSize = 0;
|
|
327
|
+
let lastReportedPercent = -1;
|
|
328
|
+
|
|
329
|
+
// Stream download with progress
|
|
330
|
+
if (response.body) {
|
|
331
|
+
const reader = response.body.getReader();
|
|
332
|
+
while (true) {
|
|
333
|
+
const { done, value } = await reader.read();
|
|
334
|
+
if (done) break;
|
|
335
|
+
|
|
336
|
+
const chunk = Buffer.from(value);
|
|
337
|
+
fileStream.write(chunk);
|
|
338
|
+
downloadedSize += chunk.length;
|
|
339
|
+
|
|
340
|
+
if (totalSize > 0) {
|
|
341
|
+
const percent = Math.round((downloadedSize / totalSize) * 100);
|
|
342
|
+
const percentTier = Math.floor(percent / 10) * 10;
|
|
343
|
+
if (percentTier > lastReportedPercent && percentTier <= 100) {
|
|
344
|
+
console.log(` Progress: ${percentTier}% (${Math.round(downloadedSize / 1024 / 1024)}MB/${Math.round(totalSize / 1024 / 1024)}MB)`);
|
|
345
|
+
lastReportedPercent = percentTier;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
await new Promise((resolve, reject) => {
|
|
352
|
+
fileStream.end((error: any) => {
|
|
353
|
+
if (error) reject(error);
|
|
354
|
+
else resolve(void 0);
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// Verify file size if content-length was provided
|
|
359
|
+
if (totalSize > 0) {
|
|
360
|
+
const actualSize = (await import('fs')).statSync(filePath).size;
|
|
361
|
+
if (actualSize !== totalSize) {
|
|
362
|
+
throw new Error(`Downloaded file size mismatch: expected ${totalSize}, got ${actualSize}`);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
console.log(`✓ Download completed successfully (${Math.round(downloadedSize / 1024 / 1024)}MB)`);
|
|
367
|
+
return; // Success, exit retry loop
|
|
368
|
+
|
|
369
|
+
} catch (error: any) {
|
|
370
|
+
console.error(`Download attempt ${attempt} failed:`, error.message);
|
|
371
|
+
|
|
372
|
+
// Clean up partial download
|
|
373
|
+
if (existsSync(filePath)) {
|
|
374
|
+
unlinkSync(filePath);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (attempt === maxRetries) {
|
|
378
|
+
throw new Error(`Failed to download after ${maxRetries} attempts: ${error.message}`);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Wait before retrying (exponential backoff)
|
|
382
|
+
const delay = Math.pow(2, attempt) * 1000; // 2s, 4s, 8s...
|
|
383
|
+
console.log(`Retrying in ${delay / 1000} seconds...`);
|
|
384
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
301
388
|
|
|
302
389
|
try {
|
|
303
|
-
//
|
|
304
|
-
const
|
|
305
|
-
if (!response.ok) {
|
|
306
|
-
throw new Error(`Failed to download CEF: ${response.status} ${response.statusText}`);
|
|
307
|
-
}
|
|
390
|
+
// Create temp file with unique name
|
|
391
|
+
const tempFile = join(ELECTROBUN_DEP_PATH, `cef-${platformOS}-${platformArch}-${Date.now()}.tar.gz`);
|
|
308
392
|
|
|
309
|
-
//
|
|
310
|
-
|
|
311
|
-
const fileStream = createWriteStream(tempFile);
|
|
312
|
-
|
|
313
|
-
// Write response to file
|
|
314
|
-
if (response.body) {
|
|
315
|
-
const reader = response.body.getReader();
|
|
316
|
-
while (true) {
|
|
317
|
-
const { done, value } = await reader.read();
|
|
318
|
-
if (done) break;
|
|
319
|
-
fileStream.write(Buffer.from(value));
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
fileStream.end();
|
|
393
|
+
// Download with retry logic
|
|
394
|
+
await downloadWithRetry(cefTarballUrl, tempFile);
|
|
323
395
|
|
|
324
396
|
// Extract to platform-specific dist directory
|
|
325
397
|
console.log(`Extracting CEF dependencies for ${platformOS}-${platformArch}...`);
|
|
326
398
|
const platformDistPath = join(ELECTROBUN_DEP_PATH, `dist-${platformOS}-${platformArch}`);
|
|
327
399
|
mkdirSync(platformDistPath, { recursive: true });
|
|
328
400
|
|
|
329
|
-
//
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
401
|
+
// Helper function to validate tar file before extraction
|
|
402
|
+
async function validateTarFile(filePath: string): Promise<void> {
|
|
403
|
+
try {
|
|
404
|
+
// Quick validation - try to read the tar file header
|
|
405
|
+
const fd = await import('fs').then(fs => fs.promises.readFile(filePath));
|
|
406
|
+
|
|
407
|
+
// Check if it's a gzip file (magic bytes: 1f 8b)
|
|
408
|
+
if (fd.length < 2 || fd[0] !== 0x1f || fd[1] !== 0x8b) {
|
|
409
|
+
throw new Error('Invalid gzip header - file may be corrupted');
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
console.log(`✓ Tar file validation passed (${Math.round(fd.length / 1024 / 1024)}MB)`);
|
|
413
|
+
} catch (error: any) {
|
|
414
|
+
throw new Error(`Tar file validation failed: ${error.message}`);
|
|
415
|
+
}
|
|
343
416
|
}
|
|
344
417
|
|
|
345
|
-
//
|
|
346
|
-
|
|
418
|
+
// Validate downloaded file before extraction
|
|
419
|
+
await validateTarFile(tempFile);
|
|
420
|
+
|
|
421
|
+
try {
|
|
422
|
+
// Use Windows native tar.exe on Windows due to npm tar library issues
|
|
423
|
+
if (OS === 'win') {
|
|
424
|
+
console.log('Using Windows native tar.exe for reliable extraction...');
|
|
425
|
+
const relativeTempFile = relative(platformDistPath, tempFile);
|
|
426
|
+
execSync(`tar -xf "${relativeTempFile}"`, {
|
|
427
|
+
stdio: 'inherit',
|
|
428
|
+
cwd: platformDistPath
|
|
429
|
+
});
|
|
430
|
+
} else {
|
|
431
|
+
await tar.x({
|
|
432
|
+
file: tempFile,
|
|
433
|
+
cwd: platformDistPath,
|
|
434
|
+
preservePaths: false,
|
|
435
|
+
strip: 0,
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
console.log(`✓ Extraction completed successfully`);
|
|
440
|
+
|
|
441
|
+
} catch (error: any) {
|
|
442
|
+
// Check if CEF directory was created despite the error (partial extraction)
|
|
443
|
+
const cefDir = join(platformDistPath, 'cef');
|
|
444
|
+
if (existsSync(cefDir)) {
|
|
445
|
+
const cefFiles = readdirSync(cefDir);
|
|
446
|
+
if (cefFiles.length > 0) {
|
|
447
|
+
console.warn(`⚠️ Extraction warning: ${error.message}`);
|
|
448
|
+
console.warn(` However, CEF files were extracted (${cefFiles.length} files found).`);
|
|
449
|
+
console.warn(` Proceeding with partial extraction - this usually works fine.`);
|
|
450
|
+
// Don't throw - continue with what we have
|
|
451
|
+
} else {
|
|
452
|
+
// No files extracted, this is a real failure
|
|
453
|
+
throw new Error(`Extraction failed (no files extracted): ${error.message}`);
|
|
454
|
+
}
|
|
455
|
+
} else {
|
|
456
|
+
// No CEF directory created, this is a real failure
|
|
457
|
+
throw new Error(`Extraction failed (no CEF directory created): ${error.message}`);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Clean up temp file only after successful extraction
|
|
462
|
+
try {
|
|
463
|
+
unlinkSync(tempFile);
|
|
464
|
+
} catch (cleanupError) {
|
|
465
|
+
console.warn('Could not clean up temp file:', cleanupError);
|
|
466
|
+
}
|
|
347
467
|
|
|
348
468
|
// Debug: List what was actually extracted for CEF
|
|
349
469
|
try {
|
|
@@ -360,11 +480,28 @@ async function ensureCEFDependencies(targetOS?: 'macos' | 'win' | 'linux', targe
|
|
|
360
480
|
console.error('Could not list CEF extracted files:', e);
|
|
361
481
|
}
|
|
362
482
|
|
|
363
|
-
console.log(
|
|
483
|
+
console.log(`✓ CEF dependencies for ${platformOS}-${platformArch} downloaded and cached successfully`);
|
|
364
484
|
|
|
365
485
|
} catch (error: any) {
|
|
366
486
|
console.error(`Failed to download CEF dependencies for ${platformOS}-${platformArch}:`, error.message);
|
|
367
|
-
|
|
487
|
+
|
|
488
|
+
// Provide helpful guidance based on the error
|
|
489
|
+
if (error.message.includes('corrupted download') || error.message.includes('zlib') || error.message.includes('unexpected end')) {
|
|
490
|
+
console.error('\n💡 This appears to be a download corruption issue. Suggestions:');
|
|
491
|
+
console.error(' • Check your internet connection stability');
|
|
492
|
+
console.error(' • Try running the command again (it will retry automatically)');
|
|
493
|
+
console.error(' • Clear the cache if the issue persists:');
|
|
494
|
+
console.error(` rm -rf "${ELECTROBUN_DEP_PATH}"`);
|
|
495
|
+
} else if (error.message.includes('HTTP 404') || error.message.includes('Not Found')) {
|
|
496
|
+
console.error('\n💡 The CEF release was not found. This could mean:');
|
|
497
|
+
console.error(' • The version specified doesn\'t have CEF binaries available');
|
|
498
|
+
console.error(' • You\'re using a development/unreleased version');
|
|
499
|
+
console.error(' • Try using a stable version instead');
|
|
500
|
+
} else {
|
|
501
|
+
console.error('\nPlease ensure you have an internet connection and the release exists.');
|
|
502
|
+
console.error(`If the problem persists, try clearing the cache: rm -rf "${ELECTROBUN_DEP_PATH}"`);
|
|
503
|
+
}
|
|
504
|
+
|
|
368
505
|
process.exit(1);
|
|
369
506
|
}
|
|
370
507
|
}
|
|
@@ -402,6 +539,9 @@ const defaultConfig = {
|
|
|
402
539
|
entitlements: {
|
|
403
540
|
// This entitlement is required for Electrobun apps with a hardened runtime (required for notarization) to run on macos
|
|
404
541
|
"com.apple.security.cs.allow-jit": true,
|
|
542
|
+
// Required for bun runtime to work with dynamic code execution and JIT compilation when signed
|
|
543
|
+
"com.apple.security.cs.allow-unsigned-executable-memory": true,
|
|
544
|
+
"com.apple.security.cs.disable-library-validation": true,
|
|
405
545
|
},
|
|
406
546
|
icons: "icon.iconset",
|
|
407
547
|
},
|
|
@@ -431,19 +571,21 @@ if (!command) {
|
|
|
431
571
|
process.exit(1);
|
|
432
572
|
}
|
|
433
573
|
|
|
434
|
-
|
|
574
|
+
// Main execution function
|
|
575
|
+
async function main() {
|
|
576
|
+
const config = await getConfig();
|
|
435
577
|
|
|
436
|
-
const envArg =
|
|
437
|
-
|
|
578
|
+
const envArg =
|
|
579
|
+
process.argv.find((arg) => arg.startsWith("--env="))?.split("=")[1] || "";
|
|
438
580
|
|
|
439
|
-
const targetsArg =
|
|
440
|
-
|
|
581
|
+
const targetsArg =
|
|
582
|
+
process.argv.find((arg) => arg.startsWith("--targets="))?.split("=")[1] || "";
|
|
441
583
|
|
|
442
|
-
const validEnvironments = ["dev", "canary", "stable"];
|
|
584
|
+
const validEnvironments = ["dev", "canary", "stable"];
|
|
443
585
|
|
|
444
|
-
// todo (yoav): dev, canary, and stable;
|
|
445
|
-
const buildEnvironment: "dev" | "canary" | "stable" =
|
|
446
|
-
|
|
586
|
+
// todo (yoav): dev, canary, and stable;
|
|
587
|
+
const buildEnvironment: "dev" | "canary" | "stable" =
|
|
588
|
+
validEnvironments.includes(envArg || "dev") ? (envArg || "dev") : "dev";
|
|
447
589
|
|
|
448
590
|
// Determine build targets
|
|
449
591
|
type BuildTarget = { os: 'macos' | 'win' | 'linux', arch: 'arm64' | 'x64' };
|
|
@@ -678,6 +820,13 @@ function escapePathForTerminal(filePath: string) {
|
|
|
678
820
|
|
|
679
821
|
return escapedPath;
|
|
680
822
|
}
|
|
823
|
+
|
|
824
|
+
function sanitizeVolumeNameForHdiutil(volumeName: string) {
|
|
825
|
+
// Remove or replace characters that cause issues with hdiutil volume mounting
|
|
826
|
+
// Parentheses and other special characters can cause "Operation not permitted" errors
|
|
827
|
+
return volumeName.replace(/[()]/g, '');
|
|
828
|
+
}
|
|
829
|
+
|
|
681
830
|
// MyApp
|
|
682
831
|
|
|
683
832
|
// const appName = config.app.name.replace(/\s/g, '-').toLowerCase();
|
|
@@ -696,57 +845,117 @@ const bundleFileName = targetOS === 'macos' ? `${appFileName}.app` : appFileName
|
|
|
696
845
|
let proc = null;
|
|
697
846
|
|
|
698
847
|
if (commandArg === "init") {
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
console.log(`🚀 Initializing Electrobun project: ${projectName}`);
|
|
703
|
-
|
|
704
|
-
// Validate template name
|
|
705
|
-
const availableTemplates = getTemplateNames();
|
|
706
|
-
if (!availableTemplates.includes(templateName)) {
|
|
707
|
-
console.error(`❌ Template "${templateName}" not found.`);
|
|
708
|
-
console.log(`Available templates: ${availableTemplates.join(", ")}`);
|
|
709
|
-
process.exit(1);
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
const template = getTemplate(templateName);
|
|
713
|
-
if (!template) {
|
|
714
|
-
console.error(`❌ Could not load template "${templateName}"`);
|
|
715
|
-
process.exit(1);
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
// Create project directory
|
|
719
|
-
const projectPath = join(process.cwd(), projectName);
|
|
720
|
-
if (existsSync(projectPath)) {
|
|
721
|
-
console.error(`❌ Directory "${projectName}" already exists.`);
|
|
722
|
-
process.exit(1);
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
mkdirSync(projectPath, { recursive: true });
|
|
726
|
-
|
|
727
|
-
// Extract template files
|
|
728
|
-
let fileCount = 0;
|
|
729
|
-
for (const [relativePath, content] of Object.entries(template.files)) {
|
|
730
|
-
const fullPath = join(projectPath, relativePath);
|
|
731
|
-
const dir = dirname(fullPath);
|
|
848
|
+
await (async () => {
|
|
849
|
+
const secondArg = process.argv[indexOfElectrobun + 2];
|
|
850
|
+
const availableTemplates = getTemplateNames();
|
|
732
851
|
|
|
733
|
-
|
|
734
|
-
|
|
852
|
+
let projectName: string;
|
|
853
|
+
let templateName: string;
|
|
735
854
|
|
|
736
|
-
//
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
855
|
+
// Check if --template= flag is used
|
|
856
|
+
const templateFlag = process.argv.find(arg => arg.startsWith("--template="));
|
|
857
|
+
if (templateFlag) {
|
|
858
|
+
// Traditional usage: electrobun init my-project --template=photo-booth
|
|
859
|
+
projectName = secondArg || "my-electrobun-app";
|
|
860
|
+
templateName = templateFlag.split("=")[1];
|
|
861
|
+
} else if (secondArg && availableTemplates.includes(secondArg)) {
|
|
862
|
+
// New intuitive usage: electrobun init photo-booth
|
|
863
|
+
projectName = secondArg; // Use template name as project name
|
|
864
|
+
templateName = secondArg;
|
|
865
|
+
} else {
|
|
866
|
+
// Interactive menu when no template specified
|
|
867
|
+
console.log("🚀 Welcome to Electrobun!");
|
|
868
|
+
console.log("");
|
|
869
|
+
console.log("Available templates:");
|
|
870
|
+
availableTemplates.forEach((template, index) => {
|
|
871
|
+
console.log(` ${index + 1}. ${template}`);
|
|
872
|
+
});
|
|
873
|
+
console.log("");
|
|
874
|
+
|
|
875
|
+
// Simple CLI selection using readline
|
|
876
|
+
const rl = readline.createInterface({
|
|
877
|
+
input: process.stdin,
|
|
878
|
+
output: process.stdout
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
const choice = await new Promise<string>((resolve) => {
|
|
882
|
+
rl.question('Select a template (enter number): ', (answer) => {
|
|
883
|
+
rl.close();
|
|
884
|
+
resolve(answer.trim());
|
|
885
|
+
});
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
const templateIndex = parseInt(choice) - 1;
|
|
889
|
+
if (templateIndex < 0 || templateIndex >= availableTemplates.length) {
|
|
890
|
+
console.error(`❌ Invalid selection. Please enter a number between 1 and ${availableTemplates.length}.`);
|
|
891
|
+
process.exit(1);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
templateName = availableTemplates[templateIndex];
|
|
895
|
+
|
|
896
|
+
// Ask for project name
|
|
897
|
+
const rl2 = readline.createInterface({
|
|
898
|
+
input: process.stdin,
|
|
899
|
+
output: process.stdout
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
projectName = await new Promise<string>((resolve) => {
|
|
903
|
+
rl2.question(`Enter project name (default: my-${templateName}-app): `, (answer) => {
|
|
904
|
+
rl2.close();
|
|
905
|
+
resolve(answer.trim() || `my-${templateName}-app`);
|
|
906
|
+
});
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
console.log(`🚀 Initializing Electrobun project: ${projectName}`);
|
|
911
|
+
console.log(`📋 Using template: ${templateName}`);
|
|
912
|
+
|
|
913
|
+
// Validate template name
|
|
914
|
+
if (!availableTemplates.includes(templateName)) {
|
|
915
|
+
console.error(`❌ Template "${templateName}" not found.`);
|
|
916
|
+
console.log(`Available templates: ${availableTemplates.join(", ")}`);
|
|
917
|
+
process.exit(1);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
const template = getTemplate(templateName);
|
|
921
|
+
if (!template) {
|
|
922
|
+
console.error(`❌ Could not load template "${templateName}"`);
|
|
923
|
+
process.exit(1);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// Create project directory
|
|
927
|
+
const projectPath = join(process.cwd(), projectName);
|
|
928
|
+
if (existsSync(projectPath)) {
|
|
929
|
+
console.error(`❌ Directory "${projectName}" already exists.`);
|
|
930
|
+
process.exit(1);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
mkdirSync(projectPath, { recursive: true });
|
|
934
|
+
|
|
935
|
+
// Extract template files
|
|
936
|
+
let fileCount = 0;
|
|
937
|
+
for (const [relativePath, content] of Object.entries(template.files)) {
|
|
938
|
+
const fullPath = join(projectPath, relativePath);
|
|
939
|
+
const dir = dirname(fullPath);
|
|
940
|
+
|
|
941
|
+
// Create directory if it doesn't exist
|
|
942
|
+
mkdirSync(dir, { recursive: true });
|
|
943
|
+
|
|
944
|
+
// Write file
|
|
945
|
+
writeFileSync(fullPath, content, 'utf-8');
|
|
946
|
+
fileCount++;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
console.log(`✅ Created ${fileCount} files from "${templateName}" template`);
|
|
950
|
+
console.log(`📁 Project created at: ${projectPath}`);
|
|
951
|
+
console.log("");
|
|
952
|
+
console.log("📦 Next steps:");
|
|
953
|
+
console.log(` cd ${projectName}`);
|
|
954
|
+
console.log(" bun install");
|
|
955
|
+
console.log(" bun start");
|
|
956
|
+
console.log("");
|
|
957
|
+
console.log("🎉 Happy building with Electrobun!");
|
|
958
|
+
})();
|
|
750
959
|
} else if (commandArg === "build") {
|
|
751
960
|
// Ensure core binaries are available for the target platform before starting build
|
|
752
961
|
await ensureCoreDependencies(currentTarget.os, currentTarget.arch);
|
|
@@ -847,23 +1056,21 @@ if (commandArg === "init") {
|
|
|
847
1056
|
// mkdirSync(destLauncherFolder, {recursive: true});
|
|
848
1057
|
// }
|
|
849
1058
|
// cpSync(zigLauncherBinarySource, zigLauncherDestination, {recursive: true, dereference: true});
|
|
850
|
-
//
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
mkdirSync(destLauncherFolder, { recursive: true });
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
cpSync(bunCliLauncherBinarySource, bunCliLauncherDestination, {
|
|
861
|
-
recursive: true,
|
|
862
|
-
dereference: true,
|
|
863
|
-
});
|
|
1059
|
+
// Copy zig launcher for all builds (dev, canary, stable)
|
|
1060
|
+
const bunCliLauncherBinarySource = targetPaths.LAUNCHER_RELEASE;
|
|
1061
|
+
const bunCliLauncherDestination = join(appBundleMacOSPath, "launcher") + targetBinExt;
|
|
1062
|
+
const destLauncherFolder = dirname(bunCliLauncherDestination);
|
|
1063
|
+
if (!existsSync(destLauncherFolder)) {
|
|
1064
|
+
// console.info('creating folder: ', destFolder);
|
|
1065
|
+
mkdirSync(destLauncherFolder, { recursive: true });
|
|
864
1066
|
}
|
|
865
1067
|
|
|
866
|
-
cpSync(
|
|
1068
|
+
cpSync(bunCliLauncherBinarySource, bunCliLauncherDestination, {
|
|
1069
|
+
recursive: true,
|
|
1070
|
+
dereference: true,
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1073
|
+
cpSync(targetPaths.MAIN_JS, join(appBundleFolderResourcesPath, 'main.js'));
|
|
867
1074
|
|
|
868
1075
|
// Bun runtime binary
|
|
869
1076
|
// todo (yoav): this only works for the current architecture
|
|
@@ -1271,11 +1478,13 @@ if (commandArg === "init") {
|
|
|
1271
1478
|
|
|
1272
1479
|
// Run postBuild script
|
|
1273
1480
|
if (config.scripts.postBuild) {
|
|
1481
|
+
console.log("Running postBuild script:", config.scripts.postBuild);
|
|
1274
1482
|
// Use host platform's bun binary for running scripts, not target platform's
|
|
1275
1483
|
const hostPaths = getPlatformPaths(OS, ARCH);
|
|
1276
1484
|
|
|
1277
|
-
Bun.spawnSync([hostPaths.BUN_BINARY, config.scripts.postBuild], {
|
|
1485
|
+
const result = Bun.spawnSync([hostPaths.BUN_BINARY, config.scripts.postBuild], {
|
|
1278
1486
|
stdio: ["ignore", "inherit", "inherit"],
|
|
1487
|
+
cwd: projectRoot, // Add cwd to ensure script runs from project root
|
|
1279
1488
|
env: {
|
|
1280
1489
|
...process.env,
|
|
1281
1490
|
ELECTROBUN_BUILD_ENV: buildEnvironment,
|
|
@@ -1288,6 +1497,18 @@ if (commandArg === "init") {
|
|
|
1288
1497
|
ELECTROBUN_ARTIFACT_DIR: artifactFolder,
|
|
1289
1498
|
},
|
|
1290
1499
|
});
|
|
1500
|
+
|
|
1501
|
+
if (result.exitCode !== 0) {
|
|
1502
|
+
console.error("postBuild script failed with exit code:", result.exitCode);
|
|
1503
|
+
if (result.stderr) {
|
|
1504
|
+
console.error("stderr:", result.stderr.toString());
|
|
1505
|
+
}
|
|
1506
|
+
// Also log which bun binary we're trying to use
|
|
1507
|
+
console.error("Tried to run with bun at:", hostPaths.BUN_BINARY);
|
|
1508
|
+
console.error("Script path:", config.scripts.postBuild);
|
|
1509
|
+
console.error("Working directory:", projectRoot);
|
|
1510
|
+
process.exit(1);
|
|
1511
|
+
}
|
|
1291
1512
|
}
|
|
1292
1513
|
// All the unique files are in the bundle now. Create an initial temporary tar file
|
|
1293
1514
|
// for hashing the contents
|
|
@@ -1476,7 +1697,7 @@ if (commandArg === "init") {
|
|
|
1476
1697
|
// hdiutil create -volname "YourAppName" -srcfolder /path/to/YourApp.app -ov -format UDZO YourAppName.dmg
|
|
1477
1698
|
// Note: use ULFO (lzfse) for better compatibility with large CEF frameworks and modern macOS
|
|
1478
1699
|
execSync(
|
|
1479
|
-
`hdiutil create -volname "${appFileName}" -srcfolder ${escapePathForTerminal(
|
|
1700
|
+
`hdiutil create -volname "${sanitizeVolumeNameForHdiutil(appFileName)}" -srcfolder ${escapePathForTerminal(
|
|
1480
1701
|
selfExtractingBundle.appBundleFolderPath
|
|
1481
1702
|
)} -ov -format ULFO ${escapePathForTerminal(dmgPath)}`
|
|
1482
1703
|
);
|
|
@@ -1741,24 +1962,27 @@ exec "\$LAUNCHER_BINARY" "\$@"
|
|
|
1741
1962
|
|
|
1742
1963
|
let mainProc;
|
|
1743
1964
|
let bundleExecPath: string;
|
|
1965
|
+
let bundleResourcesPath: string;
|
|
1744
1966
|
|
|
1745
1967
|
if (OS === 'macos') {
|
|
1746
1968
|
bundleExecPath = join(buildFolder, bundleFileName, "Contents", 'MacOS');
|
|
1969
|
+
bundleResourcesPath = join(buildFolder, bundleFileName, "Contents", 'Resources');
|
|
1747
1970
|
} else if (OS === 'linux' || OS === 'win') {
|
|
1748
1971
|
bundleExecPath = join(buildFolder, bundleFileName, "bin");
|
|
1972
|
+
bundleResourcesPath = join(buildFolder, bundleFileName, "Resources");
|
|
1749
1973
|
} else {
|
|
1750
1974
|
throw new Error(`Unsupported OS: ${OS}`);
|
|
1751
1975
|
}
|
|
1752
1976
|
|
|
1753
1977
|
if (OS === 'macos') {
|
|
1754
|
-
|
|
1755
|
-
mainProc = Bun.spawn([join(bundleExecPath,
|
|
1978
|
+
// Use the zig launcher for all builds (dev, canary, stable)
|
|
1979
|
+
mainProc = Bun.spawn([join(bundleExecPath, 'launcher')], {
|
|
1756
1980
|
stdio: ['inherit', 'inherit', 'inherit'],
|
|
1757
1981
|
cwd: bundleExecPath
|
|
1758
1982
|
})
|
|
1759
1983
|
} else if (OS === 'win') {
|
|
1760
|
-
// Try the main process
|
|
1761
|
-
mainProc = Bun.spawn(['./bun.exe', '
|
|
1984
|
+
// Try the main process - use relative path to Resources folder
|
|
1985
|
+
mainProc = Bun.spawn(['./bun.exe', '../Resources/main.js'], {
|
|
1762
1986
|
stdio: ['inherit', 'inherit', 'inherit'],
|
|
1763
1987
|
cwd: bundleExecPath,
|
|
1764
1988
|
onExit: (proc, exitCode, signalCode, error) => {
|
|
@@ -1779,7 +2003,7 @@ exec "\$LAUNCHER_BINARY" "\$@"
|
|
|
1779
2003
|
}
|
|
1780
2004
|
}
|
|
1781
2005
|
|
|
1782
|
-
mainProc = Bun.spawn([join(bundleExecPath, 'bun'), join(
|
|
2006
|
+
mainProc = Bun.spawn([join(bundleExecPath, 'bun'), join(bundleResourcesPath, 'main.js')], {
|
|
1783
2007
|
stdio: ['inherit', 'inherit', 'inherit'],
|
|
1784
2008
|
cwd: bundleExecPath,
|
|
1785
2009
|
env
|
|
@@ -1787,23 +2011,49 @@ exec "\$LAUNCHER_BINARY" "\$@"
|
|
|
1787
2011
|
}
|
|
1788
2012
|
|
|
1789
2013
|
process.on("SIGINT", () => {
|
|
1790
|
-
console.log('
|
|
1791
|
-
|
|
1792
|
-
mainProc
|
|
1793
|
-
|
|
2014
|
+
console.log('[electrobun dev] Received SIGINT, initiating graceful shutdown...')
|
|
2015
|
+
|
|
2016
|
+
if (mainProc) {
|
|
2017
|
+
// First attempt graceful shutdown by sending SIGINT to child
|
|
2018
|
+
console.log('[electrobun dev] Requesting graceful shutdown from app...')
|
|
2019
|
+
mainProc.kill("SIGINT");
|
|
2020
|
+
|
|
2021
|
+
// Give the app time to clean up (e.g., call killApp())
|
|
2022
|
+
setTimeout(() => {
|
|
2023
|
+
if (mainProc && !mainProc.killed) {
|
|
2024
|
+
console.log('[electrobun dev] App did not exit gracefully, forcing termination...')
|
|
2025
|
+
mainProc.kill("SIGKILL");
|
|
2026
|
+
}
|
|
2027
|
+
process.exit(0);
|
|
2028
|
+
}, 2000); // 2 second timeout for graceful shutdown
|
|
2029
|
+
} else {
|
|
2030
|
+
process.exit(0);
|
|
2031
|
+
}
|
|
1794
2032
|
});
|
|
1795
2033
|
|
|
1796
2034
|
}
|
|
1797
2035
|
|
|
1798
|
-
function getConfig() {
|
|
2036
|
+
async function getConfig() {
|
|
1799
2037
|
let loadedConfig = {};
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
2038
|
+
const foundConfigPath = findConfigFile();
|
|
2039
|
+
|
|
2040
|
+
if (foundConfigPath) {
|
|
2041
|
+
console.log(`Using config file: ${basename(foundConfigPath)}`);
|
|
2042
|
+
|
|
1803
2043
|
try {
|
|
1804
|
-
|
|
2044
|
+
// Use dynamic import for TypeScript ESM files
|
|
2045
|
+
// Bun handles TypeScript natively, no transpilation needed
|
|
2046
|
+
const configModule = await import(foundConfigPath);
|
|
2047
|
+
loadedConfig = configModule.default || configModule;
|
|
2048
|
+
|
|
2049
|
+
// Validate that we got a valid config object
|
|
2050
|
+
if (!loadedConfig || typeof loadedConfig !== 'object') {
|
|
2051
|
+
console.error("Config file must export a default object");
|
|
2052
|
+
console.error("using default config instead");
|
|
2053
|
+
loadedConfig = {};
|
|
2054
|
+
}
|
|
1805
2055
|
} catch (error) {
|
|
1806
|
-
console.error("Failed to
|
|
2056
|
+
console.error("Failed to load config file:", error);
|
|
1807
2057
|
console.error("using default config instead");
|
|
1808
2058
|
}
|
|
1809
2059
|
}
|
|
@@ -2187,30 +2437,189 @@ function codesignAppBundle(
|
|
|
2187
2437
|
process.exit(1);
|
|
2188
2438
|
}
|
|
2189
2439
|
|
|
2190
|
-
//
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2440
|
+
// If this is a DMG file, sign it directly
|
|
2441
|
+
if (appBundleOrDmgPath.endsWith('.dmg')) {
|
|
2442
|
+
execSync(
|
|
2443
|
+
`codesign --force --verbose --timestamp --sign "${ELECTROBUN_DEVELOPER_ID}" ${escapePathForTerminal(
|
|
2444
|
+
appBundleOrDmgPath
|
|
2445
|
+
)}`
|
|
2446
|
+
);
|
|
2447
|
+
return;
|
|
2448
|
+
}
|
|
2195
2449
|
|
|
2450
|
+
// For app bundles, sign binaries individually to avoid --deep issues with notarization
|
|
2451
|
+
const contentsPath = join(appBundleOrDmgPath, 'Contents');
|
|
2452
|
+
const macosPath = join(contentsPath, 'MacOS');
|
|
2453
|
+
|
|
2454
|
+
// Prepare entitlements if provided
|
|
2196
2455
|
if (entitlementsFilePath) {
|
|
2197
2456
|
const entitlementsFileContents = buildEntitlementsFile(
|
|
2198
2457
|
config.build.mac.entitlements
|
|
2199
2458
|
);
|
|
2200
2459
|
Bun.write(entitlementsFilePath, entitlementsFileContents);
|
|
2460
|
+
}
|
|
2201
2461
|
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2462
|
+
// Sign frameworks first (CEF framework requires special handling)
|
|
2463
|
+
const frameworksPath = join(contentsPath, 'Frameworks');
|
|
2464
|
+
if (existsSync(frameworksPath)) {
|
|
2465
|
+
try {
|
|
2466
|
+
const frameworks = readdirSync(frameworksPath);
|
|
2467
|
+
for (const framework of frameworks) {
|
|
2468
|
+
if (framework.endsWith('.framework')) {
|
|
2469
|
+
const frameworkPath = join(frameworksPath, framework);
|
|
2470
|
+
|
|
2471
|
+
if (framework === 'Chromium Embedded Framework.framework') {
|
|
2472
|
+
console.log(`Signing CEF framework components: ${framework}`);
|
|
2473
|
+
|
|
2474
|
+
// Sign CEF libraries first
|
|
2475
|
+
const librariesPath = join(frameworkPath, 'Libraries');
|
|
2476
|
+
if (existsSync(librariesPath)) {
|
|
2477
|
+
const libraries = readdirSync(librariesPath);
|
|
2478
|
+
for (const library of libraries) {
|
|
2479
|
+
if (library.endsWith('.dylib')) {
|
|
2480
|
+
const libraryPath = join(librariesPath, library);
|
|
2481
|
+
console.log(`Signing CEF library: ${library}`);
|
|
2482
|
+
execSync(
|
|
2483
|
+
`codesign --force --verbose --timestamp --sign "${ELECTROBUN_DEVELOPER_ID}" --options runtime ${escapePathForTerminal(libraryPath)}`
|
|
2484
|
+
);
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2489
|
+
// CEF helper apps are in the main Frameworks directory, not inside the CEF framework
|
|
2490
|
+
// We'll sign them after signing all frameworks
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
// Sign the framework bundle itself (for CEF and any other frameworks)
|
|
2494
|
+
console.log(`Signing framework bundle: ${framework}`);
|
|
2495
|
+
execSync(
|
|
2496
|
+
`codesign --force --verbose --timestamp --sign "${ELECTROBUN_DEVELOPER_ID}" --options runtime ${escapePathForTerminal(frameworkPath)}`
|
|
2497
|
+
);
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
} catch (err) {
|
|
2501
|
+
console.log("Error signing frameworks:", err);
|
|
2502
|
+
throw err; // Re-throw to fail the build since framework signing is critical
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
// Sign CEF helper apps (they're in the main Frameworks directory, not inside CEF framework)
|
|
2507
|
+
const cefHelperApps = [
|
|
2508
|
+
'bun Helper.app',
|
|
2509
|
+
'bun Helper (GPU).app',
|
|
2510
|
+
'bun Helper (Plugin).app',
|
|
2511
|
+
'bun Helper (Alerts).app',
|
|
2512
|
+
'bun Helper (Renderer).app'
|
|
2513
|
+
];
|
|
2514
|
+
|
|
2515
|
+
for (const helperApp of cefHelperApps) {
|
|
2516
|
+
const helperPath = join(frameworksPath, helperApp);
|
|
2517
|
+
if (existsSync(helperPath)) {
|
|
2518
|
+
const helperExecutablePath = join(helperPath, 'Contents', 'MacOS', helperApp.replace('.app', ''));
|
|
2519
|
+
if (existsSync(helperExecutablePath)) {
|
|
2520
|
+
console.log(`Signing CEF helper executable: ${helperApp}`);
|
|
2521
|
+
const entitlementFlag = entitlementsFilePath ? `--entitlements ${entitlementsFilePath}` : '';
|
|
2522
|
+
execSync(
|
|
2523
|
+
`codesign --force --verbose --timestamp --sign "${ELECTROBUN_DEVELOPER_ID}" --options runtime ${entitlementFlag} ${escapePathForTerminal(helperExecutablePath)}`
|
|
2524
|
+
);
|
|
2525
|
+
}
|
|
2526
|
+
|
|
2527
|
+
console.log(`Signing CEF helper bundle: ${helperApp}`);
|
|
2528
|
+
const entitlementFlag = entitlementsFilePath ? `--entitlements ${entitlementsFilePath}` : '';
|
|
2529
|
+
execSync(
|
|
2530
|
+
`codesign --force --verbose --timestamp --sign "${ELECTROBUN_DEVELOPER_ID}" --options runtime ${entitlementFlag} ${escapePathForTerminal(helperPath)}`
|
|
2531
|
+
);
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
// Sign all binaries and libraries in MacOS folder and subdirectories
|
|
2536
|
+
console.log("Signing all binaries in MacOS folder...");
|
|
2537
|
+
|
|
2538
|
+
// Recursively find all executables and libraries in MacOS folder
|
|
2539
|
+
function findExecutables(dir: string): string[] {
|
|
2540
|
+
let executables: string[] = [];
|
|
2541
|
+
|
|
2542
|
+
try {
|
|
2543
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
2544
|
+
|
|
2545
|
+
for (const entry of entries) {
|
|
2546
|
+
const fullPath = join(dir, entry.name);
|
|
2547
|
+
|
|
2548
|
+
if (entry.isDirectory()) {
|
|
2549
|
+
// Recursively search subdirectories
|
|
2550
|
+
executables = executables.concat(findExecutables(fullPath));
|
|
2551
|
+
} else if (entry.isFile()) {
|
|
2552
|
+
// Check if it's an executable or library
|
|
2553
|
+
try {
|
|
2554
|
+
const fileInfo = execSync(`file -b "${fullPath}"`, { encoding: 'utf8' }).trim();
|
|
2555
|
+
if (fileInfo.includes('Mach-O') || entry.name.endsWith('.dylib')) {
|
|
2556
|
+
executables.push(fullPath);
|
|
2557
|
+
}
|
|
2558
|
+
} catch {
|
|
2559
|
+
// If file command fails, check by extension
|
|
2560
|
+
if (entry.name.endsWith('.dylib') || !entry.name.includes('.')) {
|
|
2561
|
+
// No extension often means executable
|
|
2562
|
+
executables.push(fullPath);
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
} catch (err) {
|
|
2568
|
+
console.error(`Error scanning directory ${dir}:`, err);
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
return executables;
|
|
2213
2572
|
}
|
|
2573
|
+
|
|
2574
|
+
const executablesInMacOS = findExecutables(macosPath);
|
|
2575
|
+
|
|
2576
|
+
// Sign each found executable
|
|
2577
|
+
for (const execPath of executablesInMacOS) {
|
|
2578
|
+
const fileName = basename(execPath);
|
|
2579
|
+
const relativePath = execPath.replace(macosPath + '/', '');
|
|
2580
|
+
|
|
2581
|
+
// Use filename as identifier (without extension)
|
|
2582
|
+
const identifier = fileName.replace(/\.[^.]+$/, '');
|
|
2583
|
+
|
|
2584
|
+
console.log(`Signing ${relativePath} with identifier ${identifier}`);
|
|
2585
|
+
const entitlementFlag = entitlementsFilePath ? `--entitlements ${entitlementsFilePath}` : '';
|
|
2586
|
+
|
|
2587
|
+
try {
|
|
2588
|
+
execSync(
|
|
2589
|
+
`codesign --force --verbose --timestamp --sign "${ELECTROBUN_DEVELOPER_ID}" --options runtime --identifier ${identifier} ${entitlementFlag} ${escapePathForTerminal(execPath)}`
|
|
2590
|
+
);
|
|
2591
|
+
} catch (err) {
|
|
2592
|
+
console.error(`Failed to sign ${relativePath}:`, err.message);
|
|
2593
|
+
// Continue signing other files even if one fails
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
// Note: main.js is now in Resources and will be automatically sealed when signing the app bundle
|
|
2598
|
+
|
|
2599
|
+
// Sign the main executable (launcher) - this should use the app's bundle identifier, not "launcher"
|
|
2600
|
+
const launcherPath = join(macosPath, 'launcher');
|
|
2601
|
+
if (existsSync(launcherPath)) {
|
|
2602
|
+
console.log("Signing main executable (launcher)");
|
|
2603
|
+
const entitlementFlag = entitlementsFilePath ? `--entitlements ${entitlementsFilePath}` : '';
|
|
2604
|
+
try {
|
|
2605
|
+
execSync(
|
|
2606
|
+
`codesign --force --verbose --timestamp --sign "${ELECTROBUN_DEVELOPER_ID}" --options runtime ${entitlementFlag} ${escapePathForTerminal(launcherPath)}`
|
|
2607
|
+
);
|
|
2608
|
+
} catch (error) {
|
|
2609
|
+
console.error("Failed to sign launcher:", error.message);
|
|
2610
|
+
console.log("Attempting to sign launcher without runtime hardening...");
|
|
2611
|
+
execSync(
|
|
2612
|
+
`codesign --force --verbose --timestamp --sign "${ELECTROBUN_DEVELOPER_ID}" ${entitlementFlag} ${escapePathForTerminal(launcherPath)}`
|
|
2613
|
+
);
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
|
|
2617
|
+
// Finally, sign the app bundle itself (without --deep)
|
|
2618
|
+
console.log("Signing app bundle");
|
|
2619
|
+
const entitlementFlag = entitlementsFilePath ? `--entitlements ${entitlementsFilePath}` : '';
|
|
2620
|
+
execSync(
|
|
2621
|
+
`codesign --force --verbose --timestamp --sign "${ELECTROBUN_DEVELOPER_ID}" --options runtime ${entitlementFlag} ${escapePathForTerminal(appBundleOrDmgPath)}`
|
|
2622
|
+
);
|
|
2214
2623
|
}
|
|
2215
2624
|
|
|
2216
2625
|
function notarizeAndStaple(appOrDmgPath: string) {
|
|
@@ -2349,3 +2758,11 @@ function createAppBundle(bundleName: string, parentFolder: string, targetOS: 'ma
|
|
|
2349
2758
|
throw new Error(`Unsupported OS: ${targetOS}`);
|
|
2350
2759
|
}
|
|
2351
2760
|
}
|
|
2761
|
+
|
|
2762
|
+
} // End of main() function
|
|
2763
|
+
|
|
2764
|
+
// Run the main function
|
|
2765
|
+
main().catch((error) => {
|
|
2766
|
+
console.error('Fatal error:', error);
|
|
2767
|
+
process.exit(1);
|
|
2768
|
+
});
|