electrobun 0.1.21-beta.0 → 0.2.0-beta.7
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 +4 -3
- package/dist/api/bun/ElectrobunConfig.ts +14 -0
- package/dist/main.js +64 -3
- package/package.json +1 -1
- package/src/cli/index.ts +94 -0
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://
|
|
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
|
|
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
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,87 @@ 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
|
+
|
|
1596
|
+
// Get zig-asar CLI path - on Windows, check if we need the ARM64 version
|
|
1597
|
+
let zigAsarCli = join(targetPaths.BSPATCH).replace('bspatch', 'zig-asar');
|
|
1598
|
+
|
|
1599
|
+
// If the derived path doesn't exist and we're on Windows, try the runtime architecture vendor path
|
|
1600
|
+
if (!existsSync(zigAsarCli) && process.platform === 'win32') {
|
|
1601
|
+
const runtimeArch = process.arch === 'arm64' ? 'arm64' : 'x64';
|
|
1602
|
+
const vendorPath = join(ELECTROBUN_DEP_PATH, 'vendors', 'zig-asar', runtimeArch, 'zig-asar.exe');
|
|
1603
|
+
if (existsSync(vendorPath)) {
|
|
1604
|
+
zigAsarCli = vendorPath;
|
|
1605
|
+
console.log(`Using ${runtimeArch} zig-asar from vendors`);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
const appDirPath = appBundleAppCodePath;
|
|
1610
|
+
|
|
1611
|
+
// Check if app directory exists
|
|
1612
|
+
if (!existsSync(appDirPath)) {
|
|
1613
|
+
console.log("⚠ No app directory found, skipping ASAR creation");
|
|
1614
|
+
} else {
|
|
1615
|
+
// Default unpack patterns for native modules and libraries
|
|
1616
|
+
const defaultUnpackPatterns = ["*.node", "*.dll", "*.dylib", "*.so"];
|
|
1617
|
+
const unpackPatterns = config.build.asarUnpack || defaultUnpackPatterns;
|
|
1618
|
+
|
|
1619
|
+
// Check if zig-asar CLI exists
|
|
1620
|
+
if (!existsSync(zigAsarCli)) {
|
|
1621
|
+
console.error(`zig-asar CLI not found at: ${zigAsarCli}`);
|
|
1622
|
+
console.error("Make sure to run setup/vendoring first");
|
|
1623
|
+
process.exit(1);
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
// Build zig-asar command arguments
|
|
1627
|
+
// Pack the entire app directory
|
|
1628
|
+
const asarArgs = [
|
|
1629
|
+
"pack",
|
|
1630
|
+
appDirPath, // source: entire app directory
|
|
1631
|
+
asarPath, // output asar file
|
|
1632
|
+
];
|
|
1633
|
+
|
|
1634
|
+
// Add unpack patterns if any
|
|
1635
|
+
// Each pattern needs its own --unpack flag
|
|
1636
|
+
for (const pattern of unpackPatterns) {
|
|
1637
|
+
asarArgs.push("--unpack", pattern);
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
// Run zig-asar pack
|
|
1641
|
+
const asarResult = Bun.spawnSync([zigAsarCli, ...asarArgs], {
|
|
1642
|
+
stdio: ["ignore", "inherit", "inherit"],
|
|
1643
|
+
cwd: projectRoot,
|
|
1644
|
+
});
|
|
1645
|
+
|
|
1646
|
+
if (asarResult.exitCode !== 0) {
|
|
1647
|
+
console.error("ASAR packing failed with exit code:", asarResult.exitCode);
|
|
1648
|
+
if (asarResult.stderr) {
|
|
1649
|
+
console.error("stderr:", asarResult.stderr.toString());
|
|
1650
|
+
}
|
|
1651
|
+
console.error("Command:", zigAsarCli, ...asarArgs);
|
|
1652
|
+
process.exit(1);
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
// Verify ASAR was created
|
|
1656
|
+
if (!existsSync(asarPath)) {
|
|
1657
|
+
console.error("ASAR file was not created:", asarPath);
|
|
1658
|
+
process.exit(1);
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
console.log("✓ Created app.asar");
|
|
1662
|
+
|
|
1663
|
+
// Remove the entire app folder since it's now packed in ASAR
|
|
1664
|
+
rmdirSync(appDirPath, { recursive: true });
|
|
1665
|
+
console.log("✓ Removed app/ folder (now in ASAR)");
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1575
1669
|
// All the unique files are in the bundle now. Create an initial temporary tar file
|
|
1576
1670
|
// for hashing the contents
|
|
1577
1671
|
// tar the signed and notarized app bundle
|