electrobun 0.1.20-beta.0 → 0.2.0-beta.6

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 CHANGED
@@ -8,12 +8,10 @@
8
8
 
9
9
  ## What is Electrobun?
10
10
 
11
- > Electrobun is in the **_very_** early stages. We currently support development on macOS, Windows, and Linux. You can also bundle your app for these platform targets and cross-bundle them from a mac. We're actively working on stabilizing so any bug reports, especially on different platforms are most welcome.
12
-
13
11
  Electrobun aims to be a complete **solution-in-a-box** for building, updating, and shipping ultra fast, tiny, and cross-platform desktop applications written in Typescript.
14
12
  Under the hood it uses <a href="https://bun.sh">bun</a> to execute the main process and to bundle webview typescript, and has native bindings written in <a href="https://ziglang.org/">zig</a>.
15
13
 
16
- Visit <a href="https://www.electrobun.dev/">Electrobun.dev</a> to see api documentation, guides, and more.
14
+ Visit <a href="https://blackboard.sh/electrobun/">https://blackboard.sh/electrobun/</a> to see api documentation, guides, and more.
17
15
 
18
16
  **Project Goals**
19
17
 
@@ -27,6 +25,9 @@ Visit <a href="https://www.electrobun.dev/">Electrobun.dev</a> to see api docume
27
25
 
28
26
  Read about how Electrobun is designed, and why, in our <a href="https://www.electrobun.dev/docs/guides/Architecture/Overview">architecture docs</a>.
29
27
 
28
+ ## Apps Built with Electrobun
29
+ - [Co(lab)](https://blackboard.sh/colab/) - a hybrid web browser + code editor for deep work
30
+
30
31
  ## Roadmap
31
32
 
32
33
  See the <a href="https://github.com/orgs/blackboardsh/projects/5">roadmap</a>
@@ -88,6 +88,20 @@ export interface ElectrobunConfig {
88
88
  */
89
89
  targets?: string;
90
90
 
91
+ /**
92
+ * Enable ASAR archive packaging for bundled assets
93
+ * When enabled, all files in the Resources folder will be packed into an app.asar archive
94
+ * @default false
95
+ */
96
+ useAsar?: boolean;
97
+
98
+ /**
99
+ * Glob patterns for files to exclude from ASAR packing (extract to app.asar.unpacked)
100
+ * Useful for native modules or executables that need to be accessible as regular files
101
+ * @default ["*.node", "*.dll", "*.dylib", "*.so"]
102
+ */
103
+ asarUnpack?: string[];
104
+
91
105
  /**
92
106
  * macOS-specific build configuration
93
107
  */
package/dist/main.js CHANGED
@@ -3,8 +3,9 @@ var __require = import.meta.require;
3
3
 
4
4
  // src/launcher/main.ts
5
5
  import { join, dirname, resolve } from "path";
6
- import { dlopen, suffix, ptr } from "bun:ffi";
7
- import { existsSync } from "fs";
6
+ import { dlopen, suffix, ptr, toArrayBuffer } from "bun:ffi";
7
+ import { existsSync, writeFileSync } from "fs";
8
+ import { tmpdir } from "os";
8
9
  var pathToMacOS = dirname(process.argv0);
9
10
  var libPath = join(pathToMacOS, `libNativeWrapper.${suffix}`);
10
11
  var absoluteLibPath = resolve(libPath);
@@ -66,7 +67,67 @@ function main() {
66
67
  }
67
68
  const pathToLauncherBin = process.argv0;
68
69
  const pathToBinDir = dirname(pathToLauncherBin);
69
- const appEntrypointPath = join(pathToBinDir, "..", "Resources", "app", "bun", "index.js");
70
+ const resourcesDir = join(pathToBinDir, "..", "Resources");
71
+ const asarPath = join(resourcesDir, "app.asar");
72
+ const appFolderPath = join(resourcesDir, "app");
73
+ let appEntrypointPath;
74
+ if (existsSync(asarPath)) {
75
+ console.log(`[LAUNCHER] Loading app code from ASAR: ${asarPath}`);
76
+ const asarLibPath = join(pathToMacOS, `libasar.${suffix}`);
77
+ let asarLib;
78
+ try {
79
+ asarLib = dlopen(asarLibPath, {
80
+ asar_open: { args: ["cstring"], returns: "ptr" },
81
+ asar_read_file: { args: ["ptr", "cstring", "ptr"], returns: "ptr" },
82
+ asar_free_buffer: { args: ["ptr", "u64"], returns: "void" },
83
+ asar_close: { args: ["ptr"], returns: "void" }
84
+ });
85
+ } catch (error) {
86
+ console.error(`[LAUNCHER] Failed to load ASAR library: ${error.message}`);
87
+ throw error;
88
+ }
89
+ const asarArchive = asarLib.symbols.asar_open(ptr(Buffer.from(asarPath + "\x00", "utf8")));
90
+ if (!asarArchive || asarArchive === 0n) {
91
+ console.error(`[LAUNCHER] Failed to open ASAR archive at: ${asarPath}`);
92
+ throw new Error("Failed to open ASAR archive");
93
+ }
94
+ const filePath = "bun/index.js";
95
+ const sizeBuffer = new BigUint64Array(1);
96
+ const fileDataPtr = asarLib.symbols.asar_read_file(asarArchive, ptr(Buffer.from(filePath + "\x00", "utf8")), ptr(sizeBuffer));
97
+ if (!fileDataPtr || fileDataPtr === 0n) {
98
+ console.error(`[LAUNCHER] Failed to read ${filePath} from ASAR`);
99
+ asarLib.symbols.asar_close(asarArchive);
100
+ throw new Error(`Failed to read ${filePath} from ASAR`);
101
+ }
102
+ const fileSize = Number(sizeBuffer[0]);
103
+ console.log(`[LAUNCHER] Read ${fileSize} bytes from ASAR for ${filePath}`);
104
+ const arrayBuffer = toArrayBuffer(fileDataPtr, 0, fileSize);
105
+ const fileData = Buffer.from(arrayBuffer);
106
+ const systemTmpDir = tmpdir();
107
+ const randomFileName = `electrobun-${Date.now()}-${Math.random().toString(36).substring(7)}.js`;
108
+ appEntrypointPath = join(systemTmpDir, randomFileName);
109
+ const wrappedFileData = `
110
+ // Auto-delete temp file after Worker loads it
111
+ const __tempFilePath = "${appEntrypointPath}";
112
+ setTimeout(() => {
113
+ try {
114
+ require("fs").unlinkSync(__tempFilePath);
115
+ console.log("[LAUNCHER] Deleted temp file:", __tempFilePath);
116
+ } catch (error) {
117
+ console.warn("[LAUNCHER] Failed to delete temp file:", error.message);
118
+ }
119
+ }, 100);
120
+
121
+ ${fileData.toString("utf8")}
122
+ `;
123
+ writeFileSync(appEntrypointPath, wrappedFileData);
124
+ console.log(`[LAUNCHER] Wrote app entrypoint to: ${appEntrypointPath}`);
125
+ asarLib.symbols.asar_free_buffer(fileDataPtr, BigInt(fileSize));
126
+ asarLib.symbols.asar_close(asarArchive);
127
+ } else {
128
+ console.log(`[LAUNCHER] Loading app code from flat files`);
129
+ appEntrypointPath = join(appFolderPath, "bun", "index.js");
130
+ }
70
131
  new Worker(appEntrypointPath, {});
71
132
  lib.symbols.runNSApplication(ptr(Buffer.from(identifier + "\x00", "utf8")), ptr(Buffer.from(channel + "\x00", "utf8")));
72
133
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electrobun",
3
- "version": "0.1.20-beta.0",
3
+ "version": "0.2.0-beta.6",
4
4
  "description": "Build ultra fast, tiny, and cross-platform desktop apps with Typescript.",
5
5
  "license": "MIT",
6
6
  "author": "Blackboard Technologies Inc.",
package/src/cli/index.ts CHANGED
@@ -542,6 +542,8 @@ const defaultConfig = {
542
542
  buildFolder: "build",
543
543
  artifactFolder: "artifacts",
544
544
  targets: undefined, // Will default to current platform if not specified
545
+ useAsar: false,
546
+ asarUnpack: undefined, // Glob patterns for files to exclude from ASAR (e.g., ["*.node", "*.dll"])
545
547
  mac: {
546
548
  codesign: false,
547
549
  notarize: false,
@@ -1456,6 +1458,17 @@ if (commandArg === "init") {
1456
1458
  dereference: true,
1457
1459
  });
1458
1460
 
1461
+ // Copy libasar dynamic library for ASAR support
1462
+ const libExt = targetOS === 'win' ? '.dll' : targetOS === 'macos' ? '.dylib' : '.so';
1463
+ const asarLibSource = join(dirname(targetPaths.BSPATCH), 'libasar' + libExt);
1464
+ if (existsSync(asarLibSource)) {
1465
+ const asarLibDestination = join(appBundleMacOSPath, 'libasar' + libExt);
1466
+ cpSync(asarLibSource, asarLibDestination, {
1467
+ recursive: true,
1468
+ dereference: true,
1469
+ });
1470
+ }
1471
+
1459
1472
  // transpile developer's bun code
1460
1473
  const bunDestFolder = join(appBundleAppCodePath, "bun");
1461
1474
  // Build bun-javascript ts files
@@ -1572,6 +1585,73 @@ if (commandArg === "init") {
1572
1585
  process.exit(1);
1573
1586
  }
1574
1587
  }
1588
+
1589
+ // Pack app resources into ASAR archive if enabled
1590
+ if (config.build.useAsar) {
1591
+ console.log("Packing resources into ASAR archive...");
1592
+
1593
+ const asarPath = join(appBundleFolderResourcesPath, "app.asar");
1594
+ const asarUnpackedPath = join(appBundleFolderResourcesPath, "app.asar.unpacked");
1595
+ const zigAsarCli = join(targetPaths.BSPATCH).replace('bspatch', 'zig-asar');
1596
+ const appDirPath = appBundleAppCodePath;
1597
+
1598
+ // Check if app directory exists
1599
+ if (!existsSync(appDirPath)) {
1600
+ console.log("⚠ No app directory found, skipping ASAR creation");
1601
+ } else {
1602
+ // Default unpack patterns for native modules and libraries
1603
+ const defaultUnpackPatterns = ["*.node", "*.dll", "*.dylib", "*.so"];
1604
+ const unpackPatterns = config.build.asarUnpack || defaultUnpackPatterns;
1605
+
1606
+ // Check if zig-asar CLI exists
1607
+ if (!existsSync(zigAsarCli)) {
1608
+ console.error(`zig-asar CLI not found at: ${zigAsarCli}`);
1609
+ console.error("Make sure to run setup/vendoring first");
1610
+ process.exit(1);
1611
+ }
1612
+
1613
+ // Build zig-asar command arguments
1614
+ // Pack the entire app directory
1615
+ const asarArgs = [
1616
+ "pack",
1617
+ appDirPath, // source: entire app directory
1618
+ asarPath, // output asar file
1619
+ ];
1620
+
1621
+ // Add unpack patterns if any
1622
+ if (unpackPatterns.length > 0) {
1623
+ asarArgs.push("--unpack", unpackPatterns.join(","));
1624
+ }
1625
+
1626
+ // Run zig-asar pack
1627
+ const asarResult = Bun.spawnSync([zigAsarCli, ...asarArgs], {
1628
+ stdio: ["ignore", "inherit", "inherit"],
1629
+ cwd: projectRoot,
1630
+ });
1631
+
1632
+ if (asarResult.exitCode !== 0) {
1633
+ console.error("ASAR packing failed with exit code:", asarResult.exitCode);
1634
+ if (asarResult.stderr) {
1635
+ console.error("stderr:", asarResult.stderr.toString());
1636
+ }
1637
+ console.error("Command:", zigAsarCli, ...asarArgs);
1638
+ process.exit(1);
1639
+ }
1640
+
1641
+ // Verify ASAR was created
1642
+ if (!existsSync(asarPath)) {
1643
+ console.error("ASAR file was not created:", asarPath);
1644
+ process.exit(1);
1645
+ }
1646
+
1647
+ console.log("✓ Created app.asar");
1648
+
1649
+ // Remove the entire app folder since it's now packed in ASAR
1650
+ rmdirSync(appDirPath, { recursive: true });
1651
+ console.log("✓ Removed app/ folder (now in ASAR)");
1652
+ }
1653
+ }
1654
+
1575
1655
  // All the unique files are in the bundle now. Create an initial temporary tar file
1576
1656
  // for hashing the contents
1577
1657
  // tar the signed and notarized app bundle