electrobun 1.6.0-beta.0 → 1.7.0-beta.1
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/ElectrobunConfig.ts +18 -15
- package/package.json +1 -1
- package/src/cli/index.ts +262 -241
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
* Used in electrobun.config.ts files
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Bun.build() options that can be passed through to the bundler.
|
|
8
|
+
* Excludes options that are controlled by Electrobun (entrypoints, outdir, target).
|
|
9
|
+
* See https://bun.sh/docs/bundler for full documentation.
|
|
10
|
+
*/
|
|
11
|
+
type BunBuildOptions = Omit<
|
|
12
|
+
Parameters<typeof Bun.build>[0],
|
|
13
|
+
"entrypoints" | "outdir" | "target"
|
|
14
|
+
>;
|
|
15
|
+
|
|
6
16
|
export interface ElectrobunConfig {
|
|
7
17
|
/**
|
|
8
18
|
* Application metadata configuration
|
|
@@ -48,7 +58,9 @@ export interface ElectrobunConfig {
|
|
|
48
58
|
*/
|
|
49
59
|
build?: {
|
|
50
60
|
/**
|
|
51
|
-
* Bun process build configuration
|
|
61
|
+
* Bun process build configuration.
|
|
62
|
+
* Accepts all Bun.build() options (plugins, sourcemap, minify, define, etc.)
|
|
63
|
+
* in addition to the entrypoint. See https://bun.sh/docs/bundler
|
|
52
64
|
*/
|
|
53
65
|
bun?: {
|
|
54
66
|
/**
|
|
@@ -56,16 +68,12 @@ export interface ElectrobunConfig {
|
|
|
56
68
|
* @default "src/bun/index.ts"
|
|
57
69
|
*/
|
|
58
70
|
entrypoint?: string;
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* External modules to exclude from bundling
|
|
62
|
-
* @default []
|
|
63
|
-
*/
|
|
64
|
-
external?: string[];
|
|
65
|
-
};
|
|
71
|
+
} & BunBuildOptions;
|
|
66
72
|
|
|
67
73
|
/**
|
|
68
|
-
* Browser view build configurations
|
|
74
|
+
* Browser view build configurations.
|
|
75
|
+
* Each view accepts all Bun.build() options (plugins, sourcemap, minify, define, etc.)
|
|
76
|
+
* in addition to the entrypoint. See https://bun.sh/docs/bundler
|
|
69
77
|
*/
|
|
70
78
|
views?: {
|
|
71
79
|
[viewName: string]: {
|
|
@@ -73,12 +81,7 @@ export interface ElectrobunConfig {
|
|
|
73
81
|
* Entry point for this view's TypeScript code
|
|
74
82
|
*/
|
|
75
83
|
entrypoint: string;
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* External modules to exclude from bundling for this view
|
|
79
|
-
*/
|
|
80
|
-
external?: string[];
|
|
81
|
-
};
|
|
84
|
+
} & BunBuildOptions;
|
|
82
85
|
};
|
|
83
86
|
|
|
84
87
|
/**
|
package/package.json
CHANGED
package/src/cli/index.ts
CHANGED
|
@@ -704,10 +704,9 @@ const defaultConfig = {
|
|
|
704
704
|
},
|
|
705
705
|
bun: {
|
|
706
706
|
entrypoint: "src/bun/index.ts",
|
|
707
|
-
external: [] as string[],
|
|
708
707
|
},
|
|
709
708
|
views: undefined as
|
|
710
|
-
| Record<string, { entrypoint: string;
|
|
709
|
+
| Record<string, { entrypoint: string; [key: string]: unknown }>
|
|
711
710
|
| undefined,
|
|
712
711
|
copy: undefined as Record<string, string> | undefined,
|
|
713
712
|
},
|
|
@@ -1127,9 +1126,9 @@ async function createAppImage(
|
|
|
1127
1126
|
},
|
|
1128
1127
|
);
|
|
1129
1128
|
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1129
|
+
// Create AppRun script (the entry point)
|
|
1130
|
+
const appBundleBasename = basename(resolvedAppBundlePath);
|
|
1131
|
+
const appRunContent = `#!/bin/bash
|
|
1133
1132
|
# AppRun script for ${appFileName}
|
|
1134
1133
|
HERE="$(dirname "$(readlink -f "\${0}")")"
|
|
1135
1134
|
EXEC="\${HERE}/usr/bin/${appBundleBasename}/bin/launcher"
|
|
@@ -1145,9 +1144,9 @@ exec "\${EXEC}" "\$@"
|
|
|
1145
1144
|
writeFileSync(appRunPath, appRunContent);
|
|
1146
1145
|
execSync(`chmod +x ${escapePathForTerminal(appRunPath)}`);
|
|
1147
1146
|
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1147
|
+
// Create .desktop file in AppDir root
|
|
1148
|
+
// Always include Icon field since we always create an icon (either real or placeholder)
|
|
1149
|
+
const desktopContent = `[Desktop Entry]
|
|
1151
1150
|
Version=1.0
|
|
1152
1151
|
Type=Application
|
|
1153
1152
|
Name=${config.app.name}
|
|
@@ -1162,7 +1161,7 @@ Categories=Utility;
|
|
|
1162
1161
|
const desktopPath = join(appDirPath, `${appFileName}.desktop`);
|
|
1163
1162
|
writeFileSync(desktopPath, desktopContent);
|
|
1164
1163
|
|
|
1165
|
-
|
|
1164
|
+
// Copy icon if available, or create a minimal placeholder
|
|
1166
1165
|
if (
|
|
1167
1166
|
config.build.linux?.icon &&
|
|
1168
1167
|
existsSync(join(projectRoot, config.build.linux.icon))
|
|
@@ -1182,72 +1181,72 @@ Categories=Utility;
|
|
|
1182
1181
|
// Create a minimal 1x1 transparent PNG as placeholder to satisfy appimagetool
|
|
1183
1182
|
// This prevents "Icon entry not found" errors
|
|
1184
1183
|
const placeholderPNG = Buffer.from([
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1184
|
+
0x89,
|
|
1185
|
+
0x50,
|
|
1186
|
+
0x4e,
|
|
1187
|
+
0x47,
|
|
1188
|
+
0x0d,
|
|
1189
|
+
0x0a,
|
|
1190
|
+
0x1a,
|
|
1191
|
+
0x0a, // PNG signature
|
|
1192
|
+
0x00,
|
|
1193
|
+
0x00,
|
|
1194
|
+
0x00,
|
|
1195
|
+
0x0d,
|
|
1196
|
+
0x49,
|
|
1197
|
+
0x48,
|
|
1198
|
+
0x44,
|
|
1199
|
+
0x52, // IHDR chunk
|
|
1200
|
+
0x00,
|
|
1201
|
+
0x00,
|
|
1202
|
+
0x00,
|
|
1203
|
+
0x01,
|
|
1204
|
+
0x00,
|
|
1205
|
+
0x00,
|
|
1206
|
+
0x00,
|
|
1207
|
+
0x01, // 1x1 dimensions
|
|
1208
|
+
0x08,
|
|
1209
|
+
0x06,
|
|
1210
|
+
0x00,
|
|
1211
|
+
0x00,
|
|
1212
|
+
0x00,
|
|
1213
|
+
0x1f,
|
|
1214
|
+
0x15,
|
|
1215
|
+
0xc4, // 8-bit RGBA
|
|
1216
|
+
0x89,
|
|
1217
|
+
0x00,
|
|
1218
|
+
0x00,
|
|
1219
|
+
0x00,
|
|
1220
|
+
0x0b,
|
|
1221
|
+
0x49,
|
|
1222
|
+
0x44,
|
|
1223
|
+
0x41, // IDAT chunk
|
|
1224
|
+
0x54,
|
|
1225
|
+
0x08,
|
|
1226
|
+
0x99,
|
|
1227
|
+
0x01,
|
|
1228
|
+
0x00,
|
|
1229
|
+
0x00,
|
|
1230
|
+
0x05,
|
|
1231
|
+
0x00,
|
|
1232
|
+
0x01,
|
|
1233
|
+
0x06,
|
|
1234
|
+
0x7a,
|
|
1235
|
+
0x81,
|
|
1236
|
+
0x7c,
|
|
1237
|
+
0x00,
|
|
1238
|
+
0x00,
|
|
1239
|
+
0x00, // IEND chunk
|
|
1240
|
+
0x00,
|
|
1241
|
+
0x49,
|
|
1242
|
+
0x45,
|
|
1243
|
+
0x4e,
|
|
1244
|
+
0x44,
|
|
1245
|
+
0xae,
|
|
1246
|
+
0x42,
|
|
1247
|
+
0x60,
|
|
1248
|
+
0x82,
|
|
1249
|
+
]);
|
|
1251
1250
|
|
|
1252
1251
|
const iconDestPath = join(appDirPath, `${appFileName}.png`);
|
|
1253
1252
|
const dirIconPath = join(appDirPath, ".DirIcon");
|
|
@@ -1260,18 +1259,18 @@ Categories=Utility;
|
|
|
1260
1259
|
);
|
|
1261
1260
|
}
|
|
1262
1261
|
|
|
1263
|
-
|
|
1262
|
+
// Generate the AppImage using appimagetool
|
|
1264
1263
|
const appImagePath = join(buildFolder, `${appFileName}.AppImage`);
|
|
1265
1264
|
if (existsSync(appImagePath)) {
|
|
1266
1265
|
unlinkSync(appImagePath);
|
|
1267
1266
|
}
|
|
1268
1267
|
|
|
1269
|
-
|
|
1270
|
-
|
|
1268
|
+
// console.log(`DEBUG: AppDir path: ${appDirPath}`);
|
|
1269
|
+
// console.log(`DEBUG: Does AppDir exist? ${existsSync(appDirPath)}`);
|
|
1271
1270
|
console.log(`Generating AppImage: ${appImagePath}`);
|
|
1272
1271
|
const appImageArch = ARCH === "arm64" ? "aarch64" : "x86_64";
|
|
1273
1272
|
|
|
1274
|
-
|
|
1273
|
+
// Use full path to appimagetool if not in PATH
|
|
1275
1274
|
let appimagetoolBase = "appimagetool";
|
|
1276
1275
|
try {
|
|
1277
1276
|
execSync("which appimagetool", { stdio: "ignore" });
|
|
@@ -1288,7 +1287,7 @@ Categories=Utility;
|
|
|
1288
1287
|
}
|
|
1289
1288
|
}
|
|
1290
1289
|
|
|
1291
|
-
|
|
1290
|
+
// Get the command with proper environment for vendored libfuse2
|
|
1292
1291
|
const appimagetoolCmd = getAppImageToolCommand().replace(
|
|
1293
1292
|
"appimagetool",
|
|
1294
1293
|
appimagetoolBase,
|
|
@@ -1312,14 +1311,14 @@ Categories=Utility;
|
|
|
1312
1311
|
throw error;
|
|
1313
1312
|
}
|
|
1314
1313
|
|
|
1315
|
-
|
|
1314
|
+
// Verify the AppImage was created
|
|
1316
1315
|
if (!existsSync(appImagePath)) {
|
|
1317
1316
|
throw new Error(
|
|
1318
1317
|
`AppImage was not created at expected path: ${appImagePath}`,
|
|
1319
1318
|
);
|
|
1320
1319
|
}
|
|
1321
1320
|
|
|
1322
|
-
|
|
1321
|
+
// Extract and copy icon for desktop shortcut
|
|
1323
1322
|
const iconExtractPath = join(buildFolder, `${appFileName}.png`);
|
|
1324
1323
|
if (
|
|
1325
1324
|
config.build.linux?.icon &&
|
|
@@ -1331,23 +1330,23 @@ Categories=Utility;
|
|
|
1331
1330
|
} else {
|
|
1332
1331
|
// Create placeholder icon for desktop shortcut
|
|
1333
1332
|
const placeholderPNG = Buffer.from([
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1333
|
+
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
|
|
1334
|
+
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
|
|
1335
|
+
0x08, 0x06, 0x00, 0x00, 0x00, 0x1f, 0x15, 0xc4, 0x89, 0x00, 0x00, 0x00,
|
|
1336
|
+
0x0b, 0x49, 0x44, 0x41, 0x54, 0x08, 0x99, 0x01, 0x00, 0x00, 0x05, 0x00,
|
|
1337
|
+
0x01, 0x06, 0x7a, 0x81, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e,
|
|
1338
|
+
0x44, 0xae, 0x42, 0x60, 0x82,
|
|
1339
|
+
]);
|
|
1341
1340
|
writeFileSync(iconExtractPath, new Uint8Array(placeholderPNG));
|
|
1342
1341
|
console.log(
|
|
1343
1342
|
`✓ Created placeholder icon for desktop shortcut: ${iconExtractPath}`,
|
|
1344
1343
|
);
|
|
1345
1344
|
}
|
|
1346
1345
|
|
|
1347
|
-
|
|
1346
|
+
// Create desktop shortcut alongside the AppImage
|
|
1348
1347
|
const desktopShortcutPath = join(buildFolder, `${appFileName}.desktop`);
|
|
1349
1348
|
|
|
1350
|
-
|
|
1349
|
+
const desktopShortcutContent = `[Desktop Entry]
|
|
1351
1350
|
Version=1.0
|
|
1352
1351
|
Type=Application
|
|
1353
1352
|
Name=${config.app.name}
|
|
@@ -2420,10 +2419,11 @@ ${schemesXml}
|
|
|
2420
2419
|
// transpile developer's bun code
|
|
2421
2420
|
const bunDestFolder = join(appBundleAppCodePath, "bun");
|
|
2422
2421
|
// Build bun-javascript ts files
|
|
2422
|
+
const { entrypoint: _bunEntrypoint, ...bunBuildOptions } = bunConfig;
|
|
2423
2423
|
const buildResult = await Bun.build({
|
|
2424
|
+
...bunBuildOptions,
|
|
2424
2425
|
entrypoints: [bunSource],
|
|
2425
2426
|
outdir: bunDestFolder,
|
|
2426
|
-
external: bunConfig.external || [],
|
|
2427
2427
|
// minify: true, // todo (yoav): add minify in canary and prod builds
|
|
2428
2428
|
target: "bun",
|
|
2429
2429
|
});
|
|
@@ -2462,10 +2462,11 @@ ${schemesXml}
|
|
|
2462
2462
|
|
|
2463
2463
|
// console.info(`bundling ${viewSource} to ${viewDestFolder} with config: `, viewConfig);
|
|
2464
2464
|
|
|
2465
|
+
const { entrypoint: _viewEntrypoint, ...viewBuildOptions } = viewConfig;
|
|
2465
2466
|
const buildResult = await Bun.build({
|
|
2467
|
+
...viewBuildOptions,
|
|
2466
2468
|
entrypoints: [viewSource],
|
|
2467
2469
|
outdir: viewDestFolder,
|
|
2468
|
-
external: viewConfig.external || [],
|
|
2469
2470
|
target: "browser",
|
|
2470
2471
|
});
|
|
2471
2472
|
|
|
@@ -2660,20 +2661,36 @@ ${schemesXml}
|
|
|
2660
2661
|
}
|
|
2661
2662
|
}
|
|
2662
2663
|
|
|
2663
|
-
//
|
|
2664
|
-
//
|
|
2665
|
-
//
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2664
|
+
// Create a content hash for version.json. In non-dev builds this is used
|
|
2665
|
+
// by the updater to detect changes. For dev builds we skip it since
|
|
2666
|
+
// the updater isn't relevant.
|
|
2667
|
+
let hash: string;
|
|
2668
|
+
if (buildEnvironment === "dev") {
|
|
2669
|
+
hash = "dev";
|
|
2670
|
+
} else {
|
|
2671
|
+
// Walk the app bundle and create an in-memory tar for hashing
|
|
2672
|
+
// (no temp file on disk). This runs after ASAR packing so the
|
|
2673
|
+
// hash reflects the final shipped bundle contents.
|
|
2674
|
+
console.time("Generate Bundle hash");
|
|
2675
|
+
const bundleFiles: Record<string, Blob> = {};
|
|
2676
|
+
const bundleBase = basename(appBundleFolderPath);
|
|
2677
|
+
const entries = readdirSync(appBundleFolderPath, {
|
|
2678
|
+
recursive: true,
|
|
2679
|
+
});
|
|
2680
|
+
for (const entry of entries) {
|
|
2681
|
+
const entryPath = entry.toString();
|
|
2682
|
+
const fullPath = join(appBundleFolderPath, entryPath);
|
|
2683
|
+
if (statSync(fullPath).isFile()) {
|
|
2684
|
+
bundleFiles[join(bundleBase, entryPath)] = Bun.file(fullPath);
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
const archiveBytes = await new Bun.Archive(bundleFiles).bytes();
|
|
2688
|
+
// Note: wyhash is the default in Bun.hash but that may change in the future
|
|
2689
|
+
// so we're being explicit here.
|
|
2690
|
+
hash = Bun.hash.wyhash(archiveBytes, 43770n).toString(36);
|
|
2691
|
+
console.timeEnd("Generate Bundle hash");
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2677
2694
|
// const bunVersion = execSync(`${bunBinarySourcePath} --version`).toString().trim();
|
|
2678
2695
|
|
|
2679
2696
|
// version.json inside the app bundle
|
|
@@ -2819,9 +2836,7 @@ ${schemesXml}
|
|
|
2819
2836
|
);
|
|
2820
2837
|
|
|
2821
2838
|
const appImageTarPath = join(buildFolder, `${appFileName}.tar`);
|
|
2822
|
-
console.log(
|
|
2823
|
-
`Creating tar of installer contents: ${appImageTarPath}`,
|
|
2824
|
-
);
|
|
2839
|
+
console.log(`Creating tar of installer contents: ${appImageTarPath}`);
|
|
2825
2840
|
|
|
2826
2841
|
// Tar the inner directory
|
|
2827
2842
|
createTar(appImageTarPath, tempDirPath, [appFileName]);
|
|
@@ -2964,15 +2979,15 @@ ${schemesXml}
|
|
|
2964
2979
|
const decompressResult = Bun.spawnSync(
|
|
2965
2980
|
[
|
|
2966
2981
|
zstdPath,
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2982
|
+
"decompress",
|
|
2983
|
+
"-i",
|
|
2984
|
+
prevVersionCompressedTarballPath,
|
|
2985
|
+
"-o",
|
|
2986
|
+
prevTarballPath,
|
|
2987
|
+
],
|
|
2988
|
+
{
|
|
2989
|
+
cwd: buildFolder,
|
|
2990
|
+
stdout: "inherit",
|
|
2976
2991
|
stderr: "inherit",
|
|
2977
2992
|
},
|
|
2978
2993
|
);
|
|
@@ -3208,7 +3223,10 @@ ${schemesXml}
|
|
|
3208
3223
|
)} -ov -format ULFO ${escapePathForTerminal(dmgCreationPath)}`,
|
|
3209
3224
|
);
|
|
3210
3225
|
|
|
3211
|
-
if (
|
|
3226
|
+
if (
|
|
3227
|
+
buildEnvironment === "stable" &&
|
|
3228
|
+
dmgCreationPath !== finalDmgPath
|
|
3229
|
+
) {
|
|
3212
3230
|
renameSync(dmgCreationPath, finalDmgPath);
|
|
3213
3231
|
}
|
|
3214
3232
|
artifactsToUpload.push(finalDmgPath);
|
|
@@ -3319,7 +3337,10 @@ ${schemesXml}
|
|
|
3319
3337
|
|
|
3320
3338
|
artifactsToUpload.forEach((filePath) => {
|
|
3321
3339
|
const filename = basename(filePath);
|
|
3322
|
-
const destination = join(
|
|
3340
|
+
const destination = join(
|
|
3341
|
+
artifactFolder,
|
|
3342
|
+
`${platformPrefix}-${filename}`,
|
|
3343
|
+
);
|
|
3323
3344
|
try {
|
|
3324
3345
|
renameSync(filePath, destination);
|
|
3325
3346
|
} catch {
|
|
@@ -3861,57 +3882,57 @@ ${schemesXml}
|
|
|
3861
3882
|
const wrapperAppDirPath = join(buildFolder, `${wrapperName}.AppDir`);
|
|
3862
3883
|
|
|
3863
3884
|
// Clean up any existing AppDir
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3885
|
+
if (existsSync(wrapperAppDirPath)) {
|
|
3886
|
+
rmSync(wrapperAppDirPath, { recursive: true, force: true });
|
|
3887
|
+
}
|
|
3888
|
+
mkdirSync(wrapperAppDirPath, { recursive: true });
|
|
3868
3889
|
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3890
|
+
try {
|
|
3891
|
+
// Create usr/bin directory structure
|
|
3892
|
+
const usrBinPath = join(wrapperAppDirPath, "usr", "bin");
|
|
3893
|
+
mkdirSync(usrBinPath, { recursive: true });
|
|
3873
3894
|
|
|
3874
|
-
|
|
3875
|
-
|
|
3895
|
+
// Create self-extracting binary with embedded archive (following magic markers pattern)
|
|
3896
|
+
const targetPaths = getPlatformPaths("linux", ARCH);
|
|
3876
3897
|
|
|
3877
|
-
|
|
3878
|
-
|
|
3898
|
+
// Read the extractor binary
|
|
3899
|
+
const extractorBinary = readFileSync(targetPaths.EXTRACTOR);
|
|
3879
3900
|
|
|
3880
|
-
|
|
3881
|
-
|
|
3901
|
+
// Read the compressed archive
|
|
3902
|
+
const compressedArchive = readFileSync(compressedTarPath);
|
|
3882
3903
|
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3904
|
+
// Create metadata JSON
|
|
3905
|
+
const metadata = {
|
|
3906
|
+
identifier: config.app.identifier,
|
|
3907
|
+
name: config.app.name,
|
|
3908
|
+
channel: buildEnvironment,
|
|
3909
|
+
hash: hash,
|
|
3910
|
+
};
|
|
3911
|
+
const metadataJson = JSON.stringify(metadata);
|
|
3912
|
+
const metadataBuffer = Buffer.from(metadataJson, "utf8");
|
|
3913
|
+
|
|
3914
|
+
// Create marker buffers
|
|
3915
|
+
const metadataMarker = Buffer.from("ELECTROBUN_METADATA_V1", "utf8");
|
|
3916
|
+
const archiveMarker = Buffer.from("ELECTROBUN_ARCHIVE_V1", "utf8");
|
|
3917
|
+
|
|
3918
|
+
// Combine extractor + metadata marker + metadata + archive marker + archive
|
|
3919
|
+
const combinedBuffer = Buffer.concat([
|
|
3920
|
+
new Uint8Array(extractorBinary),
|
|
3921
|
+
new Uint8Array(metadataMarker),
|
|
3922
|
+
new Uint8Array(metadataBuffer),
|
|
3923
|
+
new Uint8Array(archiveMarker),
|
|
3924
|
+
new Uint8Array(compressedArchive),
|
|
3925
|
+
]);
|
|
3926
|
+
|
|
3927
|
+
// Write the self-extracting binary to AppImage/usr/bin/
|
|
3928
|
+
const wrapperExtractorPath = join(usrBinPath, wrapperName);
|
|
3929
|
+
writeFileSync(wrapperExtractorPath, new Uint8Array(combinedBuffer), {
|
|
3930
|
+
mode: 0o755,
|
|
3931
|
+
});
|
|
3932
|
+
execSync(`chmod +x ${escapePathForTerminal(wrapperExtractorPath)}`);
|
|
3912
3933
|
|
|
3913
|
-
|
|
3914
|
-
|
|
3934
|
+
// Create AppRun script
|
|
3935
|
+
const appRunContent = `#!/bin/bash
|
|
3915
3936
|
# AppRun script for ${wrapperName}
|
|
3916
3937
|
HERE="$(dirname "$(readlink -f "\${0}")")"
|
|
3917
3938
|
EXEC="\${HERE}/usr/bin/${wrapperName}"
|
|
@@ -3920,17 +3941,17 @@ EXEC="\${HERE}/usr/bin/${wrapperName}"
|
|
|
3920
3941
|
exec "\${EXEC}" "\$@"
|
|
3921
3942
|
`;
|
|
3922
3943
|
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3944
|
+
const appRunPath = join(wrapperAppDirPath, "AppRun");
|
|
3945
|
+
writeFileSync(appRunPath, appRunContent);
|
|
3946
|
+
execSync(`chmod +x ${escapePathForTerminal(appRunPath)}`);
|
|
3926
3947
|
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3948
|
+
// Check if icon will be available
|
|
3949
|
+
const hasWrapperIcon =
|
|
3950
|
+
config.build.linux?.icon &&
|
|
3951
|
+
existsSync(join(projectRoot, config.build.linux.icon));
|
|
3931
3952
|
|
|
3932
|
-
|
|
3933
|
-
|
|
3953
|
+
// Create desktop file
|
|
3954
|
+
const desktopContent = `[Desktop Entry]
|
|
3934
3955
|
Version=1.0
|
|
3935
3956
|
Type=Application
|
|
3936
3957
|
Name=${config.app.name} Installer
|
|
@@ -3940,88 +3961,88 @@ Terminal=false
|
|
|
3940
3961
|
Categories=Utility;
|
|
3941
3962
|
`;
|
|
3942
3963
|
|
|
3943
|
-
|
|
3944
|
-
|
|
3964
|
+
const desktopPath = join(wrapperAppDirPath, `${wrapperName}.desktop`);
|
|
3965
|
+
writeFileSync(desktopPath, desktopContent);
|
|
3945
3966
|
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3967
|
+
// Copy icon if available
|
|
3968
|
+
if (hasWrapperIcon) {
|
|
3969
|
+
const iconSourcePath = join(projectRoot, config.build.linux.icon);
|
|
3970
|
+
const iconDestPath = join(wrapperAppDirPath, `${wrapperName}.png`);
|
|
3971
|
+
const dirIconPath = join(wrapperAppDirPath, ".DirIcon");
|
|
3951
3972
|
|
|
3952
|
-
|
|
3953
|
-
|
|
3973
|
+
cpSync(iconSourcePath, iconDestPath, { dereference: true });
|
|
3974
|
+
cpSync(iconSourcePath, dirIconPath, { dereference: true });
|
|
3954
3975
|
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3976
|
+
console.log(
|
|
3977
|
+
`Copied icon for wrapper AppImage: ${iconSourcePath} -> ${iconDestPath}`,
|
|
3978
|
+
);
|
|
3979
|
+
}
|
|
3959
3980
|
|
|
3960
|
-
|
|
3961
|
-
|
|
3981
|
+
// Ensure appimagetool is available
|
|
3982
|
+
await ensureAppImageTooling();
|
|
3962
3983
|
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3984
|
+
// Generate the wrapper AppImage
|
|
3985
|
+
if (existsSync(wrapperAppImagePath)) {
|
|
3986
|
+
unlinkSync(wrapperAppImagePath);
|
|
3987
|
+
}
|
|
3967
3988
|
|
|
3968
|
-
|
|
3969
|
-
|
|
3989
|
+
console.log(`Creating wrapper AppImage: ${wrapperAppImagePath}`);
|
|
3990
|
+
const appImageArch = ARCH === "arm64" ? "aarch64" : "x86_64";
|
|
3970
3991
|
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3992
|
+
// Use appimagetool to create the wrapper AppImage
|
|
3993
|
+
let appimagetoolBase = "appimagetool";
|
|
3994
|
+
try {
|
|
3995
|
+
execSync("which appimagetool", { stdio: "ignore" });
|
|
3996
|
+
} catch {
|
|
3997
|
+
const localBinPath = join(
|
|
3998
|
+
process.env["HOME"] || "",
|
|
3999
|
+
".local",
|
|
4000
|
+
"bin",
|
|
4001
|
+
"appimagetool",
|
|
4002
|
+
);
|
|
4003
|
+
if (existsSync(localBinPath)) {
|
|
4004
|
+
appimagetoolBase = localBinPath;
|
|
4005
|
+
}
|
|
4006
|
+
}
|
|
4007
|
+
|
|
4008
|
+
// Get the command with proper environment for vendored libfuse2
|
|
4009
|
+
const appimagetoolCmd = getAppImageToolCommand().replace(
|
|
3980
4010
|
"appimagetool",
|
|
4011
|
+
appimagetoolBase,
|
|
3981
4012
|
);
|
|
3982
|
-
if (existsSync(localBinPath)) {
|
|
3983
|
-
appimagetoolBase = localBinPath;
|
|
3984
|
-
}
|
|
3985
|
-
}
|
|
3986
4013
|
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
4014
|
+
try {
|
|
4015
|
+
execSync(
|
|
4016
|
+
`ARCH=${appImageArch} ${appimagetoolCmd} --no-appstream ${escapePathForTerminal(wrapperAppDirPath)} ${escapePathForTerminal(wrapperAppImagePath)}`,
|
|
4017
|
+
{
|
|
4018
|
+
stdio: "inherit",
|
|
4019
|
+
env: { ...process.env, ARCH: appImageArch },
|
|
4020
|
+
},
|
|
4021
|
+
);
|
|
4022
|
+
} catch (error) {
|
|
4023
|
+
console.error("Failed to create wrapper AppImage:", error);
|
|
4024
|
+
throw error;
|
|
4025
|
+
}
|
|
3992
4026
|
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
},
|
|
4000
|
-
);
|
|
4001
|
-
} catch (error) {
|
|
4002
|
-
console.error("Failed to create wrapper AppImage:", error);
|
|
4003
|
-
throw error;
|
|
4004
|
-
}
|
|
4027
|
+
// Verify the wrapper AppImage was created
|
|
4028
|
+
if (!existsSync(wrapperAppImagePath)) {
|
|
4029
|
+
throw new Error(
|
|
4030
|
+
`Wrapper AppImage was not created at expected path: ${wrapperAppImagePath}`,
|
|
4031
|
+
);
|
|
4032
|
+
}
|
|
4005
4033
|
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
`Wrapper AppImage was not created at expected path: ${wrapperAppImagePath}`,
|
|
4034
|
+
const stats = statSync(wrapperAppImagePath);
|
|
4035
|
+
console.log(
|
|
4036
|
+
`✓ Linux wrapper AppImage created: ${wrapperAppImagePath} (${(stats.size / 1024 / 1024).toFixed(2)} MB)`,
|
|
4010
4037
|
);
|
|
4011
|
-
}
|
|
4012
|
-
|
|
4013
|
-
const stats = statSync(wrapperAppImagePath);
|
|
4014
|
-
console.log(
|
|
4015
|
-
`✓ Linux wrapper AppImage created: ${wrapperAppImagePath} (${(stats.size / 1024 / 1024).toFixed(2)} MB)`,
|
|
4016
|
-
);
|
|
4017
4038
|
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4039
|
+
return wrapperAppImagePath;
|
|
4040
|
+
} finally {
|
|
4041
|
+
if (existsSync(wrapperAppDirPath)) {
|
|
4042
|
+
rmSync(wrapperAppDirPath, { recursive: true, force: true });
|
|
4043
|
+
}
|
|
4022
4044
|
}
|
|
4023
4045
|
}
|
|
4024
|
-
}
|
|
4025
4046
|
|
|
4026
4047
|
function codesignAppBundle(
|
|
4027
4048
|
appBundleOrDmgPath: string,
|