electrobun 1.15.1 → 1.16.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/src/cli/index.ts CHANGED
@@ -5,12 +5,11 @@ import {
5
5
  readFileSync,
6
6
  writeFileSync,
7
7
  cpSync,
8
- rmdirSync,
8
+ rmSync,
9
9
  mkdirSync,
10
10
  createWriteStream,
11
11
  unlinkSync,
12
12
  readdirSync,
13
- rmSync,
14
13
  symlinkSync,
15
14
  statSync,
16
15
  copyFileSync,
@@ -21,6 +20,7 @@ import * as readline from "readline";
21
20
  import { OS, ARCH } from "../shared/platform";
22
21
  import { DEFAULT_CEF_VERSION_STRING } from "../shared/cef-version";
23
22
  import { BUN_VERSION } from "../shared/bun-version";
23
+ import { ELECTROBUN_VERSION } from "../shared/electrobun-version";
24
24
  import {
25
25
  getAppFileName,
26
26
  getBundleFileName,
@@ -74,7 +74,21 @@ const indexOfElectrobun = process.argv.findIndex((arg) =>
74
74
  );
75
75
  const commandArg = process.argv[indexOfElectrobun + 1] || "build";
76
76
 
77
- const ELECTROBUN_DEP_PATH = join(projectRoot, "node_modules", "electrobun");
77
+ // Walk up from projectRoot to find electrobun in node_modules (supports hoisted monorepo layouts)
78
+ function resolveElectrobunDir(): string {
79
+ let dir = projectRoot;
80
+ while (dir !== dirname(dir)) {
81
+ const candidate = join(dir, "node_modules", "electrobun");
82
+ if (existsSync(join(candidate, "package.json"))) {
83
+ return candidate;
84
+ }
85
+ dir = dirname(dir);
86
+ }
87
+ return join(projectRoot, "node_modules", "electrobun");
88
+ }
89
+
90
+ const ELECTROBUN_DEP_PATH = resolveElectrobunDir();
91
+ const ELECTROBUN_CACHE_PATH = join(dirname(ELECTROBUN_DEP_PATH), ".electrobun-cache");
78
92
 
79
93
  // When debugging electrobun with the example app use the builds (dev or release) right from the source folder
80
94
  // For developers using electrobun cli via npm use the release versions in /dist
@@ -182,18 +196,7 @@ async function ensureCoreDependencies(
182
196
  );
183
197
  console.log(`Downloading core binaries for ${platformOS}-${platformArch}...`);
184
198
 
185
- // Get the current Electrobun version from package.json
186
- const packageJsonPath = join(ELECTROBUN_DEP_PATH, "package.json");
187
- let version = "latest";
188
-
189
- if (existsSync(packageJsonPath)) {
190
- try {
191
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
192
- version = `v${packageJson.version}`;
193
- } catch (error) {
194
- console.warn("Could not read package version, using latest");
195
- }
196
- }
199
+ const version = `v${ELECTROBUN_VERSION}`;
197
200
 
198
201
  const platformName =
199
202
  platformOS === "macos" ? "darwin" : platformOS === "win" ? "win" : "linux";
@@ -366,13 +369,7 @@ function getEffectiveCEFDir(
366
369
  cefVersion?: string,
367
370
  ): string {
368
371
  if (cefVersion) {
369
- return join(
370
- projectRoot,
371
- "node_modules",
372
- ".electrobun-cache",
373
- "cef-override",
374
- `${platformOS}-${platformArch}`,
375
- );
372
+ return join(ELECTROBUN_CACHE_PATH, "cef-override", `${platformOS}-${platformArch}`);
376
373
  }
377
374
  return getPlatformPaths(platformOS, platformArch).CEF_DIR;
378
375
  }
@@ -491,13 +488,7 @@ async function ensureBunBinary(
491
488
  }
492
489
 
493
490
  const binExt = targetOS === "win" ? ".exe" : "";
494
- const overrideDir = join(
495
- projectRoot,
496
- "node_modules",
497
- ".electrobun-cache",
498
- "bun-override",
499
- `${targetOS}-${targetArch}`,
500
- );
491
+ const overrideDir = join(ELECTROBUN_CACHE_PATH, "bun-override", `${targetOS}-${targetArch}`);
501
492
  const overrideBinary = join(overrideDir, `bun${binExt}`);
502
493
  const versionFile = join(overrideDir, ".bun-version");
503
494
 
@@ -557,13 +548,7 @@ async function downloadCustomBun(
557
548
  }
558
549
 
559
550
  const binExt = platformOS === "win" ? ".exe" : "";
560
- const overrideDir = join(
561
- projectRoot,
562
- "node_modules",
563
- ".electrobun-cache",
564
- "bun-override",
565
- `${platformOS}-${platformArch}`,
566
- );
551
+ const overrideDir = join(ELECTROBUN_CACHE_PATH, "bun-override", `${platformOS}-${platformArch}`);
567
552
  const overrideBinary = join(overrideDir, `bun${binExt}`);
568
553
  const bunUrl = `https://github.com/oven-sh/bun/releases/download/bun-v${bunVersion}/${bunUrlSegment}`;
569
554
 
@@ -628,7 +613,7 @@ async function downloadCustomBun(
628
613
  { stdio: "inherit" },
629
614
  );
630
615
  } else {
631
- execSync(`unzip -o "${tempZipPath}" -d "${overrideDir}"`, {
616
+ execSync(`unzip -o ${escapePathForTerminal(tempZipPath)} -d ${escapePathForTerminal(overrideDir)}`, {
632
617
  stdio: "inherit",
633
618
  });
634
619
  }
@@ -645,7 +630,7 @@ async function downloadCustomBun(
645
630
 
646
631
  // Set execute permissions on non-Windows
647
632
  if (platformOS !== "win") {
648
- execSync(`chmod +x "${overrideBinary}"`);
633
+ execSync(`chmod +x ${escapePathForTerminal(overrideBinary)}`);
649
634
  }
650
635
 
651
636
  // Write version stamp
@@ -735,18 +720,7 @@ async function ensureCEFDependencies(
735
720
  `CEF dependencies not found for ${platformOS}-${platformArch}, downloading...`,
736
721
  );
737
722
 
738
- // Get the current Electrobun version from package.json
739
- const packageJsonPath = join(ELECTROBUN_DEP_PATH, "package.json");
740
- let version = "latest";
741
-
742
- if (existsSync(packageJsonPath)) {
743
- try {
744
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
745
- version = `v${packageJson.version}`;
746
- } catch (error) {
747
- console.warn("Could not read package version, using latest");
748
- }
749
- }
723
+ const version = `v${ELECTROBUN_VERSION}`;
750
724
 
751
725
  const platformName =
752
726
  platformOS === "macos" ? "darwin" : platformOS === "win" ? "win" : "linux";
@@ -1386,6 +1360,7 @@ const defaultConfig = {
1386
1360
  locales: undefined as string[] | "*" | undefined, // ICU locales subset (Linux/Windows)
1387
1361
  mac: {
1388
1362
  codesign: false,
1363
+ createDmg: true,
1389
1364
  notarize: false,
1390
1365
  bundleCEF: false,
1391
1366
  bundleWGPU: false,
@@ -1398,21 +1373,21 @@ const defaultConfig = {
1398
1373
  } as Record<string, boolean | string>,
1399
1374
  icons: "icon.iconset",
1400
1375
  defaultRenderer: undefined as "native" | "cef" | undefined,
1401
- chromiumFlags: undefined as Record<string, string | true> | undefined,
1376
+ chromiumFlags: undefined as Record<string, string | boolean> | undefined,
1402
1377
  },
1403
1378
  win: {
1404
1379
  bundleCEF: false,
1405
1380
  bundleWGPU: false,
1406
1381
  icon: undefined as string | undefined,
1407
1382
  defaultRenderer: undefined as "native" | "cef" | undefined,
1408
- chromiumFlags: undefined as Record<string, string | true> | undefined,
1383
+ chromiumFlags: undefined as Record<string, string | boolean> | undefined,
1409
1384
  },
1410
1385
  linux: {
1411
1386
  bundleCEF: false,
1412
1387
  bundleWGPU: false,
1413
1388
  icon: undefined as string | undefined,
1414
1389
  defaultRenderer: undefined as "native" | "cef" | undefined,
1415
- chromiumFlags: undefined as Record<string, string | true> | undefined,
1390
+ chromiumFlags: undefined as Record<string, string | boolean> | undefined,
1416
1391
  },
1417
1392
  bun: {
1418
1393
  entrypoint: "src/bun/index.ts",
@@ -1480,7 +1455,11 @@ function escapeXml(str: string): string {
1480
1455
 
1481
1456
  // Helper functions
1482
1457
  function escapePathForTerminal(path: string): string {
1483
- return `"${path.replace(/"/g, '\\"')}"`;
1458
+ if (OS === "win") {
1459
+ return `"${path.replace(/"/g, '""')}"`;
1460
+ } else {
1461
+ return `'${path.replace(/'/g, "'\\''")}'`;
1462
+ }
1484
1463
  }
1485
1464
 
1486
1465
  /**
@@ -2021,7 +2000,7 @@ Categories=Utility;Application;
2021
2000
 
2022
2001
  // refresh build folder
2023
2002
  if (existsSync(buildFolder)) {
2024
- rmdirSync(buildFolder, { recursive: true });
2003
+ rmSync(buildFolder, { recursive: true });
2025
2004
  }
2026
2005
  mkdirSync(buildFolder, { recursive: true });
2027
2006
  // bundle bun to build/bun
@@ -2629,7 +2608,7 @@ Categories=Utility;Application;
2629
2608
  // Fallback to copying the file
2630
2609
  cpSync(cefFilePath, mainDirPath, { dereference: true });
2631
2610
  console.log(
2632
- `Fallback: Copied CEF library to main directory: ${soFile}`,
2611
+ `Fallback: Copying CEF library to main directory: ${soFile}`,
2633
2612
  );
2634
2613
  }
2635
2614
  }
@@ -3053,7 +3032,7 @@ Categories=Utility;Application;
3053
3032
  console.log("✓ Created app.asar");
3054
3033
 
3055
3034
  // Remove the entire app folder since it's now packed in ASAR
3056
- rmdirSync(appDirPath, { recursive: true });
3035
+ rmSync(appDirPath, { recursive: true });
3057
3036
  console.log("✓ Removed app/ folder (now in ASAR)");
3058
3037
  }
3059
3038
  }
@@ -3212,7 +3191,7 @@ Categories=Utility;Application;
3212
3191
 
3213
3192
  // Remove the app bundle folder after tarring (except on Linux where it might be needed for dev)
3214
3193
  if (targetOS !== "linux" || buildEnvironment !== "dev") {
3215
- rmdirSync(appBundleFolderPath, { recursive: true });
3194
+ rmSync(appBundleFolderPath, { recursive: true });
3216
3195
  }
3217
3196
 
3218
3197
  // generate bsdiff
@@ -3516,7 +3495,7 @@ Categories=Utility;Application;
3516
3495
  }
3517
3496
 
3518
3497
  // DMG creation for macOS only
3519
- if (targetOS === "macos") {
3498
+ if (targetOS === "macos" && config.build.mac?.createDmg !== false) {
3520
3499
  console.log("creating dmg...");
3521
3500
  const finalDmgPath = join(buildFolder, `${appFileName}.dmg`);
3522
3501
  // NOTE: For some ungodly reason using the bare name in CI can conflict with some mysterious
@@ -3586,6 +3565,9 @@ Categories=Utility;Application;
3586
3565
  }
3587
3566
  }
3588
3567
  } else {
3568
+ if (targetOS === "macos") {
3569
+ console.log("skipping dmg");
3570
+ }
3589
3571
  // For Windows and Linux, add the self-extracting bundle directly
3590
3572
  // @ts-expect-error - reserved for future use
3591
3573
  const _platformBundlePath = join(
@@ -3639,7 +3621,7 @@ Categories=Utility;Application;
3639
3621
  console.log("creating artifacts folder...");
3640
3622
  if (existsSync(artifactFolder)) {
3641
3623
  console.info("deleting artifact folder: ", artifactFolder);
3642
- rmdirSync(artifactFolder, { recursive: true });
3624
+ rmSync(artifactFolder, { recursive: true });
3643
3625
  }
3644
3626
 
3645
3627
  mkdirSync(artifactFolder, { recursive: true });
@@ -4585,7 +4567,7 @@ Categories=Utility;Application;
4585
4567
  } else if (entry.isFile()) {
4586
4568
  // Check if it's an executable or library
4587
4569
  try {
4588
- const fileInfo = execSync(`file -b "${fullPath}"`, {
4570
+ const fileInfo = execSync(`file -b ${escapePathForTerminal(fullPath)}`, {
4589
4571
  encoding: "utf8",
4590
4572
  }).trim();
4591
4573
  if (
@@ -4638,6 +4620,28 @@ Categories=Utility;Application;
4638
4620
  }
4639
4621
  }
4640
4622
 
4623
+ // Sign .node native modules in Resources/app/bun
4624
+ const resourcesPath = join(contentsPath, "Resources", "app", "bun");
4625
+ if (existsSync(resourcesPath)) {
4626
+ console.log("Signing native modules in Resources/app/bun...");
4627
+ try {
4628
+ const nodeFiles = execSync(`find ${escapePathForTerminal(resourcesPath)} -name "*.node" -type f`, {
4629
+ encoding: "utf8",
4630
+ }).trim().split("\n").filter(Boolean);
4631
+
4632
+ for (const nodeFile of nodeFiles) {
4633
+ if (nodeFile) {
4634
+ console.log(`Signing native module: ${nodeFile.replace(resourcesPath + "/", "")}`);
4635
+ execSync(
4636
+ `codesign --force --verbose --timestamp --sign "${ELECTROBUN_DEVELOPER_ID}" --options runtime ${escapePathForTerminal(nodeFile)}`,
4637
+ );
4638
+ }
4639
+ }
4640
+ } catch (err) {
4641
+ console.error("Error signing native modules:", err);
4642
+ }
4643
+ }
4644
+
4641
4645
  // Note: main.js is now in Resources and will be automatically sealed when signing the app bundle
4642
4646
 
4643
4647
  // Sign the main executable (launcher) - this should use the app's bundle identifier, not "launcher"
@@ -4700,43 +4704,58 @@ Categories=Utility;Application;
4700
4704
  // }
4701
4705
 
4702
4706
  const ELECTROBUN_APPLEID = process.env["ELECTROBUN_APPLEID"];
4703
-
4704
- if (!ELECTROBUN_APPLEID) {
4705
- console.error("Env var ELECTROBUN_APPLEID is required to notarize");
4706
- process.exit(1);
4707
- }
4708
-
4709
4707
  const ELECTROBUN_APPLEIDPASS = process.env["ELECTROBUN_APPLEIDPASS"];
4708
+ const ELECTROBUN_TEAMID = process.env["ELECTROBUN_TEAMID"];
4710
4709
 
4711
- if (!ELECTROBUN_APPLEIDPASS) {
4712
- console.error("Env var ELECTROBUN_APPLEIDPASS is required to notarize");
4713
- process.exit(1);
4714
- }
4710
+ const ELECTROBUN_APPLEAPIISSUER = process.env["ELECTROBUN_APPLEAPIISSUER"];
4711
+ const ELECTROBUN_APPLEAPIKEY = process.env["ELECTROBUN_APPLEAPIKEY"];
4712
+ const ELECTROBUN_APPLEAPIKEYPATH = process.env["ELECTROBUN_APPLEAPIKEYPATH"];
4715
4713
 
4716
- const ELECTROBUN_TEAMID = process.env["ELECTROBUN_TEAMID"];
4714
+ const useApiKey = ELECTROBUN_APPLEAPIISSUER && ELECTROBUN_APPLEAPIKEY && ELECTROBUN_APPLEAPIKEYPATH;
4715
+ const useAppleId = ELECTROBUN_APPLEID && ELECTROBUN_APPLEIDPASS && ELECTROBUN_TEAMID;
4717
4716
 
4718
- if (!ELECTROBUN_TEAMID) {
4719
- console.error("Env var ELECTROBUN_TEAMID is required to notarize");
4717
+ if (!useApiKey && !useAppleId) {
4718
+ console.error("Provide either App Store Connect API key credentials (ELECTROBUN_APPLEAPIISSUER, ELECTROBUN_APPLEAPIKEY, ELECTROBUN_APPLEAPIKEYPATH) or Apple ID credentials (ELECTROBUN_APPLEID, ELECTROBUN_APPLEIDPASS, ELECTROBUN_TEAMID) to notarize");
4720
4719
  process.exit(1);
4721
4720
  }
4722
4721
 
4723
- // notarize
4724
- // todo (yoav): follow up on options here like --s3-acceleration and --webhook
4725
- // todo (yoav): don't use execSync since it's blocking and we'll only see the output at the end
4726
- const statusInfo = execSync(
4727
- `xcrun notarytool submit --apple-id "${ELECTROBUN_APPLEID}" --password "${ELECTROBUN_APPLEIDPASS}" --team-id "${ELECTROBUN_TEAMID}" --wait ${escapePathForTerminal(
4728
- fileToNotarize,
4729
- )}`,
4730
- ).toString();
4722
+ let statusInfo: string;
4723
+ if (useApiKey) {
4724
+ if (!existsSync(ELECTROBUN_APPLEAPIKEYPATH)) {
4725
+ console.error(`ELECTROBUN_APPLEAPIKEYPATH does not exist: ${ELECTROBUN_APPLEAPIKEYPATH}`);
4726
+ process.exit(1);
4727
+ }
4728
+ statusInfo = execSync(
4729
+ `xcrun notarytool submit --key "${ELECTROBUN_APPLEAPIKEYPATH}" --key-id "${ELECTROBUN_APPLEAPIKEY}" --issuer "${ELECTROBUN_APPLEAPIISSUER}" --wait ${escapePathForTerminal(
4730
+ fileToNotarize,
4731
+ )}`,
4732
+ ).toString();
4733
+ } else {
4734
+ // notarize
4735
+ // todo (yoav): follow up on options here like --s3-acceleration and --webhook
4736
+ // todo (yoav): don't use execSync since it's blocking and we'll only see the output at the end
4737
+ statusInfo = execSync(
4738
+ `xcrun notarytool submit --apple-id "${ELECTROBUN_APPLEID}" --password "${ELECTROBUN_APPLEIDPASS}" --team-id "${ELECTROBUN_TEAMID}" --wait ${escapePathForTerminal(
4739
+ fileToNotarize,
4740
+ )}`,
4741
+ ).toString();
4742
+ }
4731
4743
  const uuid = statusInfo.match(/id: ([^\n]+)/)?.[1];
4732
4744
  console.log("statusInfo", statusInfo);
4733
4745
  console.log("uuid", uuid);
4734
4746
 
4735
4747
  if (statusInfo.match("Current status: Invalid")) {
4736
4748
  console.error("notarization failed", statusInfo);
4737
- const log = execSync(
4738
- `xcrun notarytool log --apple-id "${ELECTROBUN_APPLEID}" --password "${ELECTROBUN_APPLEIDPASS}" --team-id "${ELECTROBUN_TEAMID}" ${uuid}`,
4739
- ).toString();
4749
+ let log: string;
4750
+ if (useApiKey) {
4751
+ log = execSync(
4752
+ `xcrun notarytool log --key "${ELECTROBUN_APPLEAPIKEYPATH}" --key-id "${ELECTROBUN_APPLEAPIKEY}" --issuer "${ELECTROBUN_APPLEAPIISSUER}" ${uuid}`,
4753
+ ).toString();
4754
+ } else {
4755
+ log = execSync(
4756
+ `xcrun notarytool log --apple-id "${ELECTROBUN_APPLEID}" --password "${ELECTROBUN_APPLEIDPASS}" --team-id "${ELECTROBUN_TEAMID}" ${uuid}`,
4757
+ ).toString();
4758
+ }
4740
4759
  console.log("log", log);
4741
4760
  process.exit(1);
4742
4761
  }