electrobun 1.18.1 → 1.18.4-beta.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electrobun",
3
- "version": "1.18.1",
3
+ "version": "1.18.4-beta.3",
4
4
  "description": "Build ultra fast, tiny, and cross-platform desktop apps with Typescript.",
5
5
  "license": "MIT",
6
6
  "author": "Blackboard Technologies Inc.",
@@ -14,8 +14,7 @@
14
14
  "exports": {
15
15
  ".": "./dist/api/bun/index.ts",
16
16
  "./bun": "./dist/api/bun/index.ts",
17
- "./view": "./dist/api/browser/index.ts",
18
- "./carrot": "./dist/api/carrot/bun.ts"
17
+ "./view": "./dist/api/browser/index.ts"
19
18
  },
20
19
  "type": "module",
21
20
  "bin": {
package/src/cli/index.ts CHANGED
@@ -111,6 +111,9 @@ function getPlatformPaths(
111
111
  BUN_BINARY: join(platformDistDir, "bun") + binExt,
112
112
  LAUNCHER_DEV: join(platformDistDir, "electrobun") + binExt,
113
113
  LAUNCHER_RELEASE: join(platformDistDir, "launcher") + binExt,
114
+ CORE_MACOS: join(platformDistDir, "libElectrobunCore.dylib"),
115
+ CORE_WIN: join(platformDistDir, "ElectrobunCore.dll"),
116
+ CORE_LINUX: join(platformDistDir, "libElectrobunCore.so"),
114
117
  NATIVE_WRAPPER_MACOS: join(platformDistDir, "libNativeWrapper.dylib"),
115
118
  NATIVE_WRAPPER_WIN: join(platformDistDir, "libNativeWrapper.dll"),
116
119
  NATIVE_WRAPPER_LINUX: join(platformDistDir, "libNativeWrapper.so"),
@@ -134,6 +137,8 @@ function getPlatformPaths(
134
137
  // These work with existing package.json and development workflow
135
138
  MAIN_JS: join(sharedDistDir, "main.js"),
136
139
  API_DIR: join(sharedDistDir, "api"),
140
+ PRELOAD_FULL_JS: join(sharedDistDir, "preload-full.js"),
141
+ PRELOAD_SANDBOXED_JS: join(sharedDistDir, "preload-sandboxed.js"),
137
142
  };
138
143
  }
139
144
 
@@ -141,6 +146,135 @@ function getPlatformPaths(
141
146
  // @ts-expect-error - reserved for future use
142
147
  const _PATHS = getPlatformPaths(OS, ARCH);
143
148
 
149
+ function getVendoredZigBinaryPath(): string {
150
+ return join(
151
+ ELECTROBUN_DEP_PATH,
152
+ "vendors",
153
+ "zig",
154
+ OS === "win" ? "zig.exe" : "zig",
155
+ );
156
+ }
157
+
158
+ function getZigTarget(
159
+ targetOS: "macos" | "win" | "linux",
160
+ targetArch: "arm64" | "x64",
161
+ ): string {
162
+ if (targetOS === "win") {
163
+ return "x86_64-windows";
164
+ }
165
+ if (targetOS === "linux") {
166
+ return targetArch === "arm64" ? "aarch64-linux" : "x86_64-linux";
167
+ }
168
+ return targetArch === "arm64" ? "aarch64-macos" : "x86_64-macos";
169
+ }
170
+
171
+ function getCEFHelperBaseName(mainProcess: "bun" | "zig"): string {
172
+ return mainProcess === "zig" ? "main" : "bun";
173
+ }
174
+
175
+ function getCEFHelperNames(mainProcess: "bun" | "zig"): string[] {
176
+ const baseName = getCEFHelperBaseName(mainProcess);
177
+ return [
178
+ `${baseName} Helper`,
179
+ `${baseName} Helper (Alerts)`,
180
+ `${baseName} Helper (GPU)`,
181
+ `${baseName} Helper (Plugin)`,
182
+ `${baseName} Helper (Renderer)`,
183
+ ];
184
+ }
185
+
186
+ async function buildZigMainExecutable(options: {
187
+ entrypoint: string;
188
+ buildFolder: string;
189
+ targetOS: "macos" | "win" | "linux";
190
+ targetArch: "arm64" | "x64";
191
+ buildEnvironment: "dev" | "canary" | "stable";
192
+ }) {
193
+ const zigBinary = getVendoredZigBinaryPath();
194
+ if (!existsSync(zigBinary)) {
195
+ throw new Error(
196
+ `Vendored Zig compiler not found at ${zigBinary}. Rebuild electrobun/package so vendors/zig is available.`,
197
+ );
198
+ }
199
+
200
+ const zigSdkPath = join(ELECTROBUN_DEP_PATH, "dist", "zig-sdk", "electrobun.zig");
201
+ if (!existsSync(zigSdkPath)) {
202
+ throw new Error(`Electrobun Zig SDK not found at ${zigSdkPath}`);
203
+ }
204
+
205
+ const binExt = options.targetOS === "win" ? ".exe" : "";
206
+ const tempBuildDir = join(
207
+ options.buildFolder,
208
+ ".electrobun-zig-main",
209
+ `${options.targetOS}-${options.targetArch}`,
210
+ );
211
+ const relativeZigSdkPath = path.relative(tempBuildDir, zigSdkPath) || ".";
212
+ const relativeEntrypointPath = path.relative(tempBuildDir, options.entrypoint) || ".";
213
+ const zigOutBin = join(tempBuildDir, "zig-out", "bin", "main" + binExt);
214
+ const buildScriptPath = join(tempBuildDir, "build.zig");
215
+ mkdirSync(tempBuildDir, { recursive: true });
216
+
217
+ const buildScript = `const std = @import("std");
218
+
219
+ pub fn build(b: *std.Build) void {
220
+ const target = b.standardTargetOptions(.{});
221
+ const optimize = b.standardOptimizeOption(.{});
222
+
223
+ const electrobun = b.createModule(.{
224
+ .root_source_file = b.path(${JSON.stringify(relativeZigSdkPath)}),
225
+ });
226
+
227
+ const exe = b.addExecutable(.{
228
+ .name = "main",
229
+ .root_source_file = b.path(${JSON.stringify(relativeEntrypointPath)}),
230
+ .target = target,
231
+ .optimize = optimize,
232
+ });
233
+
234
+ exe.root_module.addImport("electrobun", electrobun);
235
+ exe.linkLibC();
236
+ b.installArtifact(exe);
237
+ }
238
+ `;
239
+ writeFileSync(buildScriptPath, buildScript, "utf8");
240
+
241
+ const zigArgs = [
242
+ "build",
243
+ `-Dtarget=${getZigTarget(options.targetOS, options.targetArch)}`,
244
+ ];
245
+
246
+ if (options.targetOS === "win") {
247
+ zigArgs.push("-Dcpu=baseline");
248
+ }
249
+
250
+ if (options.buildEnvironment !== "dev") {
251
+ zigArgs.push("-Doptimize=ReleaseSmall");
252
+ }
253
+
254
+ const result = Bun.spawnSync([zigBinary, ...zigArgs], {
255
+ cwd: tempBuildDir,
256
+ stdio: ["ignore", "pipe", "pipe"],
257
+ });
258
+
259
+ if (result.exitCode !== 0) {
260
+ const stdout = result.stdout ? new TextDecoder().decode(result.stdout) : "";
261
+ const stderr = result.stderr ? new TextDecoder().decode(result.stderr) : "";
262
+ if (stdout.trim()) {
263
+ console.error(stdout);
264
+ }
265
+ if (stderr.trim()) {
266
+ console.error(stderr);
267
+ }
268
+ throw new Error("Build failed: zig main process compilation failed");
269
+ }
270
+
271
+ if (!existsSync(zigOutBin)) {
272
+ throw new Error(`Zig main process binary was not produced at ${zigOutBin}`);
273
+ }
274
+
275
+ return zigOutBin;
276
+ }
277
+
144
278
  async function ensureCoreDependencies(
145
279
  targetOS?: "macos" | "win" | "linux",
146
280
  targetArch?: "arm64" | "x64",
@@ -1483,6 +1617,7 @@ const defaultConfig = {
1483
1617
  build: {
1484
1618
  buildFolder: "build",
1485
1619
  artifactFolder: "artifacts",
1620
+ mainProcess: "bun" as "bun" | "zig",
1486
1621
  useAsar: false,
1487
1622
  asarUnpack: undefined as string[] | undefined, // Glob patterns for files to exclude from ASAR (e.g., ["*.node", "*.dll"])
1488
1623
  cefVersion: undefined as string | undefined, // Override CEF version: "CEF_VERSION+chromium-CHROMIUM_VERSION"
@@ -1524,6 +1659,9 @@ const defaultConfig = {
1524
1659
  bun: {
1525
1660
  entrypoint: "src/bun/index.ts",
1526
1661
  },
1662
+ zig: {
1663
+ entrypoint: "src/zig/main.zig",
1664
+ },
1527
1665
  views: undefined as
1528
1666
  | Record<string, { entrypoint: string; [key: string]: unknown }>
1529
1667
  | undefined,
@@ -2078,8 +2216,10 @@ ${utiDecls}
2078
2216
  // Get environment
2079
2217
  const envArg =
2080
2218
  process.argv.find((arg) => arg.startsWith("--env="))?.split("=")[1] || "";
2081
- const buildEnvironment = ["dev", "canary", "stable"].includes(envArg)
2082
- ? envArg
2219
+ const buildEnvironment: "dev" | "canary" | "stable" = ["dev", "canary", "stable"].includes(
2220
+ envArg,
2221
+ )
2222
+ ? (envArg as "dev" | "canary" | "stable")
2083
2223
  : "dev";
2084
2224
 
2085
2225
  try {
@@ -2110,7 +2250,7 @@ ${utiDecls}
2110
2250
 
2111
2251
  async function runBuild(
2112
2252
  config: Awaited<ReturnType<typeof getConfig>>,
2113
- buildEnvironment: string,
2253
+ buildEnvironment: "dev" | "canary" | "stable",
2114
2254
  ) {
2115
2255
  // Determine current platform as default target
2116
2256
  const currentTarget = { os: OS, arch: ARCH };
@@ -2395,17 +2535,31 @@ Categories=Utility;Application;
2395
2535
  rmSync(buildFolder, { recursive: true });
2396
2536
  }
2397
2537
  mkdirSync(buildFolder, { recursive: true });
2398
- // bundle bun to build/bun
2538
+
2539
+ const mainProcess = config.build.mainProcess ?? "bun";
2399
2540
  const bunConfig = config.build.bun;
2400
2541
  const bunSource = join(projectRoot, bunConfig.entrypoint);
2542
+ const zigConfig = config.build.zig;
2543
+ const zigSource = join(projectRoot, zigConfig.entrypoint);
2401
2544
 
2402
- if (!existsSync(bunSource)) {
2545
+ if (mainProcess === "bun") {
2546
+ if (!existsSync(bunSource)) {
2547
+ throw new Error(
2548
+ `failed to bundle ${bunSource} because it doesn't exist.\n You need a config.build.bun.entrypoint source file to build.`,
2549
+ );
2550
+ }
2551
+ } else if (!existsSync(zigSource)) {
2403
2552
  throw new Error(
2404
- `failed to bundle ${bunSource} because it doesn't exist.\n You need a config.build.bun.entrypoint source file to build.`,
2553
+ `failed to compile ${zigSource} because it doesn't exist.\n You need a config.build.zig.entrypoint source file to build.`,
2405
2554
  );
2406
2555
  }
2407
2556
 
2408
2557
  const isCarrotOnly = config.build.carrot?.carrotOnly === true;
2558
+ if (config.build.carrot && mainProcess === "zig") {
2559
+ throw new Error(
2560
+ `build.carrot is not supported with build.mainProcess = "zig" yet.`,
2561
+ );
2562
+ }
2409
2563
 
2410
2564
  // build macos bundle
2411
2565
  // Use display name (with spaces) for macOS bundle folders, sanitized name for other platforms
@@ -2438,6 +2592,17 @@ Categories=Utility;Application;
2438
2592
  mkdirSync(appBundleAppCodePath, { recursive: true });
2439
2593
  }
2440
2594
 
2595
+ let zigMainBinarySourcePath: string | null = null;
2596
+ if (mainProcess === "zig") {
2597
+ zigMainBinarySourcePath = await buildZigMainExecutable({
2598
+ entrypoint: zigSource,
2599
+ buildFolder,
2600
+ targetOS,
2601
+ targetArch: targetARCH,
2602
+ buildEnvironment,
2603
+ });
2604
+ }
2605
+
2441
2606
  // const bundledBunPath = join(appBundleMacOSPath, 'bun');
2442
2607
  // cpSync(bunPath, bundledBunPath);
2443
2608
 
@@ -2610,105 +2775,121 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
2610
2775
  }
2611
2776
  }
2612
2777
 
2613
- cpSync(targetPaths.MAIN_JS, join(appBundleFolderResourcesPath, "main.js"), {
2778
+ cpSync(targetPaths.PRELOAD_FULL_JS, join(appBundleFolderResourcesPath, "preload-full.js"), {
2614
2779
  dereference: true,
2615
2780
  });
2616
-
2617
- // Bun runtime binary
2618
- // todo (yoav): this only works for the current architecture
2619
- const bunBinarySourcePath = await ensureBunBinary(
2620
- currentTarget.os,
2621
- currentTarget.arch,
2622
- config.build.bunVersion,
2623
- config.build.bunnyBun,
2781
+ cpSync(
2782
+ targetPaths.PRELOAD_SANDBOXED_JS,
2783
+ join(appBundleFolderResourcesPath, "preload-sandboxed.js"),
2784
+ {
2785
+ dereference: true,
2786
+ },
2624
2787
  );
2625
- // Note: .bin/bun binary in node_modules is a symlink to the versioned one in another place
2626
- // in node_modules, so we have to dereference here to get the actual binary in the bundle.
2627
- const bunBinaryDestInBundlePath =
2628
- join(appBundleMacOSPath, "bun") + targetBinExt;
2629
- const destFolder2 = dirname(bunBinaryDestInBundlePath);
2630
- if (!existsSync(destFolder2)) {
2631
- // console.info('creating folder: ', destFolder);
2632
- mkdirSync(destFolder2, { recursive: true });
2633
- }
2634
- cpSync(bunBinarySourcePath, bunBinaryDestInBundlePath, {
2635
- dereference: true,
2636
- });
2637
2788
 
2638
- // Copy ICU data file if it exists (Linux/Windows external ICU builds)
2639
- // ICU version varies per platform WebKit build, so detect the filename dynamically
2640
- const bunDir = dirname(bunBinarySourcePath);
2641
- const icuDataFileName = readdirSync(bunDir).find((f) => /^icudt\d+l\.dat$/.test(f));
2642
- const icuDataSource = icuDataFileName ? join(bunDir, icuDataFileName) : "";
2643
- if (icuDataFileName && existsSync(icuDataSource) && targetOS !== "macos") {
2644
- const icuDataDest = join(appBundleMacOSPath, icuDataFileName);
2645
-
2646
- const locales = config.build?.locales;
2647
- if (locales && locales !== "*" && Array.isArray(locales) && locales.length > 0) {
2648
- // Trim ICU data to specified locales using icupkg
2649
- try {
2650
- await trimICUData(icuDataSource, icuDataDest, locales);
2651
- const originalSize = statSync(icuDataSource).size;
2652
- const trimmedSize = statSync(icuDataDest).size;
2653
- console.log(
2654
- `Trimmed ICU data: ${(originalSize / 1024 / 1024).toFixed(1)}MB → ${(trimmedSize / 1024 / 1024).toFixed(1)}MB (locales: ${locales.join(", ")})`,
2655
- );
2656
- } catch (error) {
2657
- console.warn(`Warning: Failed to trim ICU data, copying full file: ${error}`);
2658
- cpSync(icuDataSource, icuDataDest);
2659
- }
2660
- } else {
2661
- cpSync(icuDataSource, icuDataDest);
2662
- console.log(`Copied ICU data file: ${icuDataFileName}`);
2663
- }
2664
- }
2789
+ if (mainProcess === "bun") {
2790
+ cpSync(targetPaths.MAIN_JS, join(appBundleFolderResourcesPath, "main.js"), {
2791
+ dereference: true,
2792
+ });
2665
2793
 
2666
- // Embed icon into bun.exe on Windows
2667
- if (targetOS === "win" && config.build.win?.icon) {
2668
- const iconSourcePath =
2669
- config.build.win.icon.startsWith("/") ||
2670
- config.build.win.icon.match(/^[a-zA-Z]:/)
2671
- ? config.build.win.icon
2672
- : join(projectRoot, config.build.win.icon);
2794
+ // Bun runtime binary
2795
+ // todo (yoav): this only works for the current architecture
2796
+ const bunBinarySourcePath = await ensureBunBinary(
2797
+ currentTarget.os,
2798
+ currentTarget.arch,
2799
+ config.build.bunVersion,
2800
+ config.build.bunnyBun,
2801
+ );
2802
+ // Note: .bin/bun binary in node_modules is a symlink to the versioned one in another place
2803
+ // in node_modules, so we have to dereference here to get the actual binary in the bundle.
2804
+ const bunBinaryDestInBundlePath =
2805
+ join(appBundleMacOSPath, "bun") + targetBinExt;
2806
+ const destFolder2 = dirname(bunBinaryDestInBundlePath);
2807
+ if (!existsSync(destFolder2)) {
2808
+ mkdirSync(destFolder2, { recursive: true });
2809
+ }
2810
+ cpSync(bunBinarySourcePath, bunBinaryDestInBundlePath, {
2811
+ dereference: true,
2812
+ });
2673
2813
 
2674
- if (existsSync(iconSourcePath)) {
2675
- console.log(`Embedding icon into bun.exe: ${iconSourcePath}`);
2676
- try {
2677
- let iconPath = iconSourcePath;
2814
+ // Copy ICU data file if it exists (Linux/Windows external ICU builds)
2815
+ // ICU version varies per platform WebKit build, so detect the filename dynamically
2816
+ const bunDir = dirname(bunBinarySourcePath);
2817
+ const icuDataFileName = readdirSync(bunDir).find((f) => /^icudt\d+l\.dat$/.test(f));
2818
+ const icuDataSource = icuDataFileName ? join(bunDir, icuDataFileName) : "";
2819
+ if (icuDataFileName && existsSync(icuDataSource) && targetOS !== "macos") {
2820
+ const icuDataDest = join(appBundleMacOSPath, icuDataFileName);
2678
2821
 
2679
- // Convert PNG to ICO if needed
2680
- if (iconSourcePath.toLowerCase().endsWith(".png")) {
2681
- const pngToIco = (await import("png-to-ico")).default;
2682
- const tempIcoPath = join(buildFolder, "temp-bun-icon.ico");
2683
- const icoBuffer = await pngToIco(iconSourcePath);
2684
- writeFileSync(tempIcoPath, new Uint8Array(icoBuffer));
2685
- iconPath = tempIcoPath;
2822
+ const locales = config.build?.locales;
2823
+ if (locales && locales !== "*" && Array.isArray(locales) && locales.length > 0) {
2824
+ try {
2825
+ await trimICUData(icuDataSource, icuDataDest, locales);
2826
+ const originalSize = statSync(icuDataSource).size;
2827
+ const trimmedSize = statSync(icuDataDest).size;
2686
2828
  console.log(
2687
- `Converted PNG to ICO format for bun.exe: ${tempIcoPath}`,
2829
+ `Trimmed ICU data: ${(originalSize / 1024 / 1024).toFixed(1)}MB → ${(trimmedSize / 1024 / 1024).toFixed(1)}MB (locales: ${locales.join(", ")})`,
2688
2830
  );
2831
+ } catch (error) {
2832
+ console.warn(`Warning: Failed to trim ICU data, copying full file: ${error}`);
2833
+ cpSync(icuDataSource, icuDataDest);
2689
2834
  }
2835
+ } else {
2836
+ cpSync(icuDataSource, icuDataDest);
2837
+ console.log(`Copied ICU data file: ${icuDataFileName}`);
2838
+ }
2839
+ }
2690
2840
 
2691
- // Use rcedit to embed the icon into bun.exe
2692
- const { execFileSync } = await import("child_process");
2693
- const rceditPkgPath = require.resolve("rcedit/package.json");
2694
- const rceditDir = dirname(rceditPkgPath);
2695
- const rceditX64 = join(rceditDir, "bin", "rcedit-x64.exe");
2696
- const rceditExe = existsSync(rceditX64) ? rceditX64 : join(rceditDir, "bin", "rcedit.exe");
2697
- execFileSync(rceditExe, [bunBinaryDestInBundlePath, "--set-icon", iconPath]);
2698
- console.log(`Successfully embedded icon into bun.exe`);
2841
+ // Embed icon into bun.exe on Windows
2842
+ if (targetOS === "win" && config.build.win?.icon) {
2843
+ const iconSourcePath =
2844
+ config.build.win.icon.startsWith("/") ||
2845
+ config.build.win.icon.match(/^[a-zA-Z]:/)
2846
+ ? config.build.win.icon
2847
+ : join(projectRoot, config.build.win.icon);
2699
2848
 
2700
- // Clean up temp ICO file
2701
- if (iconPath !== iconSourcePath && existsSync(iconPath)) {
2702
- unlinkSync(iconPath);
2849
+ if (existsSync(iconSourcePath)) {
2850
+ console.log(`Embedding icon into bun.exe: ${iconSourcePath}`);
2851
+ try {
2852
+ let iconPath = iconSourcePath;
2853
+
2854
+ if (iconSourcePath.toLowerCase().endsWith(".png")) {
2855
+ const pngToIco = (await import("png-to-ico")).default;
2856
+ const tempIcoPath = join(buildFolder, "temp-bun-icon.ico");
2857
+ const icoBuffer = await pngToIco(iconSourcePath);
2858
+ writeFileSync(tempIcoPath, new Uint8Array(icoBuffer));
2859
+ iconPath = tempIcoPath;
2860
+ console.log(
2861
+ `Converted PNG to ICO format for bun.exe: ${tempIcoPath}`,
2862
+ );
2863
+ }
2864
+
2865
+ const { execFileSync } = await import("child_process");
2866
+ const rceditPkgPath = require.resolve("rcedit/package.json");
2867
+ const rceditDir = dirname(rceditPkgPath);
2868
+ const rceditX64 = join(rceditDir, "bin", "rcedit-x64.exe");
2869
+ const rceditExe = existsSync(rceditX64) ? rceditX64 : join(rceditDir, "bin", "rcedit.exe");
2870
+ execFileSync(rceditExe, [bunBinaryDestInBundlePath, "--set-icon", iconPath]);
2871
+ console.log(`Successfully embedded icon into bun.exe`);
2872
+
2873
+ if (iconPath !== iconSourcePath && existsSync(iconPath)) {
2874
+ unlinkSync(iconPath);
2875
+ }
2876
+ } catch (error) {
2877
+ console.warn(`Warning: Failed to embed icon into bun.exe: ${error}`);
2703
2878
  }
2704
- } catch (error) {
2705
- console.warn(`Warning: Failed to embed icon into bun.exe: ${error}`);
2706
2879
  }
2707
2880
  }
2881
+ } else if (zigMainBinarySourcePath) {
2882
+ cpSync(zigMainBinarySourcePath, join(appBundleMacOSPath, "main") + targetBinExt, {
2883
+ dereference: true,
2884
+ });
2708
2885
  }
2709
2886
 
2710
2887
  // copy native wrapper dynamic library
2711
2888
  if (targetOS === "macos") {
2889
+ cpSync(targetPaths.CORE_MACOS, join(appBundleMacOSPath, "libElectrobunCore.dylib"), {
2890
+ dereference: true,
2891
+ });
2892
+
2712
2893
  const nativeWrapperMacosSource = targetPaths.NATIVE_WRAPPER_MACOS;
2713
2894
  const nativeWrapperMacosDestination = join(
2714
2895
  appBundleMacOSPath,
@@ -2718,6 +2899,10 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
2718
2899
  dereference: true,
2719
2900
  });
2720
2901
  } else if (targetOS === "win") {
2902
+ cpSync(targetPaths.CORE_WIN, join(appBundleMacOSPath, "ElectrobunCore.dll"), {
2903
+ dereference: true,
2904
+ });
2905
+
2721
2906
  const nativeWrapperMacosSource = targetPaths.NATIVE_WRAPPER_WIN;
2722
2907
  const nativeWrapperMacosDestination = join(
2723
2908
  appBundleMacOSPath,
@@ -2737,6 +2922,9 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
2737
2922
  } else if (targetOS === "linux") {
2738
2923
  // Choose the appropriate native wrapper based on bundleCEF setting
2739
2924
  const useCEF = config.build.linux?.bundleCEF;
2925
+ cpSync(targetPaths.CORE_LINUX, join(appBundleMacOSPath, "libElectrobunCore.so"), {
2926
+ dereference: true,
2927
+ });
2740
2928
  const nativeWrapperLinuxSource = useCEF
2741
2929
  ? targetPaths.NATIVE_WRAPPER_LINUX_CEF
2742
2930
  : targetPaths.NATIVE_WRAPPER_LINUX;
@@ -2822,13 +3010,7 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
2822
3010
  });
2823
3011
 
2824
3012
  // cef helpers
2825
- const cefHelperNames = [
2826
- "bun Helper",
2827
- "bun Helper (Alerts)",
2828
- "bun Helper (GPU)",
2829
- "bun Helper (Plugin)",
2830
- "bun Helper (Renderer)",
2831
- ];
3013
+ const cefHelperNames = getCEFHelperNames(mainProcess);
2832
3014
 
2833
3015
  const helperSourcePath = targetPaths.CEF_HELPER_MACOS;
2834
3016
  cefHelperNames.forEach((helperName) => {
@@ -2909,13 +3091,7 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
2909
3091
  }
2910
3092
 
2911
3093
  // Copy CEF helper processes with different names
2912
- const cefHelperNames = [
2913
- "bun Helper",
2914
- "bun Helper (Alerts)",
2915
- "bun Helper (GPU)",
2916
- "bun Helper (Plugin)",
2917
- "bun Helper (Renderer)",
2918
- ];
3094
+ const cefHelperNames = getCEFHelperNames(mainProcess);
2919
3095
 
2920
3096
  const helperSourcePath = targetPaths.CEF_HELPER_WIN;
2921
3097
  if (existsSync(helperSourcePath)) {
@@ -3047,13 +3223,7 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
3047
3223
  });
3048
3224
 
3049
3225
  // Copy CEF helper processes with different names
3050
- const cefHelperNames = [
3051
- "bun Helper",
3052
- "bun Helper (Alerts)",
3053
- "bun Helper (GPU)",
3054
- "bun Helper (Plugin)",
3055
- "bun Helper (Renderer)",
3056
- ];
3226
+ const cefHelperNames = getCEFHelperNames(mainProcess);
3057
3227
 
3058
3228
  const helperSourcePath = targetPaths.CEF_HELPER_LINUX;
3059
3229
  if (existsSync(helperSourcePath)) {
@@ -3222,22 +3392,22 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
3222
3392
  }
3223
3393
  } // end if (!isCarrotOnly)
3224
3394
 
3225
- // transpile developer's bun code
3226
- const bunDestFolder = join(appBundleAppCodePath, "bun");
3227
- // Build bun-javascript ts files
3228
- const { entrypoint: _bunEntrypoint, ...bunBuildOptions } = bunConfig;
3229
- const buildResult = await Bun.build({
3230
- ...bunBuildOptions,
3231
- entrypoints: [bunSource],
3232
- outdir: bunDestFolder,
3233
- // minify: true, // todo (yoav): add minify in canary and prod builds
3234
- target: "bun",
3235
- });
3395
+ if (mainProcess === "bun") {
3396
+ // transpile developer's bun code
3397
+ const bunDestFolder = join(appBundleAppCodePath, "bun");
3398
+ const { entrypoint: _bunEntrypoint, ...bunBuildOptions } = bunConfig;
3399
+ const buildResult = await Bun.build({
3400
+ ...bunBuildOptions,
3401
+ entrypoints: [bunSource],
3402
+ outdir: bunDestFolder,
3403
+ target: "bun",
3404
+ });
3236
3405
 
3237
- if (!buildResult.success) {
3238
- console.error("failed to build", bunSource);
3239
- printBuildLogs(buildResult.logs);
3240
- throw new Error("Build failed: bun build failed");
3406
+ if (!buildResult.success) {
3407
+ console.error("failed to build", bunSource);
3408
+ printBuildLogs(buildResult.logs);
3409
+ throw new Error("Build failed: bun build failed");
3410
+ }
3241
3411
  }
3242
3412
 
3243
3413
  // transpile developer's view code
@@ -3676,13 +3846,16 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
3676
3846
  const bundlesCEF = platformConfig?.bundleCEF ?? false;
3677
3847
 
3678
3848
  const buildJsonObj: Record<string, unknown> = {
3849
+ mainProcess,
3679
3850
  defaultRenderer: platformConfig?.defaultRenderer ?? "native",
3680
3851
  availableRenderers: bundlesCEF ? ["native", "cef"] : ["native"],
3681
3852
  runtime: config.runtime ?? {},
3682
3853
  ...(bundlesCEF
3683
3854
  ? { cefVersion: config.build?.cefVersion ?? DEFAULT_CEF_VERSION_STRING }
3684
3855
  : {}),
3685
- bunVersion: config.build?.bunVersion ?? BUN_VERSION,
3856
+ ...(mainProcess === "bun"
3857
+ ? { bunVersion: config.build?.bunVersion ?? BUN_VERSION }
3858
+ : {}),
3686
3859
  ...(config.build?.bunnyBun ? { bunnyBun: config.build.bunnyBun } : {}),
3687
3860
  };
3688
3861
 
@@ -3760,10 +3933,9 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
3760
3933
  // Tar the app bundle for all platforms
3761
3934
  createTar(tarPath, buildFolder, [basename(appBundleFolderPath)]);
3762
3935
 
3763
- // Remove the app bundle folder after tarring (except on Linux where it might be needed for dev)
3764
- if (targetOS !== "linux" || buildEnvironment !== "dev") {
3765
- rmSync(appBundleFolderPath, { recursive: true });
3766
- }
3936
+ // This branch only runs for non-dev release packaging, so the temp app bundle
3937
+ // can always be removed after the tarball is produced.
3938
+ rmSync(appBundleFolderPath, { recursive: true });
3767
3939
 
3768
3940
  // generate bsdiff
3769
3941
  // https://storage.googleapis.com/eggbun-static/electrobun-playground/canary/ElectrobunPlayground-canary.app.tar.zst
@@ -4443,10 +4615,15 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
4443
4615
  const watchDirs = new Set<string>();
4444
4616
 
4445
4617
  // Bun entrypoint directory
4446
- if (config.build.bun?.entrypoint) {
4618
+ if (config.build.mainProcess !== "zig" && config.build.bun?.entrypoint) {
4447
4619
  watchDirs.add(join(projectRoot, dirname(config.build.bun.entrypoint)));
4448
4620
  }
4449
4621
 
4622
+ // Zig entrypoint directory
4623
+ if (config.build.mainProcess === "zig" && config.build.zig?.entrypoint) {
4624
+ watchDirs.add(join(projectRoot, dirname(config.build.zig.entrypoint)));
4625
+ }
4626
+
4450
4627
  // View entrypoint directories
4451
4628
  if (config.build.views) {
4452
4629
  for (const viewConfig of Object.values(config.build.views)) {
@@ -4787,6 +4964,10 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
4787
4964
  ...defaultConfig.build.bun,
4788
4965
  ...(loadedConfig?.build?.bun || {}),
4789
4966
  },
4967
+ zig: {
4968
+ ...defaultConfig.build.zig,
4969
+ ...(loadedConfig?.build?.zig || {}),
4970
+ },
4790
4971
  },
4791
4972
  runtime: {
4792
4973
  ...defaultConfig.runtime,
@@ -5084,13 +5265,10 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
5084
5265
  }
5085
5266
 
5086
5267
  // Sign CEF helper apps (they're in the main Frameworks directory, not inside CEF framework)
5087
- const cefHelperApps = [
5088
- "bun Helper.app",
5089
- "bun Helper (GPU).app",
5090
- "bun Helper (Plugin).app",
5091
- "bun Helper (Alerts).app",
5092
- "bun Helper (Renderer).app",
5093
- ];
5268
+ const mainProcess = config.build.mainProcess ?? "bun";
5269
+ const cefHelperApps = getCEFHelperNames(mainProcess).map(
5270
+ (helperName) => `${helperName}.app`,
5271
+ );
5094
5272
 
5095
5273
  for (const helperApp of cefHelperApps) {
5096
5274
  const helperPath = join(frameworksPath, helperApp);
@@ -1,5 +0,0 @@
1
- let nextWindowId = 1;
2
-
3
- export function getNextWindowId() {
4
- return nextWindowId++;
5
- }