Add rcedit dependency and implement Windows launcher icon embedding
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
- Added rcedit dependency in package.json and bun.lock for embedding icons in Windows executables. - Introduced a new script to generate and apply the application icon for Windows launcher binaries, improving visual consistency. - Updated the NSIS installer script to support dynamic icon paths, enhancing flexibility in icon management during installation. - Integrated the icon application process into the finalization of desktop artifacts, ensuring the icon is embedded in the generated binaries.
This commit is contained in:
parent
eb49ba7ce3
commit
29444e2ed4
1
bun.lock
1
bun.lock
@ -35,6 +35,7 @@
|
|||||||
"jimp": "^1.6.1",
|
"jimp": "^1.6.1",
|
||||||
"png-to-ico": "^2.1.8",
|
"png-to-ico": "^2.1.8",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
|
"rcedit": "^4.0.1",
|
||||||
"react": "^19.2.6",
|
"react": "^19.2.6",
|
||||||
"react-dom": "^19.2.6",
|
"react-dom": "^19.2.6",
|
||||||
"supertest": "^7.2.2",
|
"supertest": "^7.2.2",
|
||||||
|
|||||||
@ -46,6 +46,7 @@
|
|||||||
"jest": "^30.4.2",
|
"jest": "^30.4.2",
|
||||||
"jimp": "^1.6.1",
|
"jimp": "^1.6.1",
|
||||||
"png-to-ico": "^2.1.8",
|
"png-to-ico": "^2.1.8",
|
||||||
|
"rcedit": "^4.0.1",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react": "^19.2.6",
|
"react": "^19.2.6",
|
||||||
"react-dom": "^19.2.6",
|
"react-dom": "^19.2.6",
|
||||||
|
|||||||
@ -21,8 +21,16 @@ InstallDirRegKey HKLM "Software\Tom Butcher\Farm Control Server" "InstallDir"
|
|||||||
RequestExecutionLevel admin
|
RequestExecutionLevel admin
|
||||||
|
|
||||||
!define MUI_ABORTWARNING
|
!define MUI_ABORTWARNING
|
||||||
!define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\modern-install.ico"
|
|
||||||
!define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\modern-uninstall.ico"
|
!ifndef INSTALLER_ICON
|
||||||
|
!define INSTALLER_ICON "${NSISDIR}\Contrib\Graphics\Icons\modern-install.ico"
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!define MUI_ICON "${INSTALLER_ICON}"
|
||||||
|
!define MUI_UNICON "${INSTALLER_ICON}"
|
||||||
|
|
||||||
|
Icon "${INSTALLER_ICON}"
|
||||||
|
UninstallIcon "${INSTALLER_ICON}"
|
||||||
|
|
||||||
!insertmacro MUI_PAGE_DIRECTORY
|
!insertmacro MUI_PAGE_DIRECTORY
|
||||||
!insertmacro MUI_PAGE_COMPONENTS
|
!insertmacro MUI_PAGE_COMPONENTS
|
||||||
|
|||||||
83
scripts/apply-windows-launcher-icon.mjs
Normal file
83
scripts/apply-windows-launcher-icon.mjs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
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}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -53,14 +53,20 @@ if (-not (Test-Path $outputDir)) {
|
|||||||
New-Item -ItemType Directory -Path $outputDir | Out-Null
|
New-Item -ItemType Directory -Path $outputDir | Out-Null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$iconPath = Join-Path $rootDir "assets\icon.ico"
|
||||||
$makensisArgs = @(
|
$makensisArgs = @(
|
||||||
"/NOCD"
|
"/NOCD"
|
||||||
"/DOUTFILE=$outputExePath"
|
"/DOUTFILE=$outputExePath"
|
||||||
"/DVERSION=$Version"
|
"/DVERSION=$Version"
|
||||||
"/DAPP_SOURCE_DIR=$appDirPath"
|
"/DAPP_SOURCE_DIR=$appDirPath"
|
||||||
(Join-Path $workDir "farmcontrol-server.nsi")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (Test-Path $iconPath) {
|
||||||
|
$makensisArgs += "/DINSTALLER_ICON=$iconPath"
|
||||||
|
}
|
||||||
|
|
||||||
|
$makensisArgs += (Join-Path $workDir "farmcontrol-server.nsi")
|
||||||
|
|
||||||
Push-Location $workDir
|
Push-Location $workDir
|
||||||
try {
|
try {
|
||||||
& $makensis @makensisArgs
|
& $makensis @makensisArgs
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import {
|
|||||||
expandWindowsAppFromArchive,
|
expandWindowsAppFromArchive,
|
||||||
} from "./expand-windows-installer.mjs";
|
} from "./expand-windows-installer.mjs";
|
||||||
import { codesignMacAppBundle } from "./codesign-macos-app.mjs";
|
import { codesignMacAppBundle } from "./codesign-macos-app.mjs";
|
||||||
|
import { applyWindowsLauncherIcon } from "./apply-windows-launcher-icon.mjs";
|
||||||
|
|
||||||
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||||
const packageJson = JSON.parse(
|
const packageJson = JSON.parse(
|
||||||
@ -577,6 +578,7 @@ async function main() {
|
|||||||
installerFiles.setupArchive,
|
installerFiles.setupArchive,
|
||||||
platformDir,
|
platformDir,
|
||||||
);
|
);
|
||||||
|
await applyWindowsLauncherIcon(appDir);
|
||||||
|
|
||||||
let published;
|
let published;
|
||||||
try {
|
try {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user