electrobun 1.18.1 → 1.18.4-beta.10
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 +37 -14
- package/dash.config.ts +28 -0
- package/dist/api/browser/global.d.ts +6 -0
- package/dist/api/browser/index.ts +162 -44
- package/dist/api/{bun → config}/ElectrobunConfig.ts +109 -40
- package/dist/api/config/validate.test.ts +31 -0
- package/dist/api/config/validate.ts +19 -0
- package/dist/api/preload/.generated/compiled.ts +8 -0
- package/dist/api/{bun/preload → preload}/build.ts +6 -6
- package/dist/api/{bun/preload → preload}/encryption.ts +11 -5
- package/dist/api/{bun/preload → preload}/globals.d.ts +8 -0
- package/dist/api/{bun/preload → preload}/index.ts +18 -8
- package/dist/api/{bun/preload → preload}/webviewTag.ts +32 -3
- package/dist/api/{bun/preload → preload}/wgpuTag.ts +33 -2
- package/dist/api/{bun → sdks/bun}/__tests__/ffi-contract.test.ts +6 -9
- package/dist/api/{bun → sdks/bun}/core/BrowserView.ts +96 -55
- package/dist/api/{bun → sdks/bun}/core/BrowserWindow.ts +20 -30
- package/dist/api/{bun → sdks/bun}/core/BuildConfig.ts +32 -5
- package/dist/api/{bun → sdks/bun}/core/GpuWindow.ts +20 -7
- package/dist/api/sdks/bun/core/Socket.ts +22 -0
- package/dist/api/{bun → sdks/bun}/core/Tray.ts +33 -32
- package/dist/api/{bun → sdks/bun}/core/Updater.ts +10 -10
- package/dist/api/{bun → sdks/bun}/core/Utils.ts +3 -5
- package/dist/api/{bun → sdks/bun}/core/WGPUView.ts +43 -13
- package/dist/api/{bun → sdks/bun}/index.ts +39 -10
- package/dist/api/{bun → sdks/bun}/proc/native.ts +942 -746
- package/dist/api/{bun → sdks/bun}/webGPU.ts +1 -1
- package/dist/api/{bun → sdks/bun}/webgpuAdapter.ts +40 -33
- package/dist/api/shared/build-dependencies.test.ts +48 -0
- package/dist/api/shared/build-dependencies.ts +46 -0
- package/dist/api/shared/go-version.ts +3 -0
- package/dist/api/shared/odin-version.ts +6 -0
- package/dist/api/shared/rust-version.ts +3 -0
- package/dist/go-sdk/callbacks.go +260 -0
- package/dist/go-sdk/electrobun.go +2021 -0
- package/dist/main.js +35 -28
- package/dist/odin-sdk/electrobun/electrobun.odin +2337 -0
- package/dist/preload-full.js +948 -0
- package/dist/preload-sandboxed.js +111 -0
- package/dist/rust-sdk/electrobun.rs +2396 -0
- package/dist/zig-sdk/electrobun.zig +2005 -0
- package/package.json +5 -29
- package/src/cli/index.ts +868 -654
- package/bin/electrobun.cjs +0 -169
- package/dist/api/bun/core/Socket.ts +0 -205
- package/dist/api/bun/core/windowIds.ts +0 -5
- package/dist/api/bun/preload/.generated/compiled.ts +0 -8
- package/dist/api/shared/bun-version.ts +0 -3
- /package/dist/api/{bun/preload → preload}/dragRegions.ts +0 -0
- /package/dist/api/{bun/preload → preload}/events.ts +0 -0
- /package/dist/api/{bun/preload → preload}/index-sandboxed.ts +0 -0
- /package/dist/api/{bun/preload → preload}/internalRpc.ts +0 -0
- /package/dist/api/{bun/preload → preload}/overlaySync.ts +0 -0
- /package/dist/api/{bun → sdks/bun}/core/ApplicationMenu.ts +0 -0
- /package/dist/api/{bun → sdks/bun}/core/ContextMenu.ts +0 -0
- /package/dist/api/{bun → sdks/bun}/core/Paths.ts +0 -0
- /package/dist/api/{bun → sdks/bun}/core/menuRoles.ts +0 -0
- /package/dist/api/{bun → sdks/bun}/events/ApplicationEvents.ts +0 -0
- /package/dist/api/{bun → sdks/bun}/events/event.ts +0 -0
- /package/dist/api/{bun → sdks/bun}/events/eventEmitter.ts +0 -0
- /package/dist/api/{bun → sdks/bun}/events/trayEvents.ts +0 -0
- /package/dist/api/{bun → sdks/bun}/events/webviewEvents.ts +0 -0
- /package/dist/api/{bun → sdks/bun}/events/windowEvents.ts +0 -0
- /package/dist/api/{bun → sdks/bun}/proc/linux.md +0 -0
package/src/cli/index.ts
CHANGED
|
@@ -19,8 +19,11 @@ import { execSync } from "child_process";
|
|
|
19
19
|
import * as readline from "readline";
|
|
20
20
|
import { OS, ARCH } from "../shared/platform";
|
|
21
21
|
import { DEFAULT_CEF_VERSION_STRING } from "../shared/cef-version";
|
|
22
|
-
import {
|
|
22
|
+
import { RUST_VERSION } from "../shared/rust-version";
|
|
23
|
+
import { GO_VERSION } from "../shared/go-version";
|
|
24
|
+
import { ODIN_VERSION } from "../shared/odin-version";
|
|
23
25
|
import { ELECTROBUN_VERSION } from "../shared/electrobun-version";
|
|
26
|
+
import { assertNoLegacyBunMainProcessConfig } from "../config/validate";
|
|
24
27
|
import {
|
|
25
28
|
getAppFileName,
|
|
26
29
|
getBundleFileName,
|
|
@@ -74,6 +77,8 @@ const indexOfElectrobun = process.argv.findIndex((arg) =>
|
|
|
74
77
|
);
|
|
75
78
|
const commandArg = process.argv[indexOfElectrobun + 1] || "build";
|
|
76
79
|
|
|
80
|
+
type MainProcess = "cottontail" | "zig" | "rust" | "go" | "odin";
|
|
81
|
+
|
|
77
82
|
// Walk up from projectRoot to find electrobun in node_modules (supports hoisted monorepo layouts)
|
|
78
83
|
function resolveElectrobunDir(): string {
|
|
79
84
|
let dir = projectRoot;
|
|
@@ -108,9 +113,12 @@ function getPlatformPaths(
|
|
|
108
113
|
|
|
109
114
|
return {
|
|
110
115
|
// Platform-specific binaries (from dist-OS-ARCH/)
|
|
111
|
-
|
|
116
|
+
COTTONTAIL_BINARY: join(platformDistDir, "cottontail") + binExt,
|
|
112
117
|
LAUNCHER_DEV: join(platformDistDir, "electrobun") + binExt,
|
|
113
118
|
LAUNCHER_RELEASE: join(platformDistDir, "launcher") + binExt,
|
|
119
|
+
CORE_MACOS: join(platformDistDir, "libElectrobunCore.dylib"),
|
|
120
|
+
CORE_WIN: join(platformDistDir, "ElectrobunCore.dll"),
|
|
121
|
+
CORE_LINUX: join(platformDistDir, "libElectrobunCore.so"),
|
|
114
122
|
NATIVE_WRAPPER_MACOS: join(platformDistDir, "libNativeWrapper.dylib"),
|
|
115
123
|
NATIVE_WRAPPER_WIN: join(platformDistDir, "libNativeWrapper.dll"),
|
|
116
124
|
NATIVE_WRAPPER_LINUX: join(platformDistDir, "libNativeWrapper.so"),
|
|
@@ -134,6 +142,8 @@ function getPlatformPaths(
|
|
|
134
142
|
// These work with existing package.json and development workflow
|
|
135
143
|
MAIN_JS: join(sharedDistDir, "main.js"),
|
|
136
144
|
API_DIR: join(sharedDistDir, "api"),
|
|
145
|
+
PRELOAD_FULL_JS: join(sharedDistDir, "preload-full.js"),
|
|
146
|
+
PRELOAD_SANDBOXED_JS: join(sharedDistDir, "preload-sandboxed.js"),
|
|
137
147
|
};
|
|
138
148
|
}
|
|
139
149
|
|
|
@@ -141,6 +151,452 @@ function getPlatformPaths(
|
|
|
141
151
|
// @ts-expect-error - reserved for future use
|
|
142
152
|
const _PATHS = getPlatformPaths(OS, ARCH);
|
|
143
153
|
|
|
154
|
+
function getVendoredZigBinaryPath(): string {
|
|
155
|
+
return join(
|
|
156
|
+
ELECTROBUN_DEP_PATH,
|
|
157
|
+
"vendors",
|
|
158
|
+
"zig",
|
|
159
|
+
OS === "win" ? "zig.exe" : "zig",
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function getZigTarget(
|
|
164
|
+
targetOS: "macos" | "win" | "linux",
|
|
165
|
+
targetArch: "arm64" | "x64",
|
|
166
|
+
): string {
|
|
167
|
+
if (targetOS === "win") {
|
|
168
|
+
return "x86_64-windows";
|
|
169
|
+
}
|
|
170
|
+
if (targetOS === "linux") {
|
|
171
|
+
return targetArch === "arm64" ? "aarch64-linux" : "x86_64-linux";
|
|
172
|
+
}
|
|
173
|
+
return targetArch === "arm64" ? "aarch64-macos" : "x86_64-macos";
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function getRustTarget(
|
|
177
|
+
targetOS: "macos" | "win" | "linux",
|
|
178
|
+
targetArch: "arm64" | "x64",
|
|
179
|
+
): string {
|
|
180
|
+
if (targetOS === "win") {
|
|
181
|
+
return targetArch === "arm64"
|
|
182
|
+
? "aarch64-pc-windows-msvc"
|
|
183
|
+
: "x86_64-pc-windows-msvc";
|
|
184
|
+
}
|
|
185
|
+
if (targetOS === "linux") {
|
|
186
|
+
return targetArch === "arm64"
|
|
187
|
+
? "aarch64-unknown-linux-gnu"
|
|
188
|
+
: "x86_64-unknown-linux-gnu";
|
|
189
|
+
}
|
|
190
|
+
return targetArch === "arm64"
|
|
191
|
+
? "aarch64-apple-darwin"
|
|
192
|
+
: "x86_64-apple-darwin";
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function getVendoredRustBinaryPath(): string {
|
|
196
|
+
return join(
|
|
197
|
+
ELECTROBUN_DEP_PATH,
|
|
198
|
+
"vendors",
|
|
199
|
+
"rust",
|
|
200
|
+
"bin",
|
|
201
|
+
OS === "win" ? "rustc.exe" : "rustc",
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function getGoTarget(
|
|
206
|
+
targetOS: "macos" | "win" | "linux",
|
|
207
|
+
targetArch: "arm64" | "x64",
|
|
208
|
+
): { goOS: string; goArch: string } {
|
|
209
|
+
return {
|
|
210
|
+
goOS:
|
|
211
|
+
targetOS === "macos" ? "darwin" : targetOS === "win" ? "windows" : "linux",
|
|
212
|
+
goArch: targetArch === "arm64" ? "arm64" : "amd64",
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function getVendoredGoBinaryPath(): string {
|
|
217
|
+
return join(
|
|
218
|
+
ELECTROBUN_DEP_PATH,
|
|
219
|
+
"vendors",
|
|
220
|
+
"go",
|
|
221
|
+
"bin",
|
|
222
|
+
OS === "win" ? "go.exe" : "go",
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function getVendoredOdinBinaryPath(): string {
|
|
227
|
+
return join(
|
|
228
|
+
ELECTROBUN_DEP_PATH,
|
|
229
|
+
"vendors",
|
|
230
|
+
"odin",
|
|
231
|
+
OS === "win" ? "odin.exe" : "odin",
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function getCEFHelperNames(): string[] {
|
|
236
|
+
const baseName = "main";
|
|
237
|
+
return [
|
|
238
|
+
`${baseName} Helper`,
|
|
239
|
+
`${baseName} Helper (Alerts)`,
|
|
240
|
+
`${baseName} Helper (GPU)`,
|
|
241
|
+
`${baseName} Helper (Plugin)`,
|
|
242
|
+
`${baseName} Helper (Renderer)`,
|
|
243
|
+
];
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function buildZigMainExecutable(options: {
|
|
247
|
+
entrypoint: string;
|
|
248
|
+
buildFolder: string;
|
|
249
|
+
targetOS: "macos" | "win" | "linux";
|
|
250
|
+
targetArch: "arm64" | "x64";
|
|
251
|
+
buildEnvironment: "dev" | "canary" | "stable";
|
|
252
|
+
}) {
|
|
253
|
+
const zigBinary = getVendoredZigBinaryPath();
|
|
254
|
+
if (!existsSync(zigBinary)) {
|
|
255
|
+
throw new Error(
|
|
256
|
+
`Vendored Zig compiler not found at ${zigBinary}. Rebuild electrobun/package so vendors/zig is available.`,
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const zigSdkPath = join(ELECTROBUN_DEP_PATH, "dist", "zig-sdk", "electrobun.zig");
|
|
261
|
+
if (!existsSync(zigSdkPath)) {
|
|
262
|
+
throw new Error(`Electrobun Zig SDK not found at ${zigSdkPath}`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const binExt = options.targetOS === "win" ? ".exe" : "";
|
|
266
|
+
const tempBuildDir = join(
|
|
267
|
+
options.buildFolder,
|
|
268
|
+
".electrobun-zig-main",
|
|
269
|
+
`${options.targetOS}-${options.targetArch}`,
|
|
270
|
+
);
|
|
271
|
+
const relativeZigSdkPath = path.relative(tempBuildDir, zigSdkPath) || ".";
|
|
272
|
+
const relativeEntrypointPath = path.relative(tempBuildDir, options.entrypoint) || ".";
|
|
273
|
+
const zigOutBin = join(tempBuildDir, "zig-out", "bin", "main" + binExt);
|
|
274
|
+
const buildScriptPath = join(tempBuildDir, "build.zig");
|
|
275
|
+
mkdirSync(tempBuildDir, { recursive: true });
|
|
276
|
+
|
|
277
|
+
const buildScript = `const std = @import("std");
|
|
278
|
+
|
|
279
|
+
pub fn build(b: *std.Build) void {
|
|
280
|
+
const target = b.standardTargetOptions(.{});
|
|
281
|
+
const optimize = b.standardOptimizeOption(.{});
|
|
282
|
+
|
|
283
|
+
const electrobun = b.createModule(.{
|
|
284
|
+
.root_source_file = b.path(${JSON.stringify(relativeZigSdkPath)}),
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
const exe = b.addExecutable(.{
|
|
288
|
+
.name = "main",
|
|
289
|
+
.root_source_file = b.path(${JSON.stringify(relativeEntrypointPath)}),
|
|
290
|
+
.target = target,
|
|
291
|
+
.optimize = optimize,
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
exe.root_module.addImport("electrobun", electrobun);
|
|
295
|
+
exe.linkLibC();
|
|
296
|
+
b.installArtifact(exe);
|
|
297
|
+
}
|
|
298
|
+
`;
|
|
299
|
+
writeFileSync(buildScriptPath, buildScript, "utf8");
|
|
300
|
+
|
|
301
|
+
const zigArgs = [
|
|
302
|
+
"build",
|
|
303
|
+
`-Dtarget=${getZigTarget(options.targetOS, options.targetArch)}`,
|
|
304
|
+
];
|
|
305
|
+
|
|
306
|
+
if (options.targetOS === "win") {
|
|
307
|
+
zigArgs.push("-Dcpu=baseline");
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (options.buildEnvironment !== "dev") {
|
|
311
|
+
zigArgs.push("-Doptimize=ReleaseSmall");
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const result = Bun.spawnSync([zigBinary, ...zigArgs], {
|
|
315
|
+
cwd: tempBuildDir,
|
|
316
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
if (result.exitCode !== 0) {
|
|
320
|
+
const stdout = result.stdout ? new TextDecoder().decode(result.stdout) : "";
|
|
321
|
+
const stderr = result.stderr ? new TextDecoder().decode(result.stderr) : "";
|
|
322
|
+
if (stdout.trim()) {
|
|
323
|
+
console.error(stdout);
|
|
324
|
+
}
|
|
325
|
+
if (stderr.trim()) {
|
|
326
|
+
console.error(stderr);
|
|
327
|
+
}
|
|
328
|
+
throw new Error("Build failed: zig main process compilation failed");
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (!existsSync(zigOutBin)) {
|
|
332
|
+
throw new Error(`Zig main process binary was not produced at ${zigOutBin}`);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return zigOutBin;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async function buildRustMainExecutable(options: {
|
|
339
|
+
entrypoint: string;
|
|
340
|
+
buildFolder: string;
|
|
341
|
+
targetOS: "macos" | "win" | "linux";
|
|
342
|
+
targetArch: "arm64" | "x64";
|
|
343
|
+
buildEnvironment: "dev" | "canary" | "stable";
|
|
344
|
+
}) {
|
|
345
|
+
const rustBinary = getVendoredRustBinaryPath();
|
|
346
|
+
if (!existsSync(rustBinary)) {
|
|
347
|
+
throw new Error(
|
|
348
|
+
`Vendored Rust compiler not found at ${rustBinary}. Rebuild electrobun/package so vendors/rust is available.`,
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const rustSdkPath = join(ELECTROBUN_DEP_PATH, "dist", "rust-sdk", "electrobun.rs");
|
|
353
|
+
if (!existsSync(rustSdkPath)) {
|
|
354
|
+
throw new Error(`Electrobun Rust SDK not found at ${rustSdkPath}`);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const targetTriple = getRustTarget(options.targetOS, options.targetArch);
|
|
358
|
+
const hostTriple = getRustTarget(OS, ARCH);
|
|
359
|
+
if (targetTriple !== hostTriple) {
|
|
360
|
+
throw new Error(
|
|
361
|
+
`Rust main process cross-compilation is not supported yet. Vendored rustc is ${hostTriple}, but the build target is ${targetTriple}.`,
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const binExt = options.targetOS === "win" ? ".exe" : "";
|
|
366
|
+
const tempBuildDir = join(
|
|
367
|
+
options.buildFolder,
|
|
368
|
+
".electrobun-rust-main",
|
|
369
|
+
`${options.targetOS}-${options.targetArch}`,
|
|
370
|
+
);
|
|
371
|
+
const rustOutBin = join(tempBuildDir, "main" + binExt);
|
|
372
|
+
const wrapperPath = join(tempBuildDir, "main.rs");
|
|
373
|
+
mkdirSync(tempBuildDir, { recursive: true });
|
|
374
|
+
|
|
375
|
+
const wrapperSource = `#[path = ${JSON.stringify(rustSdkPath)}]
|
|
376
|
+
pub mod electrobun;
|
|
377
|
+
|
|
378
|
+
#[path = ${JSON.stringify(options.entrypoint)}]
|
|
379
|
+
mod user_main;
|
|
380
|
+
|
|
381
|
+
fn main() {
|
|
382
|
+
user_main::main();
|
|
383
|
+
}
|
|
384
|
+
`;
|
|
385
|
+
writeFileSync(wrapperPath, wrapperSource, "utf8");
|
|
386
|
+
|
|
387
|
+
const rustArgs = [
|
|
388
|
+
"--edition=2021",
|
|
389
|
+
wrapperPath,
|
|
390
|
+
"--target",
|
|
391
|
+
targetTriple,
|
|
392
|
+
"-o",
|
|
393
|
+
rustOutBin,
|
|
394
|
+
];
|
|
395
|
+
|
|
396
|
+
if (options.buildEnvironment === "dev") {
|
|
397
|
+
rustArgs.push("-C", "opt-level=2", "-C", "debuginfo=0");
|
|
398
|
+
} else {
|
|
399
|
+
rustArgs.push("-C", "opt-level=z", "-C", "strip=symbols");
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const result = Bun.spawnSync([rustBinary, ...rustArgs], {
|
|
403
|
+
cwd: tempBuildDir,
|
|
404
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
if (result.exitCode !== 0) {
|
|
408
|
+
const stdout = result.stdout ? new TextDecoder().decode(result.stdout) : "";
|
|
409
|
+
const stderr = result.stderr ? new TextDecoder().decode(result.stderr) : "";
|
|
410
|
+
if (stdout.trim()) {
|
|
411
|
+
console.error(stdout);
|
|
412
|
+
}
|
|
413
|
+
if (stderr.trim()) {
|
|
414
|
+
console.error(stderr);
|
|
415
|
+
}
|
|
416
|
+
throw new Error("Build failed: rust main process compilation failed");
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (!existsSync(rustOutBin)) {
|
|
420
|
+
throw new Error(`Rust main process binary was not produced at ${rustOutBin}`);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return rustOutBin;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
async function buildGoMainExecutable(options: {
|
|
427
|
+
entrypoint: string;
|
|
428
|
+
buildFolder: string;
|
|
429
|
+
targetOS: "macos" | "win" | "linux";
|
|
430
|
+
targetArch: "arm64" | "x64";
|
|
431
|
+
buildEnvironment: "dev" | "canary" | "stable";
|
|
432
|
+
}) {
|
|
433
|
+
const goBinary = getVendoredGoBinaryPath();
|
|
434
|
+
if (!existsSync(goBinary)) {
|
|
435
|
+
throw new Error(
|
|
436
|
+
`Vendored Go compiler not found at ${goBinary}. Rebuild electrobun/package so vendors/go is available.`,
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const goSdkPath = join(ELECTROBUN_DEP_PATH, "dist", "go-sdk");
|
|
441
|
+
if (!existsSync(goSdkPath)) {
|
|
442
|
+
throw new Error(`Electrobun Go SDK not found at ${goSdkPath}`);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const target = getGoTarget(options.targetOS, options.targetArch);
|
|
446
|
+
const host = getGoTarget(OS, ARCH);
|
|
447
|
+
if (target.goOS !== host.goOS || target.goArch !== host.goArch) {
|
|
448
|
+
throw new Error(
|
|
449
|
+
`Go main process cross-compilation is not supported yet. Vendored Go is ${host.goOS}-${host.goArch}, but the build target is ${target.goOS}-${target.goArch}.`,
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const binExt = options.targetOS === "win" ? ".exe" : "";
|
|
454
|
+
const tempBuildDir = join(
|
|
455
|
+
options.buildFolder,
|
|
456
|
+
".electrobun-go-main",
|
|
457
|
+
`${options.targetOS}-${options.targetArch}`,
|
|
458
|
+
);
|
|
459
|
+
const goOutBin = join(tempBuildDir, "main" + binExt);
|
|
460
|
+
const goPath = join(tempBuildDir, "gopath");
|
|
461
|
+
const goSrcPath = join(goPath, "src");
|
|
462
|
+
const sdkDestPath = join(goSrcPath, "electrobun");
|
|
463
|
+
const appDestPath = join(goSrcPath, "electrobun-app");
|
|
464
|
+
const entrypointDir = statSync(options.entrypoint).isDirectory()
|
|
465
|
+
? options.entrypoint
|
|
466
|
+
: dirname(options.entrypoint);
|
|
467
|
+
|
|
468
|
+
rmSync(tempBuildDir, { recursive: true, force: true });
|
|
469
|
+
mkdirSync(goSrcPath, { recursive: true });
|
|
470
|
+
cpSync(goSdkPath, sdkDestPath, { recursive: true, dereference: true });
|
|
471
|
+
cpSync(entrypointDir, appDestPath, { recursive: true, dereference: true });
|
|
472
|
+
|
|
473
|
+
const goArgs = ["build", "-o", goOutBin];
|
|
474
|
+
if (options.buildEnvironment !== "dev") {
|
|
475
|
+
goArgs.push("-ldflags=-s -w");
|
|
476
|
+
}
|
|
477
|
+
goArgs.push("electrobun-app");
|
|
478
|
+
|
|
479
|
+
const result = Bun.spawnSync([goBinary, ...goArgs], {
|
|
480
|
+
cwd: tempBuildDir,
|
|
481
|
+
env: {
|
|
482
|
+
...process.env,
|
|
483
|
+
CGO_ENABLED: "1",
|
|
484
|
+
GO111MODULE: "off",
|
|
485
|
+
GOARCH: target.goArch,
|
|
486
|
+
GOOS: target.goOS,
|
|
487
|
+
GOPATH: goPath,
|
|
488
|
+
GOROOT: join(ELECTROBUN_DEP_PATH, "vendors", "go"),
|
|
489
|
+
GOTOOLCHAIN: "local",
|
|
490
|
+
...(options.targetOS === "win"
|
|
491
|
+
? { CC: `${getVendoredZigBinaryPath()} cc` }
|
|
492
|
+
: {}),
|
|
493
|
+
},
|
|
494
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
if (result.exitCode !== 0) {
|
|
498
|
+
const stdout = result.stdout ? new TextDecoder().decode(result.stdout) : "";
|
|
499
|
+
const stderr = result.stderr ? new TextDecoder().decode(result.stderr) : "";
|
|
500
|
+
if (stdout.trim()) {
|
|
501
|
+
console.error(stdout);
|
|
502
|
+
}
|
|
503
|
+
if (stderr.trim()) {
|
|
504
|
+
console.error(stderr);
|
|
505
|
+
}
|
|
506
|
+
throw new Error("Build failed: go main process compilation failed");
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (!existsSync(goOutBin)) {
|
|
510
|
+
throw new Error(`Go main process binary was not produced at ${goOutBin}`);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
return goOutBin;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
async function buildOdinMainExecutable(options: {
|
|
517
|
+
entrypoint: string;
|
|
518
|
+
buildFolder: string;
|
|
519
|
+
targetOS: "macos" | "win" | "linux";
|
|
520
|
+
targetArch: "arm64" | "x64";
|
|
521
|
+
buildEnvironment: "dev" | "canary" | "stable";
|
|
522
|
+
}) {
|
|
523
|
+
const odinBinary = getVendoredOdinBinaryPath();
|
|
524
|
+
if (!existsSync(odinBinary)) {
|
|
525
|
+
throw new Error(
|
|
526
|
+
`Vendored Odin compiler not found at ${odinBinary}. Rebuild electrobun/package so vendors/odin is available.`,
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// dist/odin-sdk is exposed as the "electrobun_sdk" collection; the SDK itself
|
|
531
|
+
// is the "electrobun" package inside it (import "electrobun_sdk:electrobun").
|
|
532
|
+
const odinSdkCollectionPath = join(ELECTROBUN_DEP_PATH, "dist", "odin-sdk");
|
|
533
|
+
if (!existsSync(join(odinSdkCollectionPath, "electrobun", "electrobun.odin"))) {
|
|
534
|
+
throw new Error(`Electrobun Odin SDK not found at ${odinSdkCollectionPath}`);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Odin can cross-compile object code, but linking still requires the target
|
|
538
|
+
// platform's linker/SDK, so mirror the Rust/Go host-only policy.
|
|
539
|
+
if (options.targetOS !== OS || options.targetArch !== ARCH) {
|
|
540
|
+
throw new Error(
|
|
541
|
+
`Odin main process cross-compilation is not supported yet. Vendored Odin targets ${OS}-${ARCH}, but the build target is ${options.targetOS}-${options.targetArch}.`,
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const binExt = options.targetOS === "win" ? ".exe" : "";
|
|
546
|
+
const tempBuildDir = join(
|
|
547
|
+
options.buildFolder,
|
|
548
|
+
".electrobun-odin-main",
|
|
549
|
+
`${options.targetOS}-${options.targetArch}`,
|
|
550
|
+
);
|
|
551
|
+
const odinOutBin = join(tempBuildDir, "main" + binExt);
|
|
552
|
+
mkdirSync(tempBuildDir, { recursive: true });
|
|
553
|
+
|
|
554
|
+
// Odin compiles a package (directory); accept a file entrypoint for config
|
|
555
|
+
// symmetry with the other languages and compile its containing package.
|
|
556
|
+
const entrypointDir = statSync(options.entrypoint).isDirectory()
|
|
557
|
+
? options.entrypoint
|
|
558
|
+
: dirname(options.entrypoint);
|
|
559
|
+
|
|
560
|
+
const odinArgs = [
|
|
561
|
+
"build",
|
|
562
|
+
entrypointDir,
|
|
563
|
+
`-out:${odinOutBin}`,
|
|
564
|
+
`-collection:electrobun_sdk=${odinSdkCollectionPath}`,
|
|
565
|
+
];
|
|
566
|
+
|
|
567
|
+
if (options.buildEnvironment !== "dev") {
|
|
568
|
+
odinArgs.push("-o:size");
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const result = Bun.spawnSync([odinBinary, ...odinArgs], {
|
|
572
|
+
cwd: projectRoot,
|
|
573
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
if (result.exitCode !== 0) {
|
|
577
|
+
const stdout = result.stdout ? new TextDecoder().decode(result.stdout) : "";
|
|
578
|
+
const stderr = result.stderr ? new TextDecoder().decode(result.stderr) : "";
|
|
579
|
+
if (stdout.trim()) {
|
|
580
|
+
console.error(stdout);
|
|
581
|
+
}
|
|
582
|
+
if (stderr.trim()) {
|
|
583
|
+
console.error(stderr);
|
|
584
|
+
}
|
|
585
|
+
if (options.targetOS === "win") {
|
|
586
|
+
console.error(
|
|
587
|
+
"Note: Odin on Windows requires the MSVC toolchain (link.exe from Visual Studio Build Tools). See the Odin main process docs for prerequisites.",
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
throw new Error("Build failed: odin main process compilation failed");
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (!existsSync(odinOutBin)) {
|
|
594
|
+
throw new Error(`Odin main process binary was not produced at ${odinOutBin}`);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
return odinOutBin;
|
|
598
|
+
}
|
|
599
|
+
|
|
144
600
|
async function ensureCoreDependencies(
|
|
145
601
|
targetOS?: "macos" | "win" | "linux",
|
|
146
602
|
targetArch?: "arm64" | "x64",
|
|
@@ -154,7 +610,7 @@ async function ensureCoreDependencies(
|
|
|
154
610
|
|
|
155
611
|
// Check platform-specific binaries
|
|
156
612
|
const requiredBinaries = [
|
|
157
|
-
platformPaths.
|
|
613
|
+
platformPaths.COTTONTAIL_BINARY,
|
|
158
614
|
platformPaths.BSDIFF,
|
|
159
615
|
platformPaths.BSPATCH,
|
|
160
616
|
];
|
|
@@ -298,7 +754,7 @@ async function ensureCoreDependencies(
|
|
|
298
754
|
|
|
299
755
|
// Verify extraction completed successfully - check platform-specific binaries only
|
|
300
756
|
const requiredBinaries = [
|
|
301
|
-
platformPaths.
|
|
757
|
+
platformPaths.COTTONTAIL_BINARY,
|
|
302
758
|
platformPaths.BSDIFF,
|
|
303
759
|
platformPaths.BSPATCH,
|
|
304
760
|
platformPaths.ZSTD,
|
|
@@ -339,452 +795,56 @@ async function ensureCoreDependencies(
|
|
|
339
795
|
"Development fallback: copying main.js from platform-specific download to shared dist/",
|
|
340
796
|
);
|
|
341
797
|
mkdirSync(sharedDistPath, { recursive: true });
|
|
342
|
-
cpSync(extractedMainJs, sharedMainJs, { dereference: true });
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
console.log(
|
|
346
|
-
`Core dependencies for ${platformOS}-${platformArch} downloaded and cached successfully`,
|
|
347
|
-
);
|
|
348
|
-
} catch (error: any) {
|
|
349
|
-
console.error(
|
|
350
|
-
`Failed to download core dependencies for ${platformOS}-${platformArch}:`,
|
|
351
|
-
error.message,
|
|
352
|
-
);
|
|
353
|
-
console.error(
|
|
354
|
-
"Please ensure you have an internet connection and the release exists.",
|
|
355
|
-
);
|
|
356
|
-
process.exit(1);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/**
|
|
361
|
-
* Returns the effective CEF directory path. When a custom cefVersion is set,
|
|
362
|
-
* CEF files are stored in node_modules/.electrobun-cache/ which survives
|
|
363
|
-
* both dist rebuilds and bun install (which replaces node_modules/electrobun).
|
|
364
|
-
* When using the default version, returns the standard dist-{platform}/cef/ path.
|
|
365
|
-
*/
|
|
366
|
-
function getEffectiveCEFDir(
|
|
367
|
-
platformOS: "macos" | "win" | "linux",
|
|
368
|
-
platformArch: "arm64" | "x64",
|
|
369
|
-
cefVersion?: string,
|
|
370
|
-
): string {
|
|
371
|
-
if (cefVersion) {
|
|
372
|
-
return join(ELECTROBUN_CACHE_PATH, "cef-override", `${platformOS}-${platformArch}`);
|
|
373
|
-
}
|
|
374
|
-
return getPlatformPaths(platformOS, platformArch).CEF_DIR;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* Returns the effective WGPU directory path. WGPU files are stored in
|
|
379
|
-
* node_modules/.electrobun-cache/ to survive dist rebuilds and bun install.
|
|
380
|
-
*/
|
|
381
|
-
function getEffectiveWGPUDir(
|
|
382
|
-
platformOS: "macos" | "win" | "linux",
|
|
383
|
-
platformArch: "arm64" | "x64",
|
|
384
|
-
): string {
|
|
385
|
-
return join(
|
|
386
|
-
projectRoot,
|
|
387
|
-
"node_modules",
|
|
388
|
-
".electrobun-cache",
|
|
389
|
-
"wgpu",
|
|
390
|
-
`${platformOS}-${platformArch}`,
|
|
391
|
-
);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* Trims an ICU .dat file to only include the specified locales.
|
|
396
|
-
* Uses icupkg (from ICU tools) to list and remove unwanted locale data.
|
|
397
|
-
*/
|
|
398
|
-
async function trimICUData(
|
|
399
|
-
source: string,
|
|
400
|
-
dest: string,
|
|
401
|
-
locales: string[],
|
|
402
|
-
): Promise<void> {
|
|
403
|
-
// Copy the full .dat file first
|
|
404
|
-
cpSync(source, dest);
|
|
405
|
-
|
|
406
|
-
// Try to find icupkg in PATH or common locations
|
|
407
|
-
let icupkgPath = "icupkg";
|
|
408
|
-
try {
|
|
409
|
-
execSync(`${icupkgPath} --help`, { stdio: "ignore" });
|
|
410
|
-
} catch {
|
|
411
|
-
// icupkg not available, skip trimming
|
|
412
|
-
throw new Error(
|
|
413
|
-
"icupkg not found in PATH. Install ICU tools to enable locale trimming.",
|
|
414
|
-
);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// List all items in the .dat file
|
|
418
|
-
const listOutput = execSync(`${icupkgPath} -l "${dest}"`, {
|
|
419
|
-
encoding: "utf-8",
|
|
420
|
-
});
|
|
421
|
-
const allItems = listOutput.split("\n").filter((line) => line.trim());
|
|
422
|
-
|
|
423
|
-
// Locale-specific directories in ICU data
|
|
424
|
-
const localeDirs = [
|
|
425
|
-
"brkitr/",
|
|
426
|
-
"coll/",
|
|
427
|
-
"curr/",
|
|
428
|
-
"lang/",
|
|
429
|
-
"locales/",
|
|
430
|
-
"rbnf/",
|
|
431
|
-
"region/",
|
|
432
|
-
"unit/",
|
|
433
|
-
"zone/",
|
|
434
|
-
];
|
|
435
|
-
|
|
436
|
-
const toRemove = allItems.filter((item) => {
|
|
437
|
-
// Only consider items in locale-specific directories
|
|
438
|
-
const isLocaleItem = localeDirs.some((dir) => item.startsWith(dir));
|
|
439
|
-
if (!isLocaleItem) return false;
|
|
440
|
-
|
|
441
|
-
// Extract the basename (after the last /)
|
|
442
|
-
const basename = item.split("/").pop() || "";
|
|
443
|
-
// Remove file extension for matching
|
|
444
|
-
const name = basename.replace(/\.res$/, "");
|
|
445
|
-
|
|
446
|
-
// Keep items matching requested locales (exact match or with region suffix)
|
|
447
|
-
return !locales.some(
|
|
448
|
-
(l) =>
|
|
449
|
-
name === l ||
|
|
450
|
-
name === "root" ||
|
|
451
|
-
name.startsWith(`${l}_`) ||
|
|
452
|
-
name.startsWith(`${l}-`),
|
|
453
|
-
);
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
if (toRemove.length > 0) {
|
|
457
|
-
// Write removal list to temp file
|
|
458
|
-
const { tmpdir } = await import("os");
|
|
459
|
-
const removeListPath = join(tmpdir(), "icu-remove.txt");
|
|
460
|
-
writeFileSync(removeListPath, toRemove.join("\n"));
|
|
461
|
-
|
|
462
|
-
try {
|
|
463
|
-
execSync(`${icupkgPath} -r "@${removeListPath}" "${dest}"`, {
|
|
464
|
-
stdio: "inherit",
|
|
465
|
-
});
|
|
466
|
-
} finally {
|
|
467
|
-
try {
|
|
468
|
-
unlinkSync(removeListPath);
|
|
469
|
-
} catch {
|
|
470
|
-
// ignore cleanup errors
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
/**
|
|
477
|
-
* Ensures the correct Bun binary is available for bundling. When a custom
|
|
478
|
-
* bunVersion is specified in the config, downloads that version from GitHub
|
|
479
|
-
* releases and caches it. Otherwise returns the default binary path.
|
|
480
|
-
*/
|
|
481
|
-
async function ensureBunBinary(
|
|
482
|
-
targetOS: "macos" | "win" | "linux",
|
|
483
|
-
targetArch: "arm64" | "x64",
|
|
484
|
-
bunVersion?: string,
|
|
485
|
-
bunnyBun?: string,
|
|
486
|
-
): Promise<string> {
|
|
487
|
-
const effectiveVersion = bunnyBun || bunVersion;
|
|
488
|
-
if (!effectiveVersion) {
|
|
489
|
-
return getPlatformPaths(targetOS, targetArch).BUN_BINARY;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
const binExt = targetOS === "win" ? ".exe" : "";
|
|
493
|
-
const cacheSubdir = bunnyBun ? "bunny-bun-override" : "bun-override";
|
|
494
|
-
const overrideDir = join(ELECTROBUN_CACHE_PATH, cacheSubdir, `${targetOS}-${targetArch}`);
|
|
495
|
-
const overrideBinary = join(overrideDir, `bun${binExt}`);
|
|
496
|
-
const versionFile = join(overrideDir, ".bun-version");
|
|
497
|
-
|
|
498
|
-
// Check if already downloaded with matching version
|
|
499
|
-
if (existsSync(overrideBinary) && existsSync(versionFile)) {
|
|
500
|
-
const cachedVersion = readFileSync(versionFile, "utf8").trim();
|
|
501
|
-
if (cachedVersion === effectiveVersion) {
|
|
502
|
-
console.log(
|
|
503
|
-
`${bunnyBun ? "Bunny" : "Custom"} Bun ${effectiveVersion} already cached for ${targetOS}-${targetArch}`,
|
|
504
|
-
);
|
|
505
|
-
return overrideBinary;
|
|
506
|
-
}
|
|
507
|
-
console.log(
|
|
508
|
-
`Cached Bun version "${cachedVersion}" does not match requested "${effectiveVersion}", re-downloading...`,
|
|
509
|
-
);
|
|
510
|
-
rmSync(overrideDir, { recursive: true, force: true });
|
|
511
|
-
} else if (existsSync(overrideDir)) {
|
|
512
|
-
rmSync(overrideDir, { recursive: true, force: true });
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
if (bunnyBun) {
|
|
516
|
-
await downloadBunnyBun(bunnyBun, targetOS, targetArch);
|
|
517
|
-
} else {
|
|
518
|
-
await downloadCustomBun(effectiveVersion, targetOS, targetArch);
|
|
519
|
-
}
|
|
520
|
-
return overrideBinary;
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
/**
|
|
524
|
-
* Downloads a specific Bun version from GitHub releases for a custom version
|
|
525
|
-
* override. The binary is cached in node_modules/.electrobun-cache/bun-override/
|
|
526
|
-
* so it survives dist rebuilds and bun install.
|
|
527
|
-
*/
|
|
528
|
-
async function downloadCustomBun(
|
|
529
|
-
bunVersion: string,
|
|
530
|
-
platformOS: "macos" | "win" | "linux",
|
|
531
|
-
platformArch: "arm64" | "x64",
|
|
532
|
-
) {
|
|
533
|
-
// Map to GitHub release asset names
|
|
534
|
-
let bunUrlSegment: string;
|
|
535
|
-
let bunDirName: string;
|
|
536
|
-
|
|
537
|
-
if (platformOS === "win") {
|
|
538
|
-
bunUrlSegment = "bun-windows-x64-baseline.zip";
|
|
539
|
-
bunDirName = "bun-windows-x64-baseline";
|
|
540
|
-
} else if (platformOS === "macos") {
|
|
541
|
-
bunUrlSegment =
|
|
542
|
-
platformArch === "arm64"
|
|
543
|
-
? "bun-darwin-aarch64.zip"
|
|
544
|
-
: "bun-darwin-x64.zip";
|
|
545
|
-
bunDirName =
|
|
546
|
-
platformArch === "arm64" ? "bun-darwin-aarch64" : "bun-darwin-x64";
|
|
547
|
-
} else if (platformOS === "linux") {
|
|
548
|
-
bunUrlSegment =
|
|
549
|
-
platformArch === "arm64" ? "bun-linux-aarch64.zip" : "bun-linux-x64.zip";
|
|
550
|
-
bunDirName =
|
|
551
|
-
platformArch === "arm64" ? "bun-linux-aarch64" : "bun-linux-x64";
|
|
552
|
-
} else {
|
|
553
|
-
throw new Error(`Unsupported platform for custom Bun: ${platformOS}`);
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
const binExt = platformOS === "win" ? ".exe" : "";
|
|
557
|
-
const overrideDir = join(ELECTROBUN_CACHE_PATH, "bun-override", `${platformOS}-${platformArch}`);
|
|
558
|
-
const overrideBinary = join(overrideDir, `bun${binExt}`);
|
|
559
|
-
const bunUrl = `https://github.com/oven-sh/bun/releases/download/bun-v${bunVersion}/${bunUrlSegment}`;
|
|
560
|
-
|
|
561
|
-
console.log(`Using custom Bun version: ${bunVersion}`);
|
|
562
|
-
console.log(`Downloading from: ${bunUrl}`);
|
|
563
|
-
|
|
564
|
-
mkdirSync(overrideDir, { recursive: true });
|
|
565
|
-
|
|
566
|
-
const tempZipPath = join(overrideDir, "temp.zip");
|
|
567
|
-
|
|
568
|
-
try {
|
|
569
|
-
console.log(`Downloading custom Bun...`);
|
|
570
|
-
const response = await fetch(bunUrl);
|
|
571
|
-
if (!response.ok) {
|
|
572
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
const contentLength = response.headers.get("content-length");
|
|
576
|
-
const totalSize = contentLength ? parseInt(contentLength, 10) : 0;
|
|
577
|
-
const fileStream = createWriteStream(tempZipPath);
|
|
578
|
-
let downloadedSize = 0;
|
|
579
|
-
let lastReportedPercent = -1;
|
|
580
|
-
|
|
581
|
-
if (response.body) {
|
|
582
|
-
const reader = response.body.getReader();
|
|
583
|
-
while (true) {
|
|
584
|
-
const { done, value } = await reader.read();
|
|
585
|
-
if (done) break;
|
|
586
|
-
|
|
587
|
-
const chunk = Buffer.from(value);
|
|
588
|
-
fileStream.write(chunk);
|
|
589
|
-
downloadedSize += chunk.length;
|
|
590
|
-
|
|
591
|
-
if (totalSize > 0) {
|
|
592
|
-
const percent = Math.round((downloadedSize / totalSize) * 100);
|
|
593
|
-
const percentTier = Math.floor(percent / 10) * 10;
|
|
594
|
-
if (percentTier > lastReportedPercent && percentTier <= 100) {
|
|
595
|
-
console.log(
|
|
596
|
-
` Progress: ${percentTier}% (${Math.round(downloadedSize / 1024 / 1024)}MB/${Math.round(totalSize / 1024 / 1024)}MB)`,
|
|
597
|
-
);
|
|
598
|
-
lastReportedPercent = percentTier;
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
await new Promise((resolve, reject) => {
|
|
605
|
-
fileStream.end((error: any) => {
|
|
606
|
-
if (error) reject(error);
|
|
607
|
-
else resolve(void 0);
|
|
608
|
-
});
|
|
609
|
-
});
|
|
610
|
-
|
|
611
|
-
console.log(
|
|
612
|
-
`Download completed (${Math.round(downloadedSize / 1024 / 1024)}MB), extracting...`,
|
|
613
|
-
);
|
|
614
|
-
|
|
615
|
-
// Extract zip file
|
|
616
|
-
if (platformOS === "win") {
|
|
617
|
-
execSync(
|
|
618
|
-
`powershell -command "Expand-Archive -Path '${tempZipPath}' -DestinationPath '${overrideDir}' -Force"`,
|
|
619
|
-
{ stdio: "inherit" },
|
|
620
|
-
);
|
|
621
|
-
} else {
|
|
622
|
-
execSync(`unzip -o ${escapePathForTerminal(tempZipPath)} -d ${escapePathForTerminal(overrideDir)}`, {
|
|
623
|
-
stdio: "inherit",
|
|
624
|
-
});
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
// Move binary from extracted subdirectory to override dir root
|
|
628
|
-
const extractedBinary = join(overrideDir, bunDirName, `bun${binExt}`);
|
|
629
|
-
if (existsSync(extractedBinary)) {
|
|
630
|
-
renameSync(extractedBinary, overrideBinary);
|
|
631
|
-
} else {
|
|
632
|
-
throw new Error(
|
|
633
|
-
`Bun binary not found after extraction at ${extractedBinary}`,
|
|
634
|
-
);
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
// Set execute permissions on non-Windows
|
|
638
|
-
if (platformOS !== "win") {
|
|
639
|
-
execSync(`chmod +x ${escapePathForTerminal(overrideBinary)}`);
|
|
798
|
+
cpSync(extractedMainJs, sharedMainJs, { dereference: true });
|
|
640
799
|
}
|
|
641
800
|
|
|
642
|
-
// Write version stamp
|
|
643
|
-
writeFileSync(join(overrideDir, ".bun-version"), bunVersion);
|
|
644
|
-
|
|
645
|
-
// Clean up
|
|
646
|
-
if (existsSync(tempZipPath)) unlinkSync(tempZipPath);
|
|
647
|
-
const extractedDir = join(overrideDir, bunDirName);
|
|
648
|
-
if (existsSync(extractedDir))
|
|
649
|
-
rmSync(extractedDir, { recursive: true, force: true });
|
|
650
|
-
|
|
651
801
|
console.log(
|
|
652
|
-
`
|
|
802
|
+
`Core dependencies for ${platformOS}-${platformArch} downloaded and cached successfully`,
|
|
653
803
|
);
|
|
654
804
|
} catch (error: any) {
|
|
655
|
-
// Clean up on failure
|
|
656
|
-
if (existsSync(overrideDir)) {
|
|
657
|
-
try {
|
|
658
|
-
rmSync(overrideDir, { recursive: true, force: true });
|
|
659
|
-
} catch {}
|
|
660
|
-
}
|
|
661
|
-
|
|
662
805
|
console.error(
|
|
663
|
-
`Failed to
|
|
806
|
+
`Failed to download core dependencies for ${platformOS}-${platformArch}:`,
|
|
664
807
|
error.message,
|
|
665
808
|
);
|
|
666
809
|
console.error(
|
|
667
|
-
|
|
810
|
+
"Please ensure you have an internet connection and the release exists.",
|
|
668
811
|
);
|
|
669
812
|
process.exit(1);
|
|
670
813
|
}
|
|
671
814
|
}
|
|
672
815
|
|
|
673
816
|
/**
|
|
674
|
-
*
|
|
675
|
-
*
|
|
676
|
-
*
|
|
817
|
+
* Returns the effective CEF directory path. When a custom cefVersion is set,
|
|
818
|
+
* CEF files are stored in node_modules/.electrobun-cache/ which survives
|
|
819
|
+
* both dist rebuilds and package reinstalls.
|
|
820
|
+
* When using the default version, returns the standard dist-{platform}/cef/ path.
|
|
677
821
|
*/
|
|
678
|
-
|
|
679
|
-
releaseTag: string,
|
|
822
|
+
function getEffectiveCEFDir(
|
|
680
823
|
platformOS: "macos" | "win" | "linux",
|
|
681
824
|
platformArch: "arm64" | "x64",
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
// Asset names match the CI artifact names from blackboardsh/bun
|
|
687
|
-
if (platformOS === "win") {
|
|
688
|
-
assetName = "bun-windows-x64.zip";
|
|
689
|
-
dirName = "bun-windows-x64";
|
|
690
|
-
} else if (platformOS === "macos") {
|
|
691
|
-
assetName = platformArch === "arm64" ? "bun-darwin-arm64.zip" : "bun-darwin-x64.zip";
|
|
692
|
-
dirName = platformArch === "arm64" ? "bun-darwin-arm64" : "bun-darwin-x64";
|
|
693
|
-
} else {
|
|
694
|
-
assetName = platformArch === "arm64" ? "bun-linux-arm64.zip" : "bun-linux-x64.zip";
|
|
695
|
-
dirName = platformArch === "arm64" ? "bun-linux-arm64" : "bun-linux-x64";
|
|
825
|
+
cefVersion?: string,
|
|
826
|
+
): string {
|
|
827
|
+
if (cefVersion) {
|
|
828
|
+
return join(ELECTROBUN_CACHE_PATH, "cef-override", `${platformOS}-${platformArch}`);
|
|
696
829
|
}
|
|
830
|
+
return getPlatformPaths(platformOS, platformArch).CEF_DIR;
|
|
831
|
+
}
|
|
697
832
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
const contentLength = response.headers.get("content-length");
|
|
716
|
-
const totalSize = contentLength ? parseInt(contentLength, 10) : 0;
|
|
717
|
-
const fileStream = createWriteStream(tempZipPath);
|
|
718
|
-
let downloadedSize = 0;
|
|
719
|
-
let lastReportedPercent = -1;
|
|
720
|
-
|
|
721
|
-
if (response.body) {
|
|
722
|
-
const reader = response.body.getReader();
|
|
723
|
-
while (true) {
|
|
724
|
-
const { done, value } = await reader.read();
|
|
725
|
-
if (done) break;
|
|
726
|
-
const chunk = Buffer.from(value);
|
|
727
|
-
fileStream.write(chunk);
|
|
728
|
-
downloadedSize += chunk.length;
|
|
729
|
-
if (totalSize > 0) {
|
|
730
|
-
const percent = Math.round((downloadedSize / totalSize) * 100);
|
|
731
|
-
const percentTier = Math.floor(percent / 10) * 10;
|
|
732
|
-
if (percentTier > lastReportedPercent && percentTier <= 100) {
|
|
733
|
-
console.log(` Progress: ${percentTier}% (${Math.round(downloadedSize / 1024 / 1024)}MB/${Math.round(totalSize / 1024 / 1024)}MB)`);
|
|
734
|
-
lastReportedPercent = percentTier;
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
await new Promise((resolve, reject) => {
|
|
741
|
-
fileStream.end((error: any) => { if (error) reject(error); else resolve(void 0); });
|
|
742
|
-
});
|
|
743
|
-
|
|
744
|
-
console.log(`Download completed (${Math.round(downloadedSize / 1024 / 1024)}MB), extracting...`);
|
|
745
|
-
|
|
746
|
-
if (platformOS === "win") {
|
|
747
|
-
execSync(`powershell -command "Expand-Archive -Path '${tempZipPath}' -DestinationPath '${overrideDir}' -Force"`, { stdio: "inherit" });
|
|
748
|
-
} else {
|
|
749
|
-
execSync(`unzip -o ${escapePathForTerminal(tempZipPath)} -d ${escapePathForTerminal(overrideDir)}`, { stdio: "inherit" });
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
// Move binary from extracted subdirectory
|
|
753
|
-
const extractedBinary = join(overrideDir, dirName, `bun${binExt}`);
|
|
754
|
-
if (existsSync(extractedBinary)) {
|
|
755
|
-
renameSync(extractedBinary, overrideBinary);
|
|
756
|
-
} else {
|
|
757
|
-
throw new Error(`Bun binary not found after extraction at ${extractedBinary}`);
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
if (platformOS !== "win") {
|
|
761
|
-
execSync(`chmod +x ${escapePathForTerminal(overrideBinary)}`);
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
// Also extract ICU data if present
|
|
765
|
-
const extractedDir = join(overrideDir, dirName);
|
|
766
|
-
if (existsSync(extractedDir)) {
|
|
767
|
-
for (const file of readdirSync(extractedDir)) {
|
|
768
|
-
if (file.endsWith(".dat")) {
|
|
769
|
-
renameSync(join(extractedDir, file), join(overrideDir, file));
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
writeFileSync(join(overrideDir, ".bun-version"), releaseTag);
|
|
775
|
-
|
|
776
|
-
if (existsSync(tempZipPath)) unlinkSync(tempZipPath);
|
|
777
|
-
if (existsSync(extractedDir)) rmSync(extractedDir, { recursive: true, force: true });
|
|
778
|
-
|
|
779
|
-
console.log(`Bunny Bun ${releaseTag} for ${platformOS}-${platformArch} set up successfully`);
|
|
780
|
-
} catch (error: any) {
|
|
781
|
-
if (existsSync(overrideDir)) {
|
|
782
|
-
try { rmSync(overrideDir, { recursive: true, force: true }); } catch {}
|
|
783
|
-
}
|
|
784
|
-
console.error(`Failed to set up Bunny Bun ${releaseTag} for ${platformOS}-${platformArch}:`, error.message);
|
|
785
|
-
console.error(`\nVerify the release tag exists at: https://github.com/blackboardsh/bun/releases`);
|
|
786
|
-
process.exit(1);
|
|
787
|
-
}
|
|
833
|
+
/**
|
|
834
|
+
* Returns the effective WGPU directory path. WGPU files are stored in
|
|
835
|
+
* node_modules/.electrobun-cache/ to survive dist rebuilds and package reinstalls.
|
|
836
|
+
*/
|
|
837
|
+
function getEffectiveWGPUDir(
|
|
838
|
+
platformOS: "macos" | "win" | "linux",
|
|
839
|
+
platformArch: "arm64" | "x64",
|
|
840
|
+
): string {
|
|
841
|
+
return join(
|
|
842
|
+
projectRoot,
|
|
843
|
+
"node_modules",
|
|
844
|
+
".electrobun-cache",
|
|
845
|
+
"wgpu",
|
|
846
|
+
`${platformOS}-${platformArch}`,
|
|
847
|
+
);
|
|
788
848
|
}
|
|
789
849
|
|
|
790
850
|
async function ensureCEFDependencies(
|
|
@@ -1306,7 +1366,7 @@ async function downloadAndExtractCustomCEF(
|
|
|
1306
1366
|
console.log(`Using custom CEF version: ${cefVersion}`);
|
|
1307
1367
|
console.log(`Downloading from: ${cefUrl}`);
|
|
1308
1368
|
|
|
1309
|
-
// Store custom CEF in .electrobun-cache so it survives dist rebuilds and
|
|
1369
|
+
// Store custom CEF in .electrobun-cache so it survives dist rebuilds and package reinstalls.
|
|
1310
1370
|
const cefDir = getEffectiveCEFDir(platformOS, platformArch, cefVersion);
|
|
1311
1371
|
console.log(`Caching custom CEF to ${cefDir}`);
|
|
1312
1372
|
mkdirSync(cefDir, { recursive: true });
|
|
@@ -1470,7 +1530,7 @@ type FileAssociation = {
|
|
|
1470
1530
|
};
|
|
1471
1531
|
|
|
1472
1532
|
// Default values merged with user's electrobun.config.ts
|
|
1473
|
-
// For the user-facing type, see ElectrobunConfig in src/
|
|
1533
|
+
// For the user-facing type, see ElectrobunConfig in src/config/ElectrobunConfig.ts
|
|
1474
1534
|
const defaultConfig = {
|
|
1475
1535
|
app: {
|
|
1476
1536
|
name: "MyApp",
|
|
@@ -1483,13 +1543,11 @@ const defaultConfig = {
|
|
|
1483
1543
|
build: {
|
|
1484
1544
|
buildFolder: "build",
|
|
1485
1545
|
artifactFolder: "artifacts",
|
|
1546
|
+
mainProcess: "cottontail" as MainProcess,
|
|
1486
1547
|
useAsar: false,
|
|
1487
1548
|
asarUnpack: undefined as string[] | undefined, // Glob patterns for files to exclude from ASAR (e.g., ["*.node", "*.dll"])
|
|
1488
1549
|
cefVersion: undefined as string | undefined, // Override CEF version: "CEF_VERSION+chromium-CHROMIUM_VERSION"
|
|
1489
1550
|
wgpuVersion: undefined as string | undefined, // Override Dawn (WebGPU) version: "0.2.3" or "v0.2.3-beta.0"
|
|
1490
|
-
bunVersion: undefined as string | undefined, // Override Bun runtime version: "1.4.2"
|
|
1491
|
-
bunnyBun: undefined as string | undefined, // Use Electrobunny's Bun fork: "bunny-bun-abc1234" (release tag from blackboardsh/bun)
|
|
1492
|
-
locales: undefined as string[] | "*" | undefined, // ICU locales subset (Linux/Windows)
|
|
1493
1551
|
mac: {
|
|
1494
1552
|
codesign: false,
|
|
1495
1553
|
createDmg: true,
|
|
@@ -1499,7 +1557,7 @@ const defaultConfig = {
|
|
|
1499
1557
|
entitlements: {
|
|
1500
1558
|
// This entitlement is required for Electrobun apps with a hardened runtime (required for notarization) to run on macos
|
|
1501
1559
|
"com.apple.security.cs.allow-jit": true,
|
|
1502
|
-
// Required for
|
|
1560
|
+
// Required for Cottontail/JSC dynamic code execution and JIT compilation when signed
|
|
1503
1561
|
"com.apple.security.cs.allow-unsigned-executable-memory": true,
|
|
1504
1562
|
"com.apple.security.cs.disable-library-validation": true,
|
|
1505
1563
|
} as Record<string, boolean | string>,
|
|
@@ -1521,9 +1579,21 @@ const defaultConfig = {
|
|
|
1521
1579
|
defaultRenderer: undefined as "native" | "cef" | undefined,
|
|
1522
1580
|
chromiumFlags: undefined as Record<string, string | boolean> | undefined,
|
|
1523
1581
|
},
|
|
1524
|
-
|
|
1582
|
+
cottontail: {
|
|
1525
1583
|
entrypoint: "src/bun/index.ts",
|
|
1526
1584
|
},
|
|
1585
|
+
zig: {
|
|
1586
|
+
entrypoint: "src/zig/main.zig",
|
|
1587
|
+
},
|
|
1588
|
+
rust: {
|
|
1589
|
+
entrypoint: "src/rust/main.rs",
|
|
1590
|
+
},
|
|
1591
|
+
go: {
|
|
1592
|
+
entrypoint: "src/go/main.go",
|
|
1593
|
+
},
|
|
1594
|
+
odin: {
|
|
1595
|
+
entrypoint: "src/odin/main.odin",
|
|
1596
|
+
},
|
|
1527
1597
|
views: undefined as
|
|
1528
1598
|
| Record<string, { entrypoint: string; [key: string]: unknown }>
|
|
1529
1599
|
| undefined,
|
|
@@ -1535,7 +1605,6 @@ const defaultConfig = {
|
|
|
1535
1605
|
name: string;
|
|
1536
1606
|
description?: string;
|
|
1537
1607
|
mode?: "window" | "background";
|
|
1538
|
-
permissions?: Record<string, unknown>;
|
|
1539
1608
|
dependencies?: Record<string, string>;
|
|
1540
1609
|
// Map of remote UI ID → config. Two flavors:
|
|
1541
1610
|
// 1. { name, entrypoint, ...bunBuildOpts } — electrobun builds it via Bun.build
|
|
@@ -1548,6 +1617,24 @@ const defaultConfig = {
|
|
|
1548
1617
|
path?: string;
|
|
1549
1618
|
[key: string]: unknown;
|
|
1550
1619
|
}>;
|
|
1620
|
+
slateUIs?: Record<string, {
|
|
1621
|
+
name?: string;
|
|
1622
|
+
entrypoint?: string;
|
|
1623
|
+
path?: string;
|
|
1624
|
+
[key: string]: unknown;
|
|
1625
|
+
}>;
|
|
1626
|
+
contributions?: {
|
|
1627
|
+
fileActivators?: Array<{
|
|
1628
|
+
baseName?: string;
|
|
1629
|
+
nodeType?: "file" | "dir" | "any";
|
|
1630
|
+
slate: {
|
|
1631
|
+
type: string;
|
|
1632
|
+
name?: string;
|
|
1633
|
+
icon?: string;
|
|
1634
|
+
config?: Record<string, unknown>;
|
|
1635
|
+
};
|
|
1636
|
+
}>;
|
|
1637
|
+
};
|
|
1551
1638
|
carrotOnly?: boolean;
|
|
1552
1639
|
} | undefined,
|
|
1553
1640
|
},
|
|
@@ -2054,8 +2141,8 @@ ${utiDecls}
|
|
|
2054
2141
|
console.log("");
|
|
2055
2142
|
console.log("📦 Next steps:");
|
|
2056
2143
|
console.log(` cd ${projectName}`);
|
|
2057
|
-
console.log("
|
|
2058
|
-
console.log("
|
|
2144
|
+
console.log(" npm install");
|
|
2145
|
+
console.log(" npm start");
|
|
2059
2146
|
console.log("");
|
|
2060
2147
|
console.log("🎉 Happy building with Electrobun!");
|
|
2061
2148
|
console.log("");
|
|
@@ -2066,7 +2153,7 @@ ${utiDecls}
|
|
|
2066
2153
|
console.log(
|
|
2067
2154
|
"Different architecture, different APIs. Do not use Electron patterns.",
|
|
2068
2155
|
);
|
|
2069
|
-
console.log("Docs: https://
|
|
2156
|
+
console.log("Docs: https://framework.blackboard.sh/electrobun/");
|
|
2070
2157
|
console.log(
|
|
2071
2158
|
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
|
|
2072
2159
|
);
|
|
@@ -2078,8 +2165,10 @@ ${utiDecls}
|
|
|
2078
2165
|
// Get environment
|
|
2079
2166
|
const envArg =
|
|
2080
2167
|
process.argv.find((arg) => arg.startsWith("--env="))?.split("=")[1] || "";
|
|
2081
|
-
const buildEnvironment = ["dev", "canary", "stable"].includes(
|
|
2082
|
-
|
|
2168
|
+
const buildEnvironment: "dev" | "canary" | "stable" = ["dev", "canary", "stable"].includes(
|
|
2169
|
+
envArg,
|
|
2170
|
+
)
|
|
2171
|
+
? (envArg as "dev" | "canary" | "stable")
|
|
2083
2172
|
: "dev";
|
|
2084
2173
|
|
|
2085
2174
|
try {
|
|
@@ -2110,7 +2199,7 @@ ${utiDecls}
|
|
|
2110
2199
|
|
|
2111
2200
|
async function runBuild(
|
|
2112
2201
|
config: Awaited<ReturnType<typeof getConfig>>,
|
|
2113
|
-
buildEnvironment:
|
|
2202
|
+
buildEnvironment: "dev" | "canary" | "stable",
|
|
2114
2203
|
) {
|
|
2115
2204
|
// Determine current platform as default target
|
|
2116
2205
|
const currentTarget = { os: OS, arch: ARCH };
|
|
@@ -2158,10 +2247,10 @@ ${utiDecls}
|
|
|
2158
2247
|
if (!hookScript) return;
|
|
2159
2248
|
|
|
2160
2249
|
console.log(`Running ${String(hookName)} script:`, hookScript);
|
|
2161
|
-
//
|
|
2250
|
+
// Hooks execute with the host Cottontail runtime.
|
|
2162
2251
|
const hostPaths = getPlatformPaths(OS, ARCH);
|
|
2163
2252
|
|
|
2164
|
-
const result = Bun.spawnSync([hostPaths.
|
|
2253
|
+
const result = Bun.spawnSync([hostPaths.COTTONTAIL_BINARY, hookScript], {
|
|
2165
2254
|
stdio: ["ignore", "inherit", "inherit"],
|
|
2166
2255
|
cwd: projectRoot,
|
|
2167
2256
|
env: {
|
|
@@ -2189,7 +2278,10 @@ ${utiDecls}
|
|
|
2189
2278
|
new TextDecoder().decode(result.stderr as Uint8Array),
|
|
2190
2279
|
);
|
|
2191
2280
|
}
|
|
2192
|
-
console.error(
|
|
2281
|
+
console.error(
|
|
2282
|
+
"Tried to run with Cottontail at:",
|
|
2283
|
+
hostPaths.COTTONTAIL_BINARY,
|
|
2284
|
+
);
|
|
2193
2285
|
console.error("Script path:", hookScript);
|
|
2194
2286
|
console.error("Working directory:", projectRoot);
|
|
2195
2287
|
throw new Error("Build failed: hook script failed");
|
|
@@ -2392,20 +2484,52 @@ Categories=Utility;Application;
|
|
|
2392
2484
|
|
|
2393
2485
|
// refresh build folder
|
|
2394
2486
|
if (existsSync(buildFolder)) {
|
|
2395
|
-
rmSync(buildFolder, { recursive: true });
|
|
2487
|
+
rmSync(buildFolder, { recursive: true, force: true });
|
|
2396
2488
|
}
|
|
2397
2489
|
mkdirSync(buildFolder, { recursive: true });
|
|
2398
|
-
// bundle bun to build/bun
|
|
2399
|
-
const bunConfig = config.build.bun;
|
|
2400
|
-
const bunSource = join(projectRoot, bunConfig.entrypoint);
|
|
2401
2490
|
|
|
2402
|
-
|
|
2491
|
+
const mainProcess = config.build.mainProcess ?? "cottontail";
|
|
2492
|
+
const cottontailConfig = config.build.cottontail;
|
|
2493
|
+
const cottontailSource = join(projectRoot, cottontailConfig.entrypoint);
|
|
2494
|
+
const zigConfig = config.build.zig;
|
|
2495
|
+
const zigSource = join(projectRoot, zigConfig.entrypoint);
|
|
2496
|
+
const rustConfig = config.build.rust;
|
|
2497
|
+
const rustSource = join(projectRoot, rustConfig.entrypoint);
|
|
2498
|
+
const goConfig = config.build.go;
|
|
2499
|
+
const goSource = join(projectRoot, goConfig.entrypoint);
|
|
2500
|
+
const odinConfig = config.build.odin;
|
|
2501
|
+
const odinSource = join(projectRoot, odinConfig.entrypoint);
|
|
2502
|
+
|
|
2503
|
+
if (mainProcess === "cottontail") {
|
|
2504
|
+
if (!existsSync(cottontailSource)) {
|
|
2505
|
+
throw new Error(
|
|
2506
|
+
`failed to bundle ${cottontailSource} because it doesn't exist.\n You need a config.build.cottontail.entrypoint source file to build.`,
|
|
2507
|
+
);
|
|
2508
|
+
}
|
|
2509
|
+
} else if (mainProcess === "zig" && !existsSync(zigSource)) {
|
|
2510
|
+
throw new Error(
|
|
2511
|
+
`failed to compile ${zigSource} because it doesn't exist.\n You need a config.build.zig.entrypoint source file to build.`,
|
|
2512
|
+
);
|
|
2513
|
+
} else if (mainProcess === "rust" && !existsSync(rustSource)) {
|
|
2514
|
+
throw new Error(
|
|
2515
|
+
`failed to compile ${rustSource} because it doesn't exist.\n You need a config.build.rust.entrypoint source file to build.`,
|
|
2516
|
+
);
|
|
2517
|
+
} else if (mainProcess === "go" && !existsSync(goSource)) {
|
|
2518
|
+
throw new Error(
|
|
2519
|
+
`failed to compile ${goSource} because it doesn't exist.\n You need a config.build.go.entrypoint source file to build.`,
|
|
2520
|
+
);
|
|
2521
|
+
} else if (mainProcess === "odin" && !existsSync(odinSource)) {
|
|
2403
2522
|
throw new Error(
|
|
2404
|
-
`failed to
|
|
2523
|
+
`failed to compile ${odinSource} because it doesn't exist.\n You need a config.build.odin.entrypoint source file to build.`,
|
|
2405
2524
|
);
|
|
2406
2525
|
}
|
|
2407
2526
|
|
|
2408
2527
|
const isCarrotOnly = config.build.carrot?.carrotOnly === true;
|
|
2528
|
+
if (config.build.carrot && mainProcess !== "cottontail") {
|
|
2529
|
+
throw new Error(
|
|
2530
|
+
`build.carrot is not supported with build.mainProcess = "${mainProcess}" yet.`,
|
|
2531
|
+
);
|
|
2532
|
+
}
|
|
2409
2533
|
|
|
2410
2534
|
// build macos bundle
|
|
2411
2535
|
// Use display name (with spaces) for macOS bundle folders, sanitized name for other platforms
|
|
@@ -2419,7 +2543,7 @@ Categories=Utility;Application;
|
|
|
2419
2543
|
targetOS === "macos" ? macOSBundleDisplayName : appFileName;
|
|
2420
2544
|
|
|
2421
2545
|
if (isCarrotOnly) {
|
|
2422
|
-
// For carrot-only builds, create a minimal output structure for
|
|
2546
|
+
// For carrot-only builds, create a minimal output structure for main/view builds
|
|
2423
2547
|
appBundleFolderPath = join(buildFolder, "carrot-build");
|
|
2424
2548
|
appBundleFolderContentsPath = appBundleFolderPath;
|
|
2425
2549
|
appBundleMacOSPath = appBundleFolderPath;
|
|
@@ -2438,8 +2562,40 @@ Categories=Utility;Application;
|
|
|
2438
2562
|
mkdirSync(appBundleAppCodePath, { recursive: true });
|
|
2439
2563
|
}
|
|
2440
2564
|
|
|
2441
|
-
|
|
2442
|
-
|
|
2565
|
+
let nativeMainBinarySourcePath: string | null = null;
|
|
2566
|
+
if (mainProcess === "zig") {
|
|
2567
|
+
nativeMainBinarySourcePath = await buildZigMainExecutable({
|
|
2568
|
+
entrypoint: zigSource,
|
|
2569
|
+
buildFolder,
|
|
2570
|
+
targetOS,
|
|
2571
|
+
targetArch: targetARCH,
|
|
2572
|
+
buildEnvironment,
|
|
2573
|
+
});
|
|
2574
|
+
} else if (mainProcess === "rust") {
|
|
2575
|
+
nativeMainBinarySourcePath = await buildRustMainExecutable({
|
|
2576
|
+
entrypoint: rustSource,
|
|
2577
|
+
buildFolder,
|
|
2578
|
+
targetOS,
|
|
2579
|
+
targetArch: targetARCH,
|
|
2580
|
+
buildEnvironment,
|
|
2581
|
+
});
|
|
2582
|
+
} else if (mainProcess === "go") {
|
|
2583
|
+
nativeMainBinarySourcePath = await buildGoMainExecutable({
|
|
2584
|
+
entrypoint: goSource,
|
|
2585
|
+
buildFolder,
|
|
2586
|
+
targetOS,
|
|
2587
|
+
targetArch: targetARCH,
|
|
2588
|
+
buildEnvironment,
|
|
2589
|
+
});
|
|
2590
|
+
} else if (mainProcess === "odin") {
|
|
2591
|
+
nativeMainBinarySourcePath = await buildOdinMainExecutable({
|
|
2592
|
+
entrypoint: odinSource,
|
|
2593
|
+
buildFolder,
|
|
2594
|
+
targetOS,
|
|
2595
|
+
targetArch: targetARCH,
|
|
2596
|
+
buildEnvironment,
|
|
2597
|
+
});
|
|
2598
|
+
}
|
|
2443
2599
|
|
|
2444
2600
|
// Note: for sandboxed apps, MacOS will use the CFBundleIdentifier to create a unique container for the app,
|
|
2445
2601
|
// mirroring folders like Application Support, Caches, etc. in the user's Library folder that the sandboxed app
|
|
@@ -2498,59 +2654,27 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
|
|
|
2498
2654
|
join(appBundleFolderContentsPath, "Info.plist"),
|
|
2499
2655
|
InfoPlistContents,
|
|
2500
2656
|
);
|
|
2501
|
-
//
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
// # change directory from whatever open was or double clicking on the app to the dir of the bin in the app bundle
|
|
2505
|
-
// cd "$(dirname "$0")"/
|
|
2506
|
-
|
|
2507
|
-
// # Define the log file path
|
|
2508
|
-
// LOG_FILE="$HOME/${logPath}"
|
|
2509
|
-
|
|
2510
|
-
// # Ensure the directory exists
|
|
2511
|
-
// mkdir -p "$(dirname "$LOG_FILE")"
|
|
2512
|
-
|
|
2513
|
-
// if [[ ! -p $LOG_FILE ]]; then
|
|
2514
|
-
// mkfifo $LOG_FILE
|
|
2515
|
-
// fi
|
|
2516
|
-
|
|
2517
|
-
// # Execute bun and redirect stdout and stderr to the log file
|
|
2518
|
-
// ./bun ../Resources/app/bun/index.js >"$LOG_FILE" 2>&1
|
|
2519
|
-
// `;
|
|
2520
|
-
|
|
2521
|
-
// // Launcher binary
|
|
2522
|
-
// // todo (yoav): This will likely be a zig compiled binary in the future
|
|
2523
|
-
// Bun.write(join(appBundleMacOSPath, 'MyApp'), LauncherContents);
|
|
2524
|
-
// chmodSync(join(appBundleMacOSPath, 'MyApp'), '755');
|
|
2525
|
-
// const zigLauncherBinarySource = join(projectRoot, 'node_modules', 'electrobun', 'src', 'launcher', 'zig-out', 'bin', 'launcher');
|
|
2526
|
-
// const zigLauncherDestination = join(appBundleMacOSPath, 'MyApp');
|
|
2527
|
-
// const destLauncherFolder = dirname(zigLauncherDestination);
|
|
2528
|
-
// if (!existsSync(destLauncherFolder)) {
|
|
2529
|
-
// // console.info('creating folder: ', destFolder);
|
|
2530
|
-
// mkdirSync(destLauncherFolder, {recursive: true});
|
|
2531
|
-
// }
|
|
2532
|
-
// cpSync(zigLauncherBinarySource, zigLauncherDestination, {recursive: true, dereference: true});
|
|
2533
|
-
// Copy zig launcher for all platforms
|
|
2534
|
-
const bunCliLauncherBinarySource = targetPaths.LAUNCHER_RELEASE;
|
|
2535
|
-
const bunCliLauncherDestination =
|
|
2657
|
+
// Copy the Zig launcher for all platforms.
|
|
2658
|
+
const launcherBinarySource = targetPaths.LAUNCHER_RELEASE;
|
|
2659
|
+
const launcherDestination =
|
|
2536
2660
|
join(appBundleMacOSPath, "launcher") + targetBinExt;
|
|
2537
|
-
const destLauncherFolder = dirname(
|
|
2661
|
+
const destLauncherFolder = dirname(launcherDestination);
|
|
2538
2662
|
if (!existsSync(destLauncherFolder)) {
|
|
2539
2663
|
mkdirSync(destLauncherFolder, { recursive: true });
|
|
2540
2664
|
}
|
|
2541
2665
|
|
|
2542
|
-
cpSync(
|
|
2666
|
+
cpSync(launcherBinarySource, launcherDestination, {
|
|
2543
2667
|
recursive: true,
|
|
2544
2668
|
dereference: true,
|
|
2545
2669
|
});
|
|
2546
2670
|
|
|
2547
2671
|
// On Windows, ensure launcher has .exe extension
|
|
2548
|
-
//
|
|
2672
|
+
// cpSync may create files without .exe despite the destination path having it.
|
|
2549
2673
|
if (targetOS === "win") {
|
|
2550
2674
|
const launcherWithoutExt = join(appBundleMacOSPath, "launcher");
|
|
2551
2675
|
|
|
2552
2676
|
// Use PowerShell to force rename and add .exe extension
|
|
2553
|
-
//
|
|
2677
|
+
// because PATHEXT can treat launcher and launcher.exe as the same path.
|
|
2554
2678
|
try {
|
|
2555
2679
|
execSync(
|
|
2556
2680
|
`powershell -Command "if (Test-Path '${launcherWithoutExt}') { Rename-Item -Path '${launcherWithoutExt}' -NewName 'launcher.exe' -Force }"`,
|
|
@@ -2595,7 +2719,7 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
|
|
|
2595
2719
|
const rceditDir = dirname(rceditPkgPath);
|
|
2596
2720
|
const rceditX64 = join(rceditDir, "bin", "rcedit-x64.exe");
|
|
2597
2721
|
const rceditExe = existsSync(rceditX64) ? rceditX64 : join(rceditDir, "bin", "rcedit.exe");
|
|
2598
|
-
execFileSync(rceditExe, [
|
|
2722
|
+
execFileSync(rceditExe, [launcherDestination, "--set-icon", iconPath]);
|
|
2599
2723
|
console.log(`Successfully embedded icon into launcher.exe`);
|
|
2600
2724
|
|
|
2601
2725
|
// Clean up temp ICO file
|
|
@@ -2610,105 +2734,98 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
|
|
|
2610
2734
|
}
|
|
2611
2735
|
}
|
|
2612
2736
|
|
|
2613
|
-
cpSync(targetPaths.
|
|
2737
|
+
cpSync(targetPaths.PRELOAD_FULL_JS, join(appBundleFolderResourcesPath, "preload-full.js"), {
|
|
2614
2738
|
dereference: true,
|
|
2615
2739
|
});
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
config.build.bunVersion,
|
|
2623
|
-
config.build.bunnyBun,
|
|
2740
|
+
cpSync(
|
|
2741
|
+
targetPaths.PRELOAD_SANDBOXED_JS,
|
|
2742
|
+
join(appBundleFolderResourcesPath, "preload-sandboxed.js"),
|
|
2743
|
+
{
|
|
2744
|
+
dereference: true,
|
|
2745
|
+
},
|
|
2624
2746
|
);
|
|
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
2747
|
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
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
|
-
}
|
|
2748
|
+
if (mainProcess === "cottontail") {
|
|
2749
|
+
cpSync(targetPaths.MAIN_JS, join(appBundleFolderResourcesPath, "main.js"), {
|
|
2750
|
+
dereference: true,
|
|
2751
|
+
});
|
|
2665
2752
|
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2753
|
+
const cottontailBinarySourcePath = targetPaths.COTTONTAIL_BINARY;
|
|
2754
|
+
if (!existsSync(cottontailBinarySourcePath)) {
|
|
2755
|
+
throw new Error(
|
|
2756
|
+
`Cottontail runtime is missing for ${currentTarget.os}-${currentTarget.arch}: ${cottontailBinarySourcePath}`,
|
|
2757
|
+
);
|
|
2758
|
+
}
|
|
2759
|
+
const cottontailBinaryDestInBundlePath =
|
|
2760
|
+
join(appBundleMacOSPath, "cottontail") + targetBinExt;
|
|
2761
|
+
mkdirSync(dirname(cottontailBinaryDestInBundlePath), {
|
|
2762
|
+
recursive: true,
|
|
2763
|
+
});
|
|
2764
|
+
cpSync(cottontailBinarySourcePath, cottontailBinaryDestInBundlePath, {
|
|
2765
|
+
dereference: true,
|
|
2766
|
+
});
|
|
2673
2767
|
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2768
|
+
// Embed the application icon into the packaged runtime on Windows.
|
|
2769
|
+
if (targetOS === "win" && config.build.win?.icon) {
|
|
2770
|
+
const iconSourcePath =
|
|
2771
|
+
config.build.win.icon.startsWith("/") ||
|
|
2772
|
+
config.build.win.icon.match(/^[a-zA-Z]:/)
|
|
2773
|
+
? config.build.win.icon
|
|
2774
|
+
: join(projectRoot, config.build.win.icon);
|
|
2678
2775
|
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
const icoBuffer = await pngToIco(iconSourcePath);
|
|
2684
|
-
writeFileSync(tempIcoPath, new Uint8Array(icoBuffer));
|
|
2685
|
-
iconPath = tempIcoPath;
|
|
2686
|
-
console.log(
|
|
2687
|
-
`Converted PNG to ICO format for bun.exe: ${tempIcoPath}`,
|
|
2688
|
-
);
|
|
2689
|
-
}
|
|
2776
|
+
if (existsSync(iconSourcePath)) {
|
|
2777
|
+
console.log(`Embedding icon into cottontail.exe: ${iconSourcePath}`);
|
|
2778
|
+
try {
|
|
2779
|
+
let iconPath = iconSourcePath;
|
|
2690
2780
|
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2781
|
+
if (iconSourcePath.toLowerCase().endsWith(".png")) {
|
|
2782
|
+
const pngToIco = (await import("png-to-ico")).default;
|
|
2783
|
+
const tempIcoPath = join(
|
|
2784
|
+
buildFolder,
|
|
2785
|
+
"temp-cottontail-icon.ico",
|
|
2786
|
+
);
|
|
2787
|
+
const icoBuffer = await pngToIco(iconSourcePath);
|
|
2788
|
+
writeFileSync(tempIcoPath, new Uint8Array(icoBuffer));
|
|
2789
|
+
iconPath = tempIcoPath;
|
|
2790
|
+
console.log(
|
|
2791
|
+
`Converted PNG to ICO format for cottontail.exe: ${tempIcoPath}`,
|
|
2792
|
+
);
|
|
2793
|
+
}
|
|
2699
2794
|
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2795
|
+
const { execFileSync } = await import("child_process");
|
|
2796
|
+
const rceditPkgPath = require.resolve("rcedit/package.json");
|
|
2797
|
+
const rceditDir = dirname(rceditPkgPath);
|
|
2798
|
+
const rceditX64 = join(rceditDir, "bin", "rcedit-x64.exe");
|
|
2799
|
+
const rceditExe = existsSync(rceditX64) ? rceditX64 : join(rceditDir, "bin", "rcedit.exe");
|
|
2800
|
+
execFileSync(rceditExe, [
|
|
2801
|
+
cottontailBinaryDestInBundlePath,
|
|
2802
|
+
"--set-icon",
|
|
2803
|
+
iconPath,
|
|
2804
|
+
]);
|
|
2805
|
+
console.log(`Successfully embedded icon into cottontail.exe`);
|
|
2806
|
+
|
|
2807
|
+
if (iconPath !== iconSourcePath && existsSync(iconPath)) {
|
|
2808
|
+
unlinkSync(iconPath);
|
|
2809
|
+
}
|
|
2810
|
+
} catch (error) {
|
|
2811
|
+
console.warn(
|
|
2812
|
+
`Warning: Failed to embed icon into cottontail.exe: ${error}`,
|
|
2813
|
+
);
|
|
2703
2814
|
}
|
|
2704
|
-
} catch (error) {
|
|
2705
|
-
console.warn(`Warning: Failed to embed icon into bun.exe: ${error}`);
|
|
2706
2815
|
}
|
|
2707
2816
|
}
|
|
2817
|
+
} else if (nativeMainBinarySourcePath) {
|
|
2818
|
+
cpSync(nativeMainBinarySourcePath, join(appBundleMacOSPath, "main") + targetBinExt, {
|
|
2819
|
+
dereference: true,
|
|
2820
|
+
});
|
|
2708
2821
|
}
|
|
2709
2822
|
|
|
2710
2823
|
// copy native wrapper dynamic library
|
|
2711
2824
|
if (targetOS === "macos") {
|
|
2825
|
+
cpSync(targetPaths.CORE_MACOS, join(appBundleMacOSPath, "libElectrobunCore.dylib"), {
|
|
2826
|
+
dereference: true,
|
|
2827
|
+
});
|
|
2828
|
+
|
|
2712
2829
|
const nativeWrapperMacosSource = targetPaths.NATIVE_WRAPPER_MACOS;
|
|
2713
2830
|
const nativeWrapperMacosDestination = join(
|
|
2714
2831
|
appBundleMacOSPath,
|
|
@@ -2718,6 +2835,10 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
|
|
|
2718
2835
|
dereference: true,
|
|
2719
2836
|
});
|
|
2720
2837
|
} else if (targetOS === "win") {
|
|
2838
|
+
cpSync(targetPaths.CORE_WIN, join(appBundleMacOSPath, "ElectrobunCore.dll"), {
|
|
2839
|
+
dereference: true,
|
|
2840
|
+
});
|
|
2841
|
+
|
|
2721
2842
|
const nativeWrapperMacosSource = targetPaths.NATIVE_WRAPPER_WIN;
|
|
2722
2843
|
const nativeWrapperMacosDestination = join(
|
|
2723
2844
|
appBundleMacOSPath,
|
|
@@ -2737,6 +2858,9 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
|
|
|
2737
2858
|
} else if (targetOS === "linux") {
|
|
2738
2859
|
// Choose the appropriate native wrapper based on bundleCEF setting
|
|
2739
2860
|
const useCEF = config.build.linux?.bundleCEF;
|
|
2861
|
+
cpSync(targetPaths.CORE_LINUX, join(appBundleMacOSPath, "libElectrobunCore.so"), {
|
|
2862
|
+
dereference: true,
|
|
2863
|
+
});
|
|
2740
2864
|
const nativeWrapperLinuxSource = useCEF
|
|
2741
2865
|
? targetPaths.NATIVE_WRAPPER_LINUX_CEF
|
|
2742
2866
|
: targetPaths.NATIVE_WRAPPER_LINUX;
|
|
@@ -2822,13 +2946,7 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
|
|
|
2822
2946
|
});
|
|
2823
2947
|
|
|
2824
2948
|
// 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
|
-
];
|
|
2949
|
+
const cefHelperNames = getCEFHelperNames();
|
|
2832
2950
|
|
|
2833
2951
|
const helperSourcePath = targetPaths.CEF_HELPER_MACOS;
|
|
2834
2952
|
cefHelperNames.forEach((helperName) => {
|
|
@@ -2909,13 +3027,7 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
|
|
|
2909
3027
|
}
|
|
2910
3028
|
|
|
2911
3029
|
// 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
|
-
];
|
|
3030
|
+
const cefHelperNames = getCEFHelperNames();
|
|
2919
3031
|
|
|
2920
3032
|
const helperSourcePath = targetPaths.CEF_HELPER_WIN;
|
|
2921
3033
|
if (existsSync(helperSourcePath)) {
|
|
@@ -3047,13 +3159,7 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
|
|
|
3047
3159
|
});
|
|
3048
3160
|
|
|
3049
3161
|
// 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
|
-
];
|
|
3162
|
+
const cefHelperNames = getCEFHelperNames();
|
|
3057
3163
|
|
|
3058
3164
|
const helperSourcePath = targetPaths.CEF_HELPER_LINUX;
|
|
3059
3165
|
if (existsSync(helperSourcePath)) {
|
|
@@ -3222,22 +3328,25 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
|
|
|
3222
3328
|
}
|
|
3223
3329
|
} // end if (!isCarrotOnly)
|
|
3224
3330
|
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3331
|
+
if (mainProcess === "cottontail") {
|
|
3332
|
+
// Keep the existing app/bun layout as a source-compatible SDK detail.
|
|
3333
|
+
const mainDestFolder = join(appBundleAppCodePath, "bun");
|
|
3334
|
+
const {
|
|
3335
|
+
entrypoint: _cottontailEntrypoint,
|
|
3336
|
+
...cottontailBuildOptions
|
|
3337
|
+
} = cottontailConfig;
|
|
3338
|
+
const buildResult = await Bun.build({
|
|
3339
|
+
...cottontailBuildOptions,
|
|
3340
|
+
entrypoints: [cottontailSource],
|
|
3341
|
+
outdir: mainDestFolder,
|
|
3342
|
+
target: "bun",
|
|
3343
|
+
});
|
|
3236
3344
|
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3345
|
+
if (!buildResult.success) {
|
|
3346
|
+
console.error("failed to build", cottontailSource);
|
|
3347
|
+
printBuildLogs(buildResult.logs);
|
|
3348
|
+
throw new Error("Build failed: Cottontail main-process bundle failed");
|
|
3349
|
+
}
|
|
3241
3350
|
}
|
|
3242
3351
|
|
|
3243
3352
|
// transpile developer's view code
|
|
@@ -3322,11 +3431,14 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
|
|
|
3322
3431
|
}
|
|
3323
3432
|
mkdirSync(carrotBuildDir, { recursive: true });
|
|
3324
3433
|
|
|
3325
|
-
// Copy the
|
|
3326
|
-
const
|
|
3434
|
+
// Copy the Cottontail main-process bundle as worker.js.
|
|
3435
|
+
const mainOutputDir = join(appBundleAppCodePath, "bun");
|
|
3327
3436
|
// The output filename matches the entrypoint name (e.g., worker.ts → worker.js)
|
|
3328
|
-
const
|
|
3329
|
-
|
|
3437
|
+
const mainEntryName = basename(cottontailConfig.entrypoint).replace(
|
|
3438
|
+
/\.ts$/,
|
|
3439
|
+
".js",
|
|
3440
|
+
);
|
|
3441
|
+
const workerSrc = join(mainOutputDir, mainEntryName);
|
|
3330
3442
|
if (existsSync(workerSrc)) {
|
|
3331
3443
|
cpSync(workerSrc, join(carrotBuildDir, "worker.js"));
|
|
3332
3444
|
}
|
|
@@ -3402,6 +3514,55 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
|
|
|
3402
3514
|
}
|
|
3403
3515
|
}
|
|
3404
3516
|
|
|
3517
|
+
// Build slate UIs if configured.
|
|
3518
|
+
// slateUIs mirrors remoteUIs, but points at an ESM module entry file instead of an HTML page.
|
|
3519
|
+
const resolvedSlateUIs: Record<string, { name: string; path: string }> = {};
|
|
3520
|
+
if (carrotConfig.slateUIs) {
|
|
3521
|
+
for (const slateUIName in carrotConfig.slateUIs) {
|
|
3522
|
+
const slateUIConfig = carrotConfig.slateUIs[slateUIName]!;
|
|
3523
|
+
const label = slateUIConfig.name || slateUIName;
|
|
3524
|
+
|
|
3525
|
+
if (slateUIConfig.entrypoint) {
|
|
3526
|
+
const slateUISource = join(projectRoot, slateUIConfig.entrypoint);
|
|
3527
|
+
if (!existsSync(slateUISource)) {
|
|
3528
|
+
console.error(`Slate UI entrypoint not found: ${slateUISource}`);
|
|
3529
|
+
continue;
|
|
3530
|
+
}
|
|
3531
|
+
const slateUIDestFolder = join(carrotBuildDir, "slate-ui", slateUIName);
|
|
3532
|
+
mkdirSync(slateUIDestFolder, { recursive: true });
|
|
3533
|
+
|
|
3534
|
+
const { entrypoint: _entrypoint, name: _name, path: _path, ...slateUIBuildOptions } = slateUIConfig;
|
|
3535
|
+
const slateUIBuildResult = await Bun.build({
|
|
3536
|
+
...slateUIBuildOptions,
|
|
3537
|
+
entrypoints: [slateUISource],
|
|
3538
|
+
outdir: slateUIDestFolder,
|
|
3539
|
+
target: "browser",
|
|
3540
|
+
format: "esm",
|
|
3541
|
+
});
|
|
3542
|
+
|
|
3543
|
+
if (!slateUIBuildResult.success) {
|
|
3544
|
+
console.error(`Failed to build slate UI: ${slateUIName}`);
|
|
3545
|
+
printBuildLogs(slateUIBuildResult.logs);
|
|
3546
|
+
continue;
|
|
3547
|
+
}
|
|
3548
|
+
|
|
3549
|
+
resolvedSlateUIs[slateUIName] = {
|
|
3550
|
+
name: label,
|
|
3551
|
+
path: `slate-ui/${slateUIName}/index.js`,
|
|
3552
|
+
};
|
|
3553
|
+
} else if (slateUIConfig.path) {
|
|
3554
|
+
resolvedSlateUIs[slateUIName] = {
|
|
3555
|
+
name: label,
|
|
3556
|
+
path: slateUIConfig.path,
|
|
3557
|
+
};
|
|
3558
|
+
} else {
|
|
3559
|
+
console.warn(
|
|
3560
|
+
`Slate UI "${slateUIName}" has neither entrypoint nor path; skipping.`,
|
|
3561
|
+
);
|
|
3562
|
+
}
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3565
|
+
|
|
3405
3566
|
// Write carrot.json manifest
|
|
3406
3567
|
const carrotManifest = {
|
|
3407
3568
|
id: carrotConfig.id,
|
|
@@ -3409,7 +3570,6 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
|
|
|
3409
3570
|
version: config.app.version,
|
|
3410
3571
|
description: carrotConfig.description || config.app.description || "",
|
|
3411
3572
|
mode: carrotConfig.mode || "window",
|
|
3412
|
-
permissions: carrotConfig.permissions || {},
|
|
3413
3573
|
dependencies: Object.fromEntries(
|
|
3414
3574
|
Object.entries(carrotConfig.dependencies || {}).map(([id, spec]) => [
|
|
3415
3575
|
id,
|
|
@@ -3419,9 +3579,15 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
|
|
|
3419
3579
|
: spec,
|
|
3420
3580
|
]),
|
|
3421
3581
|
),
|
|
3582
|
+
contributions:
|
|
3583
|
+
carrotConfig.contributions &&
|
|
3584
|
+
Object.keys(carrotConfig.contributions).length > 0
|
|
3585
|
+
? carrotConfig.contributions
|
|
3586
|
+
: undefined,
|
|
3422
3587
|
worker: { relativePath: "worker.js" },
|
|
3423
3588
|
view: existsSync(viewsSrc) ? { relativePath: "views/index.html" } : undefined,
|
|
3424
3589
|
remoteUIs: Object.keys(resolvedRemoteUIs).length > 0 ? resolvedRemoteUIs : undefined,
|
|
3590
|
+
slateUIs: Object.keys(resolvedSlateUIs).length > 0 ? resolvedSlateUIs : undefined,
|
|
3425
3591
|
};
|
|
3426
3592
|
writeFileSync(
|
|
3427
3593
|
join(carrotBuildDir, "carrot.json"),
|
|
@@ -3637,7 +3803,7 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
|
|
|
3637
3803
|
// so we're being explicit here.
|
|
3638
3804
|
hash = Bun.hash.wyhash(archiveBytes, 43770n).toString(36);
|
|
3639
3805
|
} else {
|
|
3640
|
-
// Fallback for
|
|
3806
|
+
// Fallback for runtimes without Bun.Archive compatibility.
|
|
3641
3807
|
console.warn("Bun.Archive not available, using fallback hash method");
|
|
3642
3808
|
const fileList = Object.keys(bundleFiles).sort().join("\n");
|
|
3643
3809
|
hash = Bun.hash.wyhash(fileList).toString(36);
|
|
@@ -3645,9 +3811,7 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
|
|
|
3645
3811
|
console.timeEnd("Generate Bundle hash");
|
|
3646
3812
|
}
|
|
3647
3813
|
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
// version.json inside the app bundle
|
|
3814
|
+
// version.json inside the app bundle
|
|
3651
3815
|
const versionJsonContent = JSON.stringify({
|
|
3652
3816
|
version: config.app.version,
|
|
3653
3817
|
// The first tar file does not include this, it gets hashed,
|
|
@@ -3676,14 +3840,16 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
|
|
|
3676
3840
|
const bundlesCEF = platformConfig?.bundleCEF ?? false;
|
|
3677
3841
|
|
|
3678
3842
|
const buildJsonObj: Record<string, unknown> = {
|
|
3843
|
+
mainProcess,
|
|
3679
3844
|
defaultRenderer: platformConfig?.defaultRenderer ?? "native",
|
|
3680
3845
|
availableRenderers: bundlesCEF ? ["native", "cef"] : ["native"],
|
|
3681
3846
|
runtime: config.runtime ?? {},
|
|
3682
3847
|
...(bundlesCEF
|
|
3683
3848
|
? { cefVersion: config.build?.cefVersion ?? DEFAULT_CEF_VERSION_STRING }
|
|
3684
3849
|
: {}),
|
|
3685
|
-
|
|
3686
|
-
...(
|
|
3850
|
+
...(mainProcess === "rust" ? { rustVersion: RUST_VERSION } : {}),
|
|
3851
|
+
...(mainProcess === "go" ? { goVersion: GO_VERSION } : {}),
|
|
3852
|
+
...(mainProcess === "odin" ? { odinVersion: ODIN_VERSION } : {}),
|
|
3687
3853
|
};
|
|
3688
3854
|
|
|
3689
3855
|
// Include chromiumFlags only if the developer defined them
|
|
@@ -3720,9 +3886,6 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
|
|
|
3720
3886
|
console.log("skipping codesign");
|
|
3721
3887
|
}
|
|
3722
3888
|
|
|
3723
|
-
// codesign
|
|
3724
|
-
// NOTE: Codesigning fails in dev mode (when using a single-file-executable bun cli as the launcher)
|
|
3725
|
-
// see https://github.com/oven-sh/bun/issues/7208
|
|
3726
3889
|
if (shouldNotarize) {
|
|
3727
3890
|
notarizeAndStaple(appBundleFolderPath, config);
|
|
3728
3891
|
} else {
|
|
@@ -3760,10 +3923,9 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
|
|
|
3760
3923
|
// Tar the app bundle for all platforms
|
|
3761
3924
|
createTar(tarPath, buildFolder, [basename(appBundleFolderPath)]);
|
|
3762
3925
|
|
|
3763
|
-
//
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
}
|
|
3926
|
+
// This branch only runs for non-dev release packaging, so the temp app bundle
|
|
3927
|
+
// can always be removed after the tarball is produced.
|
|
3928
|
+
rmSync(appBundleFolderPath, { recursive: true });
|
|
3767
3929
|
|
|
3768
3930
|
// generate bsdiff
|
|
3769
3931
|
// https://storage.googleapis.com/eggbun-static/electrobun-playground/canary/ElectrobunPlayground-canary.app.tar.zst
|
|
@@ -4422,6 +4584,7 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
|
|
|
4422
4584
|
console.log(
|
|
4423
4585
|
"\n[electrobun dev] Shutting down gracefully... (press Ctrl+C again to force quit)",
|
|
4424
4586
|
);
|
|
4587
|
+
handle.kill();
|
|
4425
4588
|
} else {
|
|
4426
4589
|
console.log("\n[electrobun dev] Force quitting...");
|
|
4427
4590
|
try {
|
|
@@ -4442,9 +4605,34 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
|
|
|
4442
4605
|
// Collect watch directories from config entrypoints
|
|
4443
4606
|
const watchDirs = new Set<string>();
|
|
4444
4607
|
|
|
4445
|
-
//
|
|
4446
|
-
if (
|
|
4447
|
-
|
|
4608
|
+
// Cottontail entrypoint directory
|
|
4609
|
+
if (
|
|
4610
|
+
config.build.mainProcess === "cottontail" &&
|
|
4611
|
+
config.build.cottontail?.entrypoint
|
|
4612
|
+
) {
|
|
4613
|
+
watchDirs.add(
|
|
4614
|
+
join(projectRoot, dirname(config.build.cottontail.entrypoint)),
|
|
4615
|
+
);
|
|
4616
|
+
}
|
|
4617
|
+
|
|
4618
|
+
// Zig entrypoint directory
|
|
4619
|
+
if (config.build.mainProcess === "zig" && config.build.zig?.entrypoint) {
|
|
4620
|
+
watchDirs.add(join(projectRoot, dirname(config.build.zig.entrypoint)));
|
|
4621
|
+
}
|
|
4622
|
+
|
|
4623
|
+
// Rust entrypoint directory
|
|
4624
|
+
if (config.build.mainProcess === "rust" && config.build.rust?.entrypoint) {
|
|
4625
|
+
watchDirs.add(join(projectRoot, dirname(config.build.rust.entrypoint)));
|
|
4626
|
+
}
|
|
4627
|
+
|
|
4628
|
+
// Go entrypoint directory
|
|
4629
|
+
if (config.build.mainProcess === "go" && config.build.go?.entrypoint) {
|
|
4630
|
+
watchDirs.add(join(projectRoot, dirname(config.build.go.entrypoint)));
|
|
4631
|
+
}
|
|
4632
|
+
|
|
4633
|
+
// Odin entrypoint directory
|
|
4634
|
+
if (config.build.mainProcess === "odin" && config.build.odin?.entrypoint) {
|
|
4635
|
+
watchDirs.add(join(projectRoot, dirname(config.build.odin.entrypoint)));
|
|
4448
4636
|
}
|
|
4449
4637
|
|
|
4450
4638
|
// View entrypoint directories
|
|
@@ -4526,17 +4714,37 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
|
|
|
4526
4714
|
);
|
|
4527
4715
|
|
|
4528
4716
|
function shouldIgnore(fullPath: string): boolean {
|
|
4717
|
+
const resolvedFullPath = path.resolve(fullPath);
|
|
4718
|
+
const pathSegments = resolvedFullPath.split(path.sep).filter(Boolean);
|
|
4719
|
+
const genericIgnoredSegments = new Set([
|
|
4720
|
+
"node_modules",
|
|
4721
|
+
path.basename(buildDir),
|
|
4722
|
+
path.basename(artifactDir),
|
|
4723
|
+
".electrobun-cache",
|
|
4724
|
+
]);
|
|
4725
|
+
if (pathSegments.some((segment) => genericIgnoredSegments.has(segment))) {
|
|
4726
|
+
return true;
|
|
4727
|
+
}
|
|
4529
4728
|
// Check built-in ignore dirs
|
|
4530
4729
|
if (
|
|
4531
4730
|
ignoreDirs.some(
|
|
4532
|
-
(ignored) =>
|
|
4533
|
-
|
|
4731
|
+
(ignored) => {
|
|
4732
|
+
const relativeToIgnored = path.relative(ignored, resolvedFullPath);
|
|
4733
|
+
return (
|
|
4734
|
+
relativeToIgnored === "" ||
|
|
4735
|
+
(!relativeToIgnored.startsWith("..") &&
|
|
4736
|
+
!path.isAbsolute(relativeToIgnored))
|
|
4737
|
+
);
|
|
4738
|
+
},
|
|
4534
4739
|
)
|
|
4535
4740
|
) {
|
|
4536
4741
|
return true;
|
|
4537
4742
|
}
|
|
4538
4743
|
// Check user-configured watchIgnore globs (match against project-relative path)
|
|
4539
|
-
const relativePath =
|
|
4744
|
+
const relativePath = path
|
|
4745
|
+
.relative(projectRoot, resolvedFullPath)
|
|
4746
|
+
.split(path.sep)
|
|
4747
|
+
.join("/");
|
|
4540
4748
|
if (ignoreGlobs.some((glob) => glob.match(relativePath))) {
|
|
4541
4749
|
return true;
|
|
4542
4750
|
}
|
|
@@ -4756,6 +4964,8 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
|
|
|
4756
4964
|
}
|
|
4757
4965
|
}
|
|
4758
4966
|
|
|
4967
|
+
assertNoLegacyBunMainProcessConfig(loadedConfig);
|
|
4968
|
+
|
|
4759
4969
|
// todo (yoav): write a deep clone fn
|
|
4760
4970
|
return {
|
|
4761
4971
|
...defaultConfig,
|
|
@@ -4783,9 +4993,21 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
|
|
|
4783
4993
|
...defaultConfig.build.linux,
|
|
4784
4994
|
...(loadedConfig?.build?.linux || {}),
|
|
4785
4995
|
},
|
|
4786
|
-
|
|
4787
|
-
...defaultConfig.build.
|
|
4788
|
-
...(loadedConfig?.build?.
|
|
4996
|
+
cottontail: {
|
|
4997
|
+
...defaultConfig.build.cottontail,
|
|
4998
|
+
...(loadedConfig?.build?.cottontail || {}),
|
|
4999
|
+
},
|
|
5000
|
+
zig: {
|
|
5001
|
+
...defaultConfig.build.zig,
|
|
5002
|
+
...(loadedConfig?.build?.zig || {}),
|
|
5003
|
+
},
|
|
5004
|
+
rust: {
|
|
5005
|
+
...defaultConfig.build.rust,
|
|
5006
|
+
...(loadedConfig?.build?.rust || {}),
|
|
5007
|
+
},
|
|
5008
|
+
go: {
|
|
5009
|
+
...defaultConfig.build.go,
|
|
5010
|
+
...(loadedConfig?.build?.go || {}),
|
|
4789
5011
|
},
|
|
4790
5012
|
},
|
|
4791
5013
|
runtime: {
|
|
@@ -5084,13 +5306,9 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
|
|
|
5084
5306
|
}
|
|
5085
5307
|
|
|
5086
5308
|
// Sign CEF helper apps (they're in the main Frameworks directory, not inside CEF framework)
|
|
5087
|
-
const cefHelperApps =
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
"bun Helper (Plugin).app",
|
|
5091
|
-
"bun Helper (Alerts).app",
|
|
5092
|
-
"bun Helper (Renderer).app",
|
|
5093
|
-
];
|
|
5309
|
+
const cefHelperApps = getCEFHelperNames().map(
|
|
5310
|
+
(helperName) => `${helperName}.app`,
|
|
5311
|
+
);
|
|
5094
5312
|
|
|
5095
5313
|
for (const helperApp of cefHelperApps) {
|
|
5096
5314
|
const helperPath = join(frameworksPath, helperApp);
|
|
@@ -5193,7 +5411,7 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
|
|
|
5193
5411
|
}
|
|
5194
5412
|
}
|
|
5195
5413
|
|
|
5196
|
-
// Sign
|
|
5414
|
+
// Sign native modules in the source-compatible Resources/app/bun path.
|
|
5197
5415
|
const resourcesPath = join(contentsPath, "Resources", "app", "bun");
|
|
5198
5416
|
if (existsSync(resourcesPath)) {
|
|
5199
5417
|
console.log("Signing native modules in Resources/app/bun...");
|
|
@@ -5256,10 +5474,6 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
|
|
|
5256
5474
|
}
|
|
5257
5475
|
|
|
5258
5476
|
let fileToNotarize = appOrDmgPath;
|
|
5259
|
-
// codesign
|
|
5260
|
-
// NOTE: Codesigning fails in dev mode (when using a single-file-executable bun cli as the launcher)
|
|
5261
|
-
// see https://github.com/oven-sh/bun/issues/7208
|
|
5262
|
-
// if (shouldNotarize) {
|
|
5263
5477
|
console.log("notarizing...");
|
|
5264
5478
|
const zipPath = appOrDmgPath + ".zip";
|
|
5265
5479
|
// if (appOrDmgPath.endsWith('.app')) {
|