electrobun 0.0.19-beta.72 → 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.
|
@@ -341,9 +341,22 @@ const Updater = {
|
|
|
341
341
|
}
|
|
342
342
|
|
|
343
343
|
// Note: resolve here removes the extra trailing / that the tar file adds
|
|
344
|
-
const
|
|
344
|
+
const extractedAppPath = resolve(
|
|
345
345
|
join(extractionFolder, appBundleSubpath)
|
|
346
346
|
);
|
|
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
|
+
}
|
|
347
360
|
// Platform-specific app path calculation
|
|
348
361
|
let runningAppBundlePath: string;
|
|
349
362
|
if (currentOS === 'macos') {
|
|
@@ -353,23 +366,50 @@ const Updater = {
|
|
|
353
366
|
"..",
|
|
354
367
|
".."
|
|
355
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
|
+
);
|
|
356
376
|
} else {
|
|
357
|
-
// On Windows
|
|
377
|
+
// On Windows, the executable is the app itself
|
|
358
378
|
runningAppBundlePath = process.execPath;
|
|
359
379
|
}
|
|
360
|
-
// Platform-specific backup naming
|
|
361
|
-
|
|
362
|
-
|
|
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
|
+
}
|
|
363
390
|
|
|
364
391
|
try {
|
|
365
|
-
//
|
|
392
|
+
// Remove existing backup if it exists
|
|
366
393
|
if (statSync(backupAppBundlePath, { throwIfNoEntry: false })) {
|
|
367
394
|
rmdirSync(backupAppBundlePath, { recursive: true });
|
|
368
395
|
} else {
|
|
369
396
|
console.log("backupAppBundlePath does not exist");
|
|
370
397
|
}
|
|
398
|
+
|
|
399
|
+
// Move current running app to backup
|
|
371
400
|
renameSync(runningAppBundlePath, backupAppBundlePath);
|
|
401
|
+
|
|
402
|
+
// Move new app to running location
|
|
372
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
|
+
}
|
|
373
413
|
} catch (error) {
|
|
374
414
|
console.error("Failed to replace app with new version", error);
|
|
375
415
|
return;
|
|
@@ -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,12 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"app": {
|
|
3
|
-
"name": "
|
|
3
|
+
"name": "hello-world",
|
|
4
4
|
"identifier": "helloworld.electrobun.dev",
|
|
5
5
|
"version": "0.0.1"
|
|
6
6
|
},
|
|
7
7
|
"build": {
|
|
8
|
-
"mac": {
|
|
9
|
-
|
|
8
|
+
"mac": {
|
|
9
|
+
"bundleCEF": true
|
|
10
|
+
},
|
|
11
|
+
"linux": {
|
|
12
|
+
"bundleCEF": true
|
|
13
|
+
},
|
|
14
|
+
"win": {
|
|
15
|
+
"bundleCEF": true
|
|
10
16
|
}
|
|
11
17
|
},
|
|
12
18
|
}
|