farmcontrol-server/scripts/prepare-windows-app-binaries.mjs
Tom Butcher d4b22e3734
All checks were successful
farmcontrol/farmcontrol-server/pipeline/head This commit looks good
Enhance Windows app binary preparation and icon embedding
- Added a new script to embed icons into Windows executables, improving visual consistency across application binaries.
- Updated the NSIS installer script to conditionally embed an icon if provided, enhancing flexibility in icon management.
- Refactored the Windows app binary preparation process to utilize the new icon embedding functionality, ensuring icons are correctly applied to the launcher and runtime executables.
2026-08-01 23:52:59 +01:00

125 lines
3.8 KiB
JavaScript

import {
cpSync,
existsSync,
readFileSync,
renameSync,
rmSync,
writeFileSync,
} from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import {
DEFAULT_WINDOWS_ICON,
embedWindowsAppIcons,
} from "./embed-windows-exe-icon.mjs";
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
/** User-facing Windows app entry point (Electrobun launcher). */
export const WINDOWS_APP_EXECUTABLE = "farmcontrol-server.exe";
/**
* Bun runtime filename embedded in the Electrobun launcher binary.
* Must stay exactly 7 characters (same length as "bun.exe") for binary patching.
*/
export const WINDOWS_RUNTIME_EXECUTABLE = "farm.exe";
const LAUNCHER_BINARY = "launcher.exe";
const LEGACY_RUNTIME_BINARY = "bun.exe";
const LAUNCHER_RUNTIME_REFERENCE = Buffer.from(`${LEGACY_RUNTIME_BINARY}\0`);
const PATCHED_RUNTIME_REFERENCE = Buffer.from(`${WINDOWS_RUNTIME_EXECUTABLE}\0`);
function patchLauncherRuntimeReference(launcherPath) {
const binary = readFileSync(launcherPath);
const index = binary.indexOf(LAUNCHER_RUNTIME_REFERENCE);
if (index === -1) {
if (binary.includes(PATCHED_RUNTIME_REFERENCE)) {
return;
}
throw new Error(
`prepare-windows-app-binaries: could not find ${LEGACY_RUNTIME_BINARY} reference in ${launcherPath}`,
);
}
PATCHED_RUNTIME_REFERENCE.copy(binary, index);
writeFileSync(launcherPath, binary);
console.log(
`prepare-windows-app-binaries: patched ${path.basename(launcherPath)} to spawn ${WINDOWS_RUNTIME_EXECUTABLE}`,
);
}
function renameWindowsRuntime(binDir) {
const legacyRuntimePath = path.join(binDir, LEGACY_RUNTIME_BINARY);
const runtimePath = path.join(binDir, WINDOWS_RUNTIME_EXECUTABLE);
if (existsSync(runtimePath)) {
return runtimePath;
}
if (!existsSync(legacyRuntimePath)) {
throw new Error(
`prepare-windows-app-binaries: neither ${LEGACY_RUNTIME_BINARY} nor ${WINDOWS_RUNTIME_EXECUTABLE} found in ${binDir}`,
);
}
renameSync(legacyRuntimePath, runtimePath);
console.log(
`prepare-windows-app-binaries: renamed ${LEGACY_RUNTIME_BINARY} -> ${WINDOWS_RUNTIME_EXECUTABLE}`,
);
return runtimePath;
}
function createBrandedAppExecutable(binDir) {
const launcherPath = path.join(binDir, LAUNCHER_BINARY);
const appExecutablePath = path.join(binDir, WINDOWS_APP_EXECUTABLE);
if (!existsSync(launcherPath)) {
if (existsSync(appExecutablePath)) {
patchLauncherRuntimeReference(appExecutablePath);
return appExecutablePath;
}
throw new Error(
`prepare-windows-app-binaries: ${LAUNCHER_BINARY} not found in ${binDir}`,
);
}
patchLauncherRuntimeReference(launcherPath);
cpSync(launcherPath, appExecutablePath);
rmSync(launcherPath, { force: true });
console.log(
`prepare-windows-app-binaries: created ${WINDOWS_APP_EXECUTABLE} and removed ${LAUNCHER_BINARY}`,
);
return appExecutablePath;
}
/**
* Brand Windows app binaries and embed icons per Electrobun application-icons docs:
* launcher executable, Bun runtime executable, then NSIS installer (separately).
*/
export async function prepareWindowsAppBinaries(appDir) {
if (process.platform !== "win32") {
console.log("prepare-windows-app-binaries: skipped (not Windows)");
return;
}
const binDir = path.join(appDir, "bin");
if (!existsSync(binDir)) {
throw new Error(`prepare-windows-app-binaries: bin directory not found: ${binDir}`);
}
const launcherPath = path.join(binDir, LAUNCHER_BINARY);
if (existsSync(launcherPath)) {
await embedWindowsAppIcons([launcherPath], { icon: DEFAULT_WINDOWS_ICON });
}
const runtimePath = renameWindowsRuntime(binDir);
const appExecutablePath = createBrandedAppExecutable(binDir);
await embedWindowsAppIcons([appExecutablePath, runtimePath], {
icon: DEFAULT_WINDOWS_ICON,
cleanupTemp: true,
});
}