electrobun 0.0.19-beta.71 → 0.0.19-beta.73
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/dist/api/bun/core/Updater.ts +113 -19
- package/dist/api/bun/proc/native.ts +4 -1
- package/dist/main.js +50 -9
- package/package.json +1 -1
- package/src/cli/index.ts +287 -8
- package/templates/hello-world/electrobun.config +16 -15
|
@@ -5,7 +5,22 @@ import tar from "tar";
|
|
|
5
5
|
import { ZstdInit } from "@oneidentity/zstd-js/wasm";
|
|
6
6
|
import { OS as currentOS, ARCH as currentArch } from '../../shared/platform';
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
// Cross-platform app data directory
|
|
9
|
+
function getAppDataDir(): string {
|
|
10
|
+
switch (currentOS) {
|
|
11
|
+
case 'macos':
|
|
12
|
+
return join(homedir(), "Library", "Application Support");
|
|
13
|
+
case 'win':
|
|
14
|
+
// Use APPDATA environment variable or fallback to default location
|
|
15
|
+
return process.env.APPDATA || join(homedir(), "AppData", "Roaming");
|
|
16
|
+
case 'linux':
|
|
17
|
+
// Use XDG_CONFIG_HOME or fallback to ~/.config
|
|
18
|
+
return process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
19
|
+
default:
|
|
20
|
+
// Fallback to home directory with .config
|
|
21
|
+
return join(homedir(), ".config");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
9
24
|
|
|
10
25
|
// todo (yoav): share type with cli
|
|
11
26
|
let localInfo: {
|
|
@@ -214,10 +229,20 @@ const Updater = {
|
|
|
214
229
|
if (currentHash !== latestHash) {
|
|
215
230
|
const cacheBuster = Math.random().toString(36).substring(7);
|
|
216
231
|
const platformFolder = `${localInfo.channel}-${currentOS}-${currentArch}`;
|
|
232
|
+
// Platform-specific tarball naming
|
|
233
|
+
let tarballName: string;
|
|
234
|
+
if (currentOS === 'macos') {
|
|
235
|
+
tarballName = `${appFileName}.app.tar.zst`;
|
|
236
|
+
} else if (currentOS === 'win') {
|
|
237
|
+
tarballName = `${appFileName}.tar.zst`;
|
|
238
|
+
} else {
|
|
239
|
+
tarballName = `${appFileName}.tar.zst`;
|
|
240
|
+
}
|
|
241
|
+
|
|
217
242
|
const urlToLatestTarball = join(
|
|
218
243
|
localInfo.bucketUrl,
|
|
219
244
|
platformFolder,
|
|
220
|
-
|
|
245
|
+
tarballName
|
|
221
246
|
);
|
|
222
247
|
const prevVersionCompressedTarballPath = join(
|
|
223
248
|
appDataFolder,
|
|
@@ -294,47 +319,116 @@ const Updater = {
|
|
|
294
319
|
file: latestTarPath,
|
|
295
320
|
cwd: extractionFolder,
|
|
296
321
|
onentry: (entry) => {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
appBundleSubpath
|
|
322
|
+
if (currentOS === 'macos') {
|
|
323
|
+
// find the first .app bundle in the tarball
|
|
324
|
+
// Some apps may have nested .app bundles
|
|
325
|
+
if (!appBundleSubpath && entry.path.endsWith(".app/")) {
|
|
326
|
+
appBundleSubpath = entry.path;
|
|
327
|
+
}
|
|
328
|
+
} else {
|
|
329
|
+
// For Windows/Linux, look for the main executable
|
|
330
|
+
// This assumes the tarball contains the app at the root
|
|
331
|
+
if (!appBundleSubpath) {
|
|
332
|
+
appBundleSubpath = "./";
|
|
333
|
+
}
|
|
301
334
|
}
|
|
302
335
|
},
|
|
303
336
|
});
|
|
304
337
|
|
|
305
338
|
if (!appBundleSubpath) {
|
|
306
|
-
console.error("Failed to find app
|
|
339
|
+
console.error("Failed to find app in tarball");
|
|
307
340
|
return;
|
|
308
341
|
}
|
|
309
342
|
|
|
310
343
|
// Note: resolve here removes the extra trailing / that the tar file adds
|
|
311
|
-
const
|
|
344
|
+
const extractedAppPath = resolve(
|
|
312
345
|
join(extractionFolder, appBundleSubpath)
|
|
313
346
|
);
|
|
314
|
-
|
|
315
|
-
//
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
".."
|
|
320
|
-
|
|
321
|
-
|
|
347
|
+
|
|
348
|
+
// On Linux, we need to move the app from self-extraction to the app directory
|
|
349
|
+
let newAppBundlePath: string;
|
|
350
|
+
if (currentOS === 'linux') {
|
|
351
|
+
// Get the parent directory (one level up from self-extraction)
|
|
352
|
+
const parentDir = resolve(extractionFolder, "..");
|
|
353
|
+
newAppBundlePath = join(parentDir, "app_new");
|
|
354
|
+
|
|
355
|
+
// Move the extracted app to a temporary location
|
|
356
|
+
renameSync(extractedAppPath, newAppBundlePath);
|
|
357
|
+
} else {
|
|
358
|
+
newAppBundlePath = extractedAppPath;
|
|
359
|
+
}
|
|
360
|
+
// Platform-specific app path calculation
|
|
361
|
+
let runningAppBundlePath: string;
|
|
362
|
+
if (currentOS === 'macos') {
|
|
363
|
+
// On macOS, executable is at Contents/MacOS/binary inside .app bundle
|
|
364
|
+
runningAppBundlePath = resolve(
|
|
365
|
+
dirname(process.execPath),
|
|
366
|
+
"..",
|
|
367
|
+
".."
|
|
368
|
+
);
|
|
369
|
+
} else if (currentOS === 'linux') {
|
|
370
|
+
// On Linux, executable is at app/bin/launcher
|
|
371
|
+
runningAppBundlePath = resolve(
|
|
372
|
+
dirname(process.execPath),
|
|
373
|
+
"..",
|
|
374
|
+
".."
|
|
375
|
+
);
|
|
376
|
+
} else {
|
|
377
|
+
// On Windows, the executable is the app itself
|
|
378
|
+
runningAppBundlePath = process.execPath;
|
|
379
|
+
}
|
|
380
|
+
// Platform-specific backup naming and location
|
|
381
|
+
let backupAppBundlePath: string;
|
|
382
|
+
if (currentOS === 'linux') {
|
|
383
|
+
// On Linux, keep backup in self-extraction folder
|
|
384
|
+
backupAppBundlePath = join(extractionFolder, "backup");
|
|
385
|
+
} else {
|
|
386
|
+
// On macOS/Windows, backup in extraction folder
|
|
387
|
+
const backupName = currentOS === 'macos' ? "backup.app" : "backup";
|
|
388
|
+
backupAppBundlePath = join(extractionFolder, backupName);
|
|
389
|
+
}
|
|
322
390
|
|
|
323
391
|
try {
|
|
324
|
-
//
|
|
392
|
+
// Remove existing backup if it exists
|
|
325
393
|
if (statSync(backupAppBundlePath, { throwIfNoEntry: false })) {
|
|
326
394
|
rmdirSync(backupAppBundlePath, { recursive: true });
|
|
327
395
|
} else {
|
|
328
396
|
console.log("backupAppBundlePath does not exist");
|
|
329
397
|
}
|
|
398
|
+
|
|
399
|
+
// Move current running app to backup
|
|
330
400
|
renameSync(runningAppBundlePath, backupAppBundlePath);
|
|
401
|
+
|
|
402
|
+
// Move new app to running location
|
|
331
403
|
renameSync(newAppBundlePath, runningAppBundlePath);
|
|
404
|
+
|
|
405
|
+
// On Linux, clean up the temporary app_new if it wasn't already moved
|
|
406
|
+
if (currentOS === 'linux') {
|
|
407
|
+
try {
|
|
408
|
+
rmdirSync(newAppBundlePath, { recursive: true });
|
|
409
|
+
} catch {
|
|
410
|
+
// Ignore if already moved
|
|
411
|
+
}
|
|
412
|
+
}
|
|
332
413
|
} catch (error) {
|
|
333
414
|
console.error("Failed to replace app with new version", error);
|
|
334
415
|
return;
|
|
335
416
|
}
|
|
336
417
|
|
|
337
|
-
|
|
418
|
+
// Cross-platform app launch
|
|
419
|
+
switch (currentOS) {
|
|
420
|
+
case 'macos':
|
|
421
|
+
await Bun.spawn(["open", runningAppBundlePath]);
|
|
422
|
+
break;
|
|
423
|
+
case 'win':
|
|
424
|
+
// On Windows, the runningAppBundlePath would be the .exe file
|
|
425
|
+
await Bun.spawn([runningAppBundlePath]);
|
|
426
|
+
break;
|
|
427
|
+
case 'linux':
|
|
428
|
+
// On Linux, directly execute the binary
|
|
429
|
+
await Bun.spawn([runningAppBundlePath]);
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
338
432
|
process.exit(0);
|
|
339
433
|
}
|
|
340
434
|
}
|
|
@@ -349,7 +443,7 @@ const Updater = {
|
|
|
349
443
|
appDataFolder: async () => {
|
|
350
444
|
await Updater.getLocallocalInfo();
|
|
351
445
|
const appDataFolder = join(
|
|
352
|
-
|
|
446
|
+
getAppDataDir(),
|
|
353
447
|
localInfo.identifier,
|
|
354
448
|
localInfo.name
|
|
355
449
|
);
|
|
@@ -247,11 +247,14 @@ export const native = (() => {
|
|
|
247
247
|
// },
|
|
248
248
|
});
|
|
249
249
|
} catch (err) {
|
|
250
|
-
console.log('FATAL Error opening native FFI', err);
|
|
250
|
+
console.log('FATAL Error opening native FFI:', err.message);
|
|
251
251
|
console.log('This may be due to:');
|
|
252
252
|
console.log(' - Missing libNativeWrapper.dll/so/dylib');
|
|
253
253
|
console.log(' - Architecture mismatch (ARM64 vs x64)');
|
|
254
254
|
console.log(' - Missing WebView2 or CEF dependencies');
|
|
255
|
+
if (suffix === 'so') {
|
|
256
|
+
console.log(' - Missing system libraries (try: ldd ./libNativeWrapper.so)');
|
|
257
|
+
}
|
|
255
258
|
console.log('Check that the build process completed successfully for your architecture.');
|
|
256
259
|
process.exit();
|
|
257
260
|
}
|
package/dist/main.js
CHANGED
|
@@ -1,12 +1,53 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
var __require = import.meta.require;
|
|
3
|
+
|
|
2
4
|
// src/launcher/main.ts
|
|
3
|
-
import { join, dirname } from "path";
|
|
5
|
+
import { join, dirname, resolve } from "path";
|
|
4
6
|
import { dlopen, suffix } from "bun:ffi";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
import { existsSync } from "fs";
|
|
8
|
+
var libPath = `./libNativeWrapper.${suffix}`;
|
|
9
|
+
var absoluteLibPath = resolve(libPath);
|
|
10
|
+
function main() {
|
|
11
|
+
if (process.platform === "linux") {
|
|
12
|
+
const cefLibs = ["./libcef.so", "./libvk_swiftshader.so"];
|
|
13
|
+
const existingCefLibs = cefLibs.filter((lib2) => existsSync(lib2));
|
|
14
|
+
if (existingCefLibs.length > 0 && !process.env.LD_PRELOAD) {
|
|
15
|
+
console.error(`[LAUNCHER] ERROR: CEF libraries found but LD_PRELOAD not set!`);
|
|
16
|
+
console.error(`[LAUNCHER] Please run through the wrapper script: ./run.sh`);
|
|
17
|
+
console.error(`[LAUNCHER] Or set: LD_PRELOAD="${existingCefLibs.join(":")}" before starting.`);
|
|
18
|
+
const { spawn } = __require("child_process");
|
|
19
|
+
const env = { ...process.env, LD_PRELOAD: existingCefLibs.join(":") };
|
|
20
|
+
const child = spawn(process.argv[0], process.argv.slice(1), {
|
|
21
|
+
env,
|
|
22
|
+
stdio: "inherit"
|
|
23
|
+
});
|
|
24
|
+
child.on("exit", (code) => process.exit(code));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
let lib;
|
|
29
|
+
try {
|
|
30
|
+
if (!process.env.LD_LIBRARY_PATH?.includes(".")) {
|
|
31
|
+
process.env.LD_LIBRARY_PATH = `.${process.env.LD_LIBRARY_PATH ? ":" + process.env.LD_LIBRARY_PATH : ""}`;
|
|
32
|
+
}
|
|
33
|
+
lib = dlopen(libPath, {
|
|
34
|
+
runNSApplication: { args: [], returns: "void" }
|
|
35
|
+
});
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error(`[LAUNCHER] Failed to load library: ${error.message}`);
|
|
38
|
+
try {
|
|
39
|
+
lib = dlopen(absoluteLibPath, {
|
|
40
|
+
runNSApplication: { args: [], returns: "void" }
|
|
41
|
+
});
|
|
42
|
+
} catch (absError) {
|
|
43
|
+
console.error(`[LAUNCHER] Library loading failed. Try running: ldd ${libPath}`);
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const pathToLauncherBin = process.argv0;
|
|
48
|
+
const pathToMacOS = dirname(pathToLauncherBin);
|
|
49
|
+
const appEntrypointPath = join(pathToMacOS, "..", "Resources", "app", "bun", "index.js");
|
|
50
|
+
new Worker(appEntrypointPath, {});
|
|
51
|
+
lib.symbols.runNSApplication();
|
|
52
|
+
}
|
|
53
|
+
main();
|
package/package.json
CHANGED
package/src/cli/index.ts
CHANGED
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
createWriteStream,
|
|
10
10
|
unlinkSync,
|
|
11
11
|
readdirSync,
|
|
12
|
+
rmSync,
|
|
13
|
+
symlinkSync,
|
|
12
14
|
} from "fs";
|
|
13
15
|
import { execSync } from "child_process";
|
|
14
16
|
import tar from "tar";
|
|
@@ -400,6 +402,12 @@ const defaultConfig = {
|
|
|
400
402
|
},
|
|
401
403
|
icons: "icon.iconset",
|
|
402
404
|
},
|
|
405
|
+
win: {
|
|
406
|
+
bundleCEF: false,
|
|
407
|
+
},
|
|
408
|
+
linux: {
|
|
409
|
+
bundleCEF: false,
|
|
410
|
+
},
|
|
403
411
|
bun: {
|
|
404
412
|
entrypoint: "src/bun/index.ts",
|
|
405
413
|
external: [],
|
|
@@ -432,7 +440,7 @@ const validEnvironments = ["dev", "canary", "stable"];
|
|
|
432
440
|
|
|
433
441
|
// todo (yoav): dev, canary, and stable;
|
|
434
442
|
const buildEnvironment: "dev" | "canary" | "stable" =
|
|
435
|
-
validEnvironments.includes(envArg) ? envArg : "dev";
|
|
443
|
+
validEnvironments.includes(envArg || "dev") ? (envArg || "dev") : "dev";
|
|
436
444
|
|
|
437
445
|
// Determine build targets
|
|
438
446
|
type BuildTarget = { os: 'macos' | 'win' | 'linux', arch: 'arm64' | 'x64' };
|
|
@@ -1046,11 +1054,13 @@ if (commandArg === "init") {
|
|
|
1046
1054
|
'libvulkan.so.1'
|
|
1047
1055
|
];
|
|
1048
1056
|
|
|
1057
|
+
// Copy CEF .so files to main directory as symlinks to cef/ subdirectory
|
|
1049
1058
|
cefSoFiles.forEach(soFile => {
|
|
1050
1059
|
const sourcePath = join(cefSourcePath, soFile);
|
|
1051
1060
|
const destPath = join(appBundleMacOSPath, soFile);
|
|
1052
1061
|
if (existsSync(sourcePath)) {
|
|
1053
|
-
|
|
1062
|
+
// We'll create the actual file in cef/ and symlink from main directory
|
|
1063
|
+
// This will be done after the cef/ directory is populated
|
|
1054
1064
|
}
|
|
1055
1065
|
});
|
|
1056
1066
|
|
|
@@ -1112,6 +1122,30 @@ if (commandArg === "init") {
|
|
|
1112
1122
|
}
|
|
1113
1123
|
});
|
|
1114
1124
|
|
|
1125
|
+
// Create symlinks from main directory to cef/ subdirectory for .so files
|
|
1126
|
+
console.log('Creating symlinks for CEF libraries...');
|
|
1127
|
+
cefSoFiles.forEach(soFile => {
|
|
1128
|
+
const cefFilePath = join(cefResourcesDestination, soFile);
|
|
1129
|
+
const mainDirPath = join(appBundleMacOSPath, soFile);
|
|
1130
|
+
|
|
1131
|
+
if (existsSync(cefFilePath)) {
|
|
1132
|
+
try {
|
|
1133
|
+
// Remove any existing file/symlink in main directory
|
|
1134
|
+
if (existsSync(mainDirPath)) {
|
|
1135
|
+
rmSync(mainDirPath);
|
|
1136
|
+
}
|
|
1137
|
+
// Create symlink from main directory to cef/ subdirectory
|
|
1138
|
+
symlinkSync(join('cef', soFile), mainDirPath);
|
|
1139
|
+
console.log(`Created symlink for CEF library: ${soFile} -> cef/${soFile}`);
|
|
1140
|
+
} catch (error) {
|
|
1141
|
+
console.log(`WARNING: Failed to create symlink for ${soFile}: ${error}`);
|
|
1142
|
+
// Fallback to copying the file
|
|
1143
|
+
cpSync(cefFilePath, mainDirPath);
|
|
1144
|
+
console.log(`Fallback: Copied CEF library to main directory: ${soFile}`);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
});
|
|
1148
|
+
|
|
1115
1149
|
// Copy CEF helper processes with different names
|
|
1116
1150
|
const cefHelperNames = [
|
|
1117
1151
|
"bun Helper",
|
|
@@ -1352,7 +1386,7 @@ if (commandArg === "init") {
|
|
|
1352
1386
|
// zstd is the clear winner here. dev iteration speed gain of 1min 15s per build is much more valubale
|
|
1353
1387
|
// than saving 1 more MB of space/bandwidth.
|
|
1354
1388
|
|
|
1355
|
-
|
|
1389
|
+
let compressedTarPath = `${tarPath}.zst`;
|
|
1356
1390
|
artifactsToUpload.push(compressedTarPath);
|
|
1357
1391
|
|
|
1358
1392
|
// zstd compress tarball
|
|
@@ -1365,11 +1399,10 @@ if (commandArg === "init") {
|
|
|
1365
1399
|
const useStream = tarball.size > 100 * 1024 * 1024;
|
|
1366
1400
|
|
|
1367
1401
|
if (tarball.size > 0) {
|
|
1368
|
-
|
|
1369
|
-
|
|
1402
|
+
// Uint8 array filestream of the tar file
|
|
1370
1403
|
const data = new Uint8Array(tarBuffer);
|
|
1371
1404
|
|
|
1372
|
-
const compressionLevel = 22;
|
|
1405
|
+
const compressionLevel = 22; // Maximum compression - now safe with stripped CEF libraries
|
|
1373
1406
|
const compressedData = ZstdSimple.compress(data, compressionLevel);
|
|
1374
1407
|
|
|
1375
1408
|
console.log(
|
|
@@ -1461,9 +1494,68 @@ if (commandArg === "init") {
|
|
|
1461
1494
|
// Copy the self-extracting bundle to platform-specific filename
|
|
1462
1495
|
if (targetOS === 'win') {
|
|
1463
1496
|
// On Windows, create a self-extracting exe
|
|
1464
|
-
|
|
1465
|
-
|
|
1497
|
+
const selfExtractingExePath = await createWindowsSelfExtractingExe(
|
|
1498
|
+
buildFolder,
|
|
1499
|
+
compressedTarPath,
|
|
1500
|
+
appFileName,
|
|
1501
|
+
targetPaths,
|
|
1502
|
+
buildEnvironment
|
|
1503
|
+
);
|
|
1504
|
+
artifactsToUpload.push(selfExtractingExePath);
|
|
1466
1505
|
} else if (targetOS === 'linux') {
|
|
1506
|
+
// Create desktop file for Linux
|
|
1507
|
+
const desktopFileContent = `[Desktop Entry]
|
|
1508
|
+
Version=1.0
|
|
1509
|
+
Type=Application
|
|
1510
|
+
Name=${config.package?.name || config.app.name}
|
|
1511
|
+
Comment=${config.package?.description || config.app.description || ''}
|
|
1512
|
+
Exec=${appFileName}
|
|
1513
|
+
Icon=${appFileName}
|
|
1514
|
+
Terminal=false
|
|
1515
|
+
StartupWMClass=${appFileName}
|
|
1516
|
+
Categories=Application;
|
|
1517
|
+
`;
|
|
1518
|
+
|
|
1519
|
+
const desktopFilePath = join(appBundleFolderPath, `${appFileName}.desktop`);
|
|
1520
|
+
writeFileSync(desktopFilePath, desktopFileContent);
|
|
1521
|
+
|
|
1522
|
+
// Make desktop file executable
|
|
1523
|
+
execSync(`chmod +x ${escapePathForTerminal(desktopFilePath)}`);
|
|
1524
|
+
|
|
1525
|
+
// Create user-friendly launcher script
|
|
1526
|
+
const launcherScriptContent = `#!/bin/bash
|
|
1527
|
+
# ${config.package?.name || config.app.name} Launcher
|
|
1528
|
+
# This script launches the application from any location
|
|
1529
|
+
|
|
1530
|
+
# Get the directory where this script is located
|
|
1531
|
+
SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
|
|
1532
|
+
|
|
1533
|
+
# Find the launcher binary relative to this script
|
|
1534
|
+
LAUNCHER_BINARY="\$SCRIPT_DIR/bin/launcher"
|
|
1535
|
+
|
|
1536
|
+
if [ ! -x "\$LAUNCHER_BINARY" ]; then
|
|
1537
|
+
echo "Error: Could not find launcher binary at \$LAUNCHER_BINARY"
|
|
1538
|
+
exit 1
|
|
1539
|
+
fi
|
|
1540
|
+
|
|
1541
|
+
# Launch the application
|
|
1542
|
+
exec "\$LAUNCHER_BINARY" "\$@"
|
|
1543
|
+
`;
|
|
1544
|
+
|
|
1545
|
+
const launcherScriptPath = join(appBundleFolderPath, `${appFileName}.sh`);
|
|
1546
|
+
writeFileSync(launcherScriptPath, launcherScriptContent);
|
|
1547
|
+
execSync(`chmod +x ${escapePathForTerminal(launcherScriptPath)}`);
|
|
1548
|
+
|
|
1549
|
+
// Create self-extracting Linux binary (similar to Windows approach)
|
|
1550
|
+
const selfExtractingLinuxPath = await createLinuxSelfExtractingBinary(
|
|
1551
|
+
buildFolder,
|
|
1552
|
+
compressedTarPath,
|
|
1553
|
+
appFileName,
|
|
1554
|
+
targetPaths,
|
|
1555
|
+
buildEnvironment
|
|
1556
|
+
);
|
|
1557
|
+
artifactsToUpload.push(selfExtractingLinuxPath);
|
|
1558
|
+
|
|
1467
1559
|
// On Linux, create a tar.gz of the bundle
|
|
1468
1560
|
const linuxTarPath = join(buildFolder, `${appFileName}.tar.gz`);
|
|
1469
1561
|
execSync(`tar -czf ${escapePathForTerminal(linuxTarPath)} -C ${escapePathForTerminal(buildFolder)} ${escapePathForTerminal(basename(appBundleFolderPath))}`);
|
|
@@ -1765,6 +1857,193 @@ function getEntitlementValue(value: boolean | string) {
|
|
|
1765
1857
|
}
|
|
1766
1858
|
}
|
|
1767
1859
|
|
|
1860
|
+
async function createWindowsSelfExtractingExe(
|
|
1861
|
+
buildFolder: string,
|
|
1862
|
+
compressedTarPath: string,
|
|
1863
|
+
appFileName: string,
|
|
1864
|
+
targetPaths: any,
|
|
1865
|
+
buildEnvironment: string
|
|
1866
|
+
): Promise<string> {
|
|
1867
|
+
console.log("Creating self-extracting Windows exe...");
|
|
1868
|
+
|
|
1869
|
+
// Format: MyApp-Setup.exe (stable) or MyApp-Setup-canary.exe (non-stable)
|
|
1870
|
+
const setupFileName = buildEnvironment === "stable"
|
|
1871
|
+
? `${config.app.name}-Setup.exe`
|
|
1872
|
+
: `${config.app.name}-Setup-${buildEnvironment}.exe`;
|
|
1873
|
+
|
|
1874
|
+
const outputExePath = join(buildFolder, setupFileName);
|
|
1875
|
+
|
|
1876
|
+
// Read the extractor exe
|
|
1877
|
+
const extractorExe = readFileSync(targetPaths.EXTRACTOR);
|
|
1878
|
+
|
|
1879
|
+
// Read the compressed archive
|
|
1880
|
+
const compressedArchive = readFileSync(compressedTarPath);
|
|
1881
|
+
|
|
1882
|
+
// Create metadata JSON
|
|
1883
|
+
const metadata = {
|
|
1884
|
+
identifier: config.app.identifier,
|
|
1885
|
+
name: config.app.name,
|
|
1886
|
+
channel: buildEnvironment
|
|
1887
|
+
};
|
|
1888
|
+
const metadataJson = JSON.stringify(metadata);
|
|
1889
|
+
const metadataBuffer = Buffer.from(metadataJson, 'utf8');
|
|
1890
|
+
|
|
1891
|
+
// Create marker buffers
|
|
1892
|
+
const metadataMarker = Buffer.from('ELECTROBUN_METADATA_V1', 'utf8');
|
|
1893
|
+
const archiveMarker = Buffer.from('ELECTROBUN_ARCHIVE_V1', 'utf8');
|
|
1894
|
+
|
|
1895
|
+
// Combine extractor + metadata marker + metadata + archive marker + archive
|
|
1896
|
+
const combinedBuffer = Buffer.concat([
|
|
1897
|
+
extractorExe,
|
|
1898
|
+
metadataMarker,
|
|
1899
|
+
metadataBuffer,
|
|
1900
|
+
archiveMarker,
|
|
1901
|
+
compressedArchive
|
|
1902
|
+
]);
|
|
1903
|
+
|
|
1904
|
+
// Write the self-extracting exe
|
|
1905
|
+
writeFileSync(outputExePath, combinedBuffer);
|
|
1906
|
+
|
|
1907
|
+
// Make it executable (though Windows doesn't need chmod)
|
|
1908
|
+
if (OS !== 'win') {
|
|
1909
|
+
execSync(`chmod +x ${escapePathForTerminal(outputExePath)}`);
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
console.log(`Created self-extracting exe: ${outputExePath} (${(combinedBuffer.length / 1024 / 1024).toFixed(2)} MB)`);
|
|
1913
|
+
|
|
1914
|
+
return outputExePath;
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
async function createLinuxSelfExtractingBinary(
|
|
1918
|
+
buildFolder: string,
|
|
1919
|
+
compressedTarPath: string,
|
|
1920
|
+
appFileName: string,
|
|
1921
|
+
targetPaths: any,
|
|
1922
|
+
buildEnvironment: string
|
|
1923
|
+
): Promise<string> {
|
|
1924
|
+
console.log("Creating self-extracting Linux binary...");
|
|
1925
|
+
|
|
1926
|
+
// Format: MyApp-Setup.run (stable) or MyApp-Setup-canary.run (non-stable)
|
|
1927
|
+
const setupFileName = buildEnvironment === "stable"
|
|
1928
|
+
? `${config.app.name}-Setup.run`
|
|
1929
|
+
: `${config.app.name}-Setup-${buildEnvironment}.run`;
|
|
1930
|
+
|
|
1931
|
+
const outputPath = join(buildFolder, setupFileName);
|
|
1932
|
+
|
|
1933
|
+
// Read the extractor binary
|
|
1934
|
+
const extractorBinary = readFileSync(targetPaths.EXTRACTOR);
|
|
1935
|
+
|
|
1936
|
+
// Read the compressed archive
|
|
1937
|
+
const compressedArchive = readFileSync(compressedTarPath);
|
|
1938
|
+
|
|
1939
|
+
// Create metadata JSON
|
|
1940
|
+
const metadata = {
|
|
1941
|
+
identifier: config.app.identifier,
|
|
1942
|
+
name: config.app.name,
|
|
1943
|
+
channel: buildEnvironment
|
|
1944
|
+
};
|
|
1945
|
+
const metadataJson = JSON.stringify(metadata);
|
|
1946
|
+
const metadataBuffer = Buffer.from(metadataJson, 'utf8');
|
|
1947
|
+
|
|
1948
|
+
// Create marker buffers
|
|
1949
|
+
const metadataMarker = Buffer.from('ELECTROBUN_METADATA_V1', 'utf8');
|
|
1950
|
+
const archiveMarker = Buffer.from('ELECTROBUN_ARCHIVE_V1', 'utf8');
|
|
1951
|
+
|
|
1952
|
+
// Combine extractor + metadata marker + metadata + archive marker + archive
|
|
1953
|
+
const combinedBuffer = Buffer.concat([
|
|
1954
|
+
extractorBinary,
|
|
1955
|
+
metadataMarker,
|
|
1956
|
+
metadataBuffer,
|
|
1957
|
+
archiveMarker,
|
|
1958
|
+
compressedArchive
|
|
1959
|
+
]);
|
|
1960
|
+
|
|
1961
|
+
// Write the self-extracting binary
|
|
1962
|
+
writeFileSync(outputPath, combinedBuffer, { mode: 0o755 });
|
|
1963
|
+
|
|
1964
|
+
// Ensure it's executable (redundant but explicit)
|
|
1965
|
+
execSync(`chmod +x ${escapePathForTerminal(outputPath)}`);
|
|
1966
|
+
|
|
1967
|
+
console.log(`Created self-extracting Linux binary: ${outputPath} (${(combinedBuffer.length / 1024 / 1024).toFixed(2)} MB)`);
|
|
1968
|
+
|
|
1969
|
+
return outputPath;
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
async function createAppImage(buildFolder: string, appBundlePath: string, appFileName: string, config: any): Promise<string | null> {
|
|
1973
|
+
try {
|
|
1974
|
+
console.log("Creating AppImage...");
|
|
1975
|
+
|
|
1976
|
+
// Create AppDir structure
|
|
1977
|
+
const appDirPath = join(buildFolder, `${appFileName}.AppDir`);
|
|
1978
|
+
mkdirSync(appDirPath, { recursive: true });
|
|
1979
|
+
|
|
1980
|
+
// Copy app bundle contents to AppDir
|
|
1981
|
+
const appDirAppPath = join(appDirPath, "app");
|
|
1982
|
+
cpSync(appBundlePath, appDirAppPath, { recursive: true });
|
|
1983
|
+
|
|
1984
|
+
// Create AppRun script (main executable for AppImage)
|
|
1985
|
+
const appRunContent = `#!/bin/bash
|
|
1986
|
+
HERE="$(dirname "$(readlink -f "\${0}")")"
|
|
1987
|
+
export APPDIR="\$HERE"
|
|
1988
|
+
cd "\$HERE"
|
|
1989
|
+
exec "\$HERE/app/bin/launcher" "\$@"
|
|
1990
|
+
`;
|
|
1991
|
+
|
|
1992
|
+
const appRunPath = join(appDirPath, "AppRun");
|
|
1993
|
+
writeFileSync(appRunPath, appRunContent);
|
|
1994
|
+
execSync(`chmod +x ${escapePathForTerminal(appRunPath)}`);
|
|
1995
|
+
|
|
1996
|
+
// Create desktop file in AppDir root
|
|
1997
|
+
const desktopContent = `[Desktop Entry]
|
|
1998
|
+
Version=1.0
|
|
1999
|
+
Type=Application
|
|
2000
|
+
Name=${config.package?.name || config.app.name}
|
|
2001
|
+
Comment=${config.package?.description || config.app.description || ''}
|
|
2002
|
+
Exec=AppRun
|
|
2003
|
+
Icon=${appFileName}
|
|
2004
|
+
Terminal=false
|
|
2005
|
+
StartupWMClass=${appFileName}
|
|
2006
|
+
Categories=Application;
|
|
2007
|
+
`;
|
|
2008
|
+
|
|
2009
|
+
const appDirDesktopPath = join(appDirPath, `${appFileName}.desktop`);
|
|
2010
|
+
writeFileSync(appDirDesktopPath, desktopContent);
|
|
2011
|
+
|
|
2012
|
+
// Copy icon if it exists
|
|
2013
|
+
const iconPath = config.build.linux?.appImageIcon;
|
|
2014
|
+
if (iconPath && existsSync(iconPath)) {
|
|
2015
|
+
const iconDestPath = join(appDirPath, `${appFileName}.png`);
|
|
2016
|
+
cpSync(iconPath, iconDestPath);
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
// Try to create AppImage using available tools
|
|
2020
|
+
const appImagePath = join(buildFolder, `${appFileName}.AppImage`);
|
|
2021
|
+
|
|
2022
|
+
// Check for appimagetool
|
|
2023
|
+
try {
|
|
2024
|
+
execSync('which appimagetool', { stdio: 'pipe' });
|
|
2025
|
+
console.log("Using appimagetool to create AppImage...");
|
|
2026
|
+
execSync(`appimagetool ${escapePathForTerminal(appDirPath)} ${escapePathForTerminal(appImagePath)}`, { stdio: 'inherit' });
|
|
2027
|
+
return appImagePath;
|
|
2028
|
+
} catch {
|
|
2029
|
+
// Check for Docker
|
|
2030
|
+
try {
|
|
2031
|
+
execSync('which docker', { stdio: 'pipe' });
|
|
2032
|
+
console.log("Using Docker to create AppImage...");
|
|
2033
|
+
execSync(`docker run --rm -v "${buildFolder}:/workspace" linuxserver/appimagetool "/workspace/${basename(appDirPath)}" "/workspace/${basename(appImagePath)}"`, { stdio: 'inherit' });
|
|
2034
|
+
return appImagePath;
|
|
2035
|
+
} catch {
|
|
2036
|
+
console.warn("Neither appimagetool nor Docker found. AppImage creation skipped.");
|
|
2037
|
+
console.warn("To create AppImages, install appimagetool or Docker.");
|
|
2038
|
+
return null;
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
} catch (error) {
|
|
2042
|
+
console.error("Failed to create AppImage:", error);
|
|
2043
|
+
return null;
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
|
|
1768
2047
|
function codesignAppBundle(
|
|
1769
2048
|
appBundleOrDmgPath: string,
|
|
1770
2049
|
entitlementsFilePath?: string
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
2
|
+
"app": {
|
|
3
|
+
"name": "hello-world",
|
|
4
|
+
"identifier": "helloworld.electrobun.dev",
|
|
5
|
+
"version": "0.0.1"
|
|
6
|
+
},
|
|
7
|
+
"build": {
|
|
8
|
+
"mac": {
|
|
9
|
+
"bundleCEF": true
|
|
10
|
+
},
|
|
11
|
+
"linux": {
|
|
12
|
+
"bundleCEF": true
|
|
13
|
+
},
|
|
14
|
+
"win": {
|
|
15
|
+
"bundleCEF": true
|
|
16
|
+
}
|
|
17
|
+
},
|
|
17
18
|
}
|