Refactor Windows installer and update launcher executable
All checks were successful
farmcontrol/farmcontrol-server/pipeline/head This commit looks good
All checks were successful
farmcontrol/farmcontrol-server/pipeline/head This commit looks good
- Updated the NSIS installer script to replace references from `launcher.exe` to `farmcontrol-server.exe`, ensuring consistency with the new executable naming. - Removed the `apply-windows-launcher-icon.mjs` script and integrated its functionality into a new script, `prepare-windows-app-binaries.mjs`, which handles branding and icon embedding for Windows app binaries. - Enhanced the finalization process of desktop artifacts to utilize the new script for preparing Windows app binaries, improving the overall build workflow.
This commit is contained in:
parent
29444e2ed4
commit
d0d43aebb7
@ -1,10 +1,10 @@
|
||||
!macro createDesktopShortcut
|
||||
CreateShortCut "$DESKTOP\Farm Control Server.lnk" "$INSTDIR\bin\launcher.exe"
|
||||
CreateShortCut "$DESKTOP\Farm Control Server.lnk" "$INSTDIR\bin\farmcontrol-server.exe"
|
||||
!macroend
|
||||
|
||||
!macro createStartMenuShortcut
|
||||
CreateDirectory "$SMPROGRAMS\Farm Control Server"
|
||||
CreateShortCut "$SMPROGRAMS\Farm Control Server\Farm Control Server.lnk" "$INSTDIR\bin\launcher.exe"
|
||||
CreateShortCut "$SMPROGRAMS\Farm Control Server\Farm Control Server.lnk" "$INSTDIR\bin\farmcontrol-server.exe"
|
||||
!macroend
|
||||
|
||||
!macro removeDesktopShortcut
|
||||
@ -21,10 +21,10 @@
|
||||
DeleteRegKey HKCR "farmcontrolserver"
|
||||
WriteRegStr HKCR "farmcontrolserver" "" "URL:farmcontrolserver"
|
||||
WriteRegStr HKCR "farmcontrolserver" "URL Protocol" ""
|
||||
WriteRegStr HKCR "farmcontrolserver\DefaultIcon" "" "$INSTDIR\bin\launcher.exe"
|
||||
WriteRegStr HKCR "farmcontrolserver\DefaultIcon" "" "$INSTDIR\bin\farmcontrol-server.exe"
|
||||
WriteRegStr HKCR "farmcontrolserver\shell" "" ""
|
||||
WriteRegStr HKCR "farmcontrolserver\shell\Open" "" ""
|
||||
WriteRegStr HKCR "farmcontrolserver\shell\Open\command" "" '"$INSTDIR\bin\launcher.exe" "%1"'
|
||||
WriteRegStr HKCR "farmcontrolserver\shell\Open\command" "" '"$INSTDIR\bin\farmcontrol-server.exe" "%1"'
|
||||
!macroend
|
||||
|
||||
!macro customUnInstall
|
||||
|
||||
@ -1,83 +0,0 @@
|
||||
import { existsSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
|
||||
const WINDOWS_BIN_FILES = ["launcher.exe", "bun.exe"];
|
||||
|
||||
function ensureIconIco() {
|
||||
const iconPath = path.join(rootDir, "assets/icon.ico");
|
||||
if (existsSync(iconPath)) {
|
||||
return iconPath;
|
||||
}
|
||||
|
||||
const sources = [
|
||||
path.join(rootDir, "assets/farmcontrolhosticon.png"),
|
||||
path.join(rootDir, "assets/icon.png"),
|
||||
];
|
||||
const source = sources.find((candidate) => existsSync(candidate));
|
||||
if (!source) {
|
||||
throw new Error(
|
||||
"apply-windows-launcher-icon: assets/icon.ico not found and no source PNG to generate it",
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`apply-windows-launcher-icon: generating icon.ico from ${source}`);
|
||||
const result = spawnSync(
|
||||
"bun",
|
||||
[path.join(rootDir, "scripts/prepare-app-icons.mjs"), source],
|
||||
{ cwd: rootDir, stdio: "inherit", env: process.env },
|
||||
);
|
||||
|
||||
if (result.status !== 0 || !existsSync(iconPath)) {
|
||||
throw new Error("apply-windows-launcher-icon: failed to generate assets/icon.ico");
|
||||
}
|
||||
|
||||
return iconPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Embed the Farm Control icon into Windows launcher binaries.
|
||||
* Electrobun's built-in rcedit step often fails in CI (see electrobun#429).
|
||||
*/
|
||||
export async function applyWindowsLauncherIcon(appDir) {
|
||||
if (process.platform !== "win32") {
|
||||
console.log("apply-windows-launcher-icon: skipped (not Windows)");
|
||||
return;
|
||||
}
|
||||
|
||||
const iconPath = ensureIconIco();
|
||||
const binDir = path.join(appDir, "bin");
|
||||
if (!existsSync(binDir)) {
|
||||
throw new Error(`apply-windows-launcher-icon: bin directory not found: ${binDir}`);
|
||||
}
|
||||
|
||||
const rcedit = (await import("rcedit")).default;
|
||||
let patched = 0;
|
||||
|
||||
for (const fileName of WINDOWS_BIN_FILES) {
|
||||
const exePath = path.join(binDir, fileName);
|
||||
if (!existsSync(exePath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`apply-windows-launcher-icon: embedding icon into ${exePath}`);
|
||||
try {
|
||||
await rcedit(exePath, { icon: iconPath });
|
||||
patched += 1;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
throw new Error(
|
||||
`apply-windows-launcher-icon: failed to embed icon into ${fileName}: ${message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (patched === 0) {
|
||||
throw new Error(
|
||||
`apply-windows-launcher-icon: no launcher binaries found in ${binDir}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -20,7 +20,7 @@ import {
|
||||
expandWindowsAppFromArchive,
|
||||
} from "./expand-windows-installer.mjs";
|
||||
import { codesignMacAppBundle } from "./codesign-macos-app.mjs";
|
||||
import { applyWindowsLauncherIcon } from "./apply-windows-launcher-icon.mjs";
|
||||
import { prepareWindowsAppBinaries } from "./prepare-windows-app-binaries.mjs";
|
||||
|
||||
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const packageJson = JSON.parse(
|
||||
@ -578,7 +578,7 @@ async function main() {
|
||||
installerFiles.setupArchive,
|
||||
platformDir,
|
||||
);
|
||||
await applyWindowsLauncherIcon(appDir);
|
||||
await prepareWindowsAppBinaries(appDir);
|
||||
|
||||
let published;
|
||||
try {
|
||||
|
||||
168
scripts/prepare-windows-app-binaries.mjs
Normal file
168
scripts/prepare-windows-app-binaries.mjs
Normal file
@ -0,0 +1,168 @@
|
||||
import {
|
||||
cpSync,
|
||||
existsSync,
|
||||
readFileSync,
|
||||
renameSync,
|
||||
rmSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import path from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
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 ensureIconIco() {
|
||||
const iconPath = path.join(rootDir, "assets/icon.ico");
|
||||
if (existsSync(iconPath)) {
|
||||
return iconPath;
|
||||
}
|
||||
|
||||
const sources = [
|
||||
path.join(rootDir, "assets/farmcontrolhosticon.png"),
|
||||
path.join(rootDir, "assets/icon.png"),
|
||||
];
|
||||
const source = sources.find((candidate) => existsSync(candidate));
|
||||
if (!source) {
|
||||
throw new Error(
|
||||
"prepare-windows-app-binaries: assets/icon.ico not found and no source PNG to generate it",
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`prepare-windows-app-binaries: generating icon.ico from ${source}`);
|
||||
const result = spawnSync(
|
||||
"bun",
|
||||
[path.join(rootDir, "scripts/prepare-app-icons.mjs"), source],
|
||||
{ cwd: rootDir, stdio: "inherit", env: process.env },
|
||||
);
|
||||
|
||||
if (result.status !== 0 || !existsSync(iconPath)) {
|
||||
throw new Error("prepare-windows-app-binaries: failed to generate assets/icon.ico");
|
||||
}
|
||||
|
||||
return iconPath;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
async function applyWindowsExecutableMetadata(exePath, iconPath) {
|
||||
const rcedit = (await import("rcedit")).default;
|
||||
await rcedit(exePath, {
|
||||
icon: iconPath,
|
||||
"version-string": {
|
||||
CompanyName: "Tom Butcher",
|
||||
FileDescription: "Farm Control Server",
|
||||
ProductName: "Farm Control Server",
|
||||
OriginalFilename: path.basename(exePath),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Brand Windows app binaries for install: farmcontrol-server.exe entry point,
|
||||
* farm.exe runtime (patched launcher reference), and embedded icons.
|
||||
*/
|
||||
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 iconPath = ensureIconIco();
|
||||
const runtimePath = renameWindowsRuntime(binDir);
|
||||
const appExecutablePath = createBrandedAppExecutable(binDir);
|
||||
|
||||
for (const exePath of [appExecutablePath, runtimePath]) {
|
||||
console.log(`prepare-windows-app-binaries: embedding icon into ${exePath}`);
|
||||
try {
|
||||
await applyWindowsExecutableMetadata(exePath, iconPath);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
throw new Error(
|
||||
`prepare-windows-app-binaries: failed to update ${path.basename(exePath)}: ${message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user