Refactor Windows installer scripts and enhance build process
Some checks failed
farmcontrol/farmcontrol-server/pipeline/head There was a failure building this commit

- Updated farmcontrol-server.nsi to simplify setup file handling by defining APP_SOURCE_DIR and removing unnecessary variables.
- Modified installer.nsh to reference the correct launcher executable path.
- Refactored build-windows-nsis.ps1 to accept APP_DIR instead of individual setup file parameters, streamlining the build process.
- Introduced expand-windows-installer.mjs for handling decompression and extraction of Windows app archives, improving artifact management.
- Enhanced finalize-desktop-artifacts.mjs to integrate the new expansion process and ensure proper cleanup of temporary files.
This commit is contained in:
Tom Butcher 2026-08-01 22:10:41 +01:00
parent ebc3920d65
commit 72cea3a740
5 changed files with 163 additions and 62 deletions

View File

@ -9,16 +9,8 @@
!define VERSION "1.0.0"
!endif
!ifndef SETUP_EXE_NAME
!define SETUP_EXE_NAME "Farm Control Server-Setup.exe"
!endif
!ifndef SETUP_ARCHIVE_NAME
!define SETUP_ARCHIVE_NAME "Farm Control Server-Setup.tar.zst"
!endif
!ifndef SETUP_METADATA_NAME
!define SETUP_METADATA_NAME "Farm Control Server-Setup.metadata.json"
!ifndef APP_SOURCE_DIR
!define APP_SOURCE_DIR "."
!endif
Name "Farm Control Server"
@ -39,20 +31,8 @@ RequestExecutionLevel admin
Section "Farm Control Server" SecMain
SetOutPath $INSTDIR
File /r "${APP_SOURCE_DIR}\*"
File "${SETUP_EXE_NAME}"
File "${SETUP_ARCHIVE_NAME}"
File "${SETUP_METADATA_NAME}"
DetailPrint "Running Farm Control Server setup..."
ExecWait '"$INSTDIR\${SETUP_EXE_NAME}" /S' $0
StrCmp $0 "0" setup_ok setup_failed
setup_failed:
MessageBox MB_ICONSTOP "Farm Control Server setup failed (exit code $0)."
Abort
setup_ok:
!insertmacro customInstall
WriteRegStr HKLM "Software\Tom Butcher\Farm Control Server" "InstallDir" $INSTDIR
@ -75,12 +55,7 @@ SectionEnd
Section "Uninstall"
!insertmacro customUnInstall
Delete "$INSTDIR\${SETUP_EXE_NAME}"
Delete "$INSTDIR\${SETUP_ARCHIVE_NAME}"
Delete "$INSTDIR\${SETUP_METADATA_NAME}"
Delete "$INSTDIR\launcher.exe"
Delete "$INSTDIR\Uninstall.exe"
RMDir /r "$INSTDIR"
DeleteRegKey HKLM "Software\Tom Butcher\Farm Control Server"

View File

@ -3,10 +3,10 @@
DeleteRegKey HKCR "farmcontrolserver"
WriteRegStr HKCR "farmcontrolserver" "" "URL:farmcontrolserver"
WriteRegStr HKCR "farmcontrolserver" "URL Protocol" ""
WriteRegStr HKCR "farmcontrolserver\DefaultIcon" "" "$INSTDIR\launcher.exe"
WriteRegStr HKCR "farmcontrolserver\DefaultIcon" "" "$INSTDIR\bin\launcher.exe"
WriteRegStr HKCR "farmcontrolserver\shell" "" ""
WriteRegStr HKCR "farmcontrolserver\shell\Open" "" ""
WriteRegStr HKCR "farmcontrolserver\shell\Open\command" "" '"$INSTDIR\launcher.exe" "%1"'
WriteRegStr HKCR "farmcontrolserver\shell\Open\command" "" '"$INSTDIR\bin\launcher.exe" "%1"'
!macroend
!macro customUnInstall

View File

@ -1,12 +1,6 @@
param(
[Parameter(Mandatory = $true)]
[string]$SetupExe,
[Parameter(Mandatory = $true)]
[string]$SetupArchive,
[Parameter(Mandatory = $true)]
[string]$SetupMetadata,
[string]$AppDir,
[Parameter(Mandatory = $true)]
[string]$OutputExe,
@ -47,17 +41,8 @@ if (Test-Path $workDir) {
}
New-Item -ItemType Directory -Path $workDir | Out-Null
$setupExePath = (Resolve-Path -LiteralPath $SetupExe).Path
$setupArchivePath = (Resolve-Path -LiteralPath $SetupArchive).Path
$setupMetadataPath = (Resolve-Path -LiteralPath $SetupMetadata).Path
$appDirPath = (Resolve-Path -LiteralPath $AppDir).Path
$setupExeName = Split-Path $setupExePath -Leaf
$setupArchiveName = Split-Path $setupArchivePath -Leaf
$setupMetadataName = Split-Path $setupMetadataPath -Leaf
Copy-Item -LiteralPath $setupExePath -Destination (Join-Path $workDir $setupExeName)
Copy-Item -LiteralPath $setupArchivePath -Destination (Join-Path $workDir $setupArchiveName)
Copy-Item -LiteralPath $setupMetadataPath -Destination (Join-Path $workDir $setupMetadataName)
Copy-Item -LiteralPath $nsiPath -Destination (Join-Path $workDir "farmcontrol-server.nsi")
Copy-Item -LiteralPath $installerInclude -Destination (Join-Path $workDir "installer.nsh")
@ -72,9 +57,7 @@ $makensisArgs = @(
"/NOCD"
"/DOUTFILE=$outputExePath"
"/DVERSION=$Version"
"/DSETUP_EXE_NAME=$setupExeName"
"/DSETUP_ARCHIVE_NAME=$setupArchiveName"
"/DSETUP_METADATA_NAME=$setupMetadataName"
"/DAPP_SOURCE_DIR=$appDirPath"
(Join-Path $workDir "farmcontrol-server.nsi")
)

View File

@ -0,0 +1,123 @@
import {
existsSync,
mkdirSync,
readdirSync,
rmSync,
statSync,
} 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)), "..");
function findZstdDecompressCommand() {
const systemZstd = spawnSync("zstd", ["--version"], { encoding: "utf8" });
if (systemZstd.status === 0) {
return { command: "zstd", args: (inputPath, outputPath) => [
"-d",
"-f",
"-o",
outputPath,
inputPath,
] };
}
const hostArch = process.arch === "arm64" ? "arm64" : "x64";
const candidates = [
path.join(
rootDir,
"node_modules/electrobun/dist-win-x64/zig-zstd",
hostArch,
"zig-zstd.exe",
),
path.join(
rootDir,
"node_modules/electrobun/dist-win-x64/zig-zstd",
"x64",
"zig-zstd.exe",
),
path.join(rootDir, "node_modules/electrobun/dist-win-x64", "zig-zstd"),
];
for (const candidate of candidates) {
if (existsSync(candidate)) {
return {
command: candidate,
args: (inputPath, outputPath) => [
"decompress",
"-i",
inputPath,
"-o",
outputPath,
"--no-timing",
],
};
}
}
return null;
}
function decompressTarZst(inputPath, outputPath) {
const zstd = findZstdDecompressCommand();
if (!zstd) {
throw new Error(
"expand-windows-installer: zstd not found (install zstd or use electrobun dist binaries)",
);
}
const result = spawnSync(zstd.command, zstd.args(inputPath, outputPath), {
stdio: "inherit",
});
if (result.status !== 0) {
throw new Error(
`expand-windows-installer: decompression failed (exit ${result.status ?? 1})`,
);
}
}
export function expandWindowsAppFromArchive(setupArchivePath, parentDir) {
const resolvedArchive = path.resolve(setupArchivePath);
if (!existsSync(resolvedArchive)) {
throw new Error(`expand-windows-installer: archive not found: ${resolvedArchive}`);
}
const workDir = path.join(path.resolve(parentDir), ".expanded-app");
const tarPath = path.join(workDir, "app.tar");
rmSync(workDir, { recursive: true, force: true });
mkdirSync(workDir, { recursive: true });
decompressTarZst(resolvedArchive, tarPath);
const extractTar = spawnSync("tar", ["-xf", tarPath, "-C", workDir], {
stdio: "inherit",
});
if (extractTar.status !== 0) {
rmSync(workDir, { recursive: true, force: true });
throw new Error(
`expand-windows-installer: tar extraction failed (exit ${extractTar.status ?? 1})`,
);
}
const appDir = readdirSync(workDir)
.filter((entry) => entry !== "app.tar" && entry !== "__MACOSX")
.map((entry) => path.join(workDir, entry))
.find((entryPath) => statSync(entryPath).isDirectory());
if (!appDir) {
rmSync(workDir, { recursive: true, force: true });
throw new Error("expand-windows-installer: app folder not found in archive");
}
console.log(`expand-windows-installer: expanded ${appDir}`);
return appDir;
}
export function cleanExpandedWindowsApp(parentDir) {
const workDir = path.join(path.resolve(parentDir), ".expanded-app");
rmSync(workDir, { recursive: true, force: true });
}

View File

@ -16,6 +16,10 @@ import {
getReleaseArtifactName,
getReleaseVersion,
} from "./release-artifact-utils.mjs";
import {
cleanExpandedWindowsApp,
expandWindowsAppFromArchive,
} from "./expand-windows-installer.mjs";
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const packageJson = JSON.parse(
@ -299,7 +303,15 @@ function buildMacPkg(appBundlePath, arch) {
return pkgPath;
}
function buildWindowsNsis(installerFiles, arch) {
function cleanWinBuildDir(arch) {
const platformDir = path.join(getBuildRoot(), `stable-win-${arch}`);
if (existsSync(platformDir)) {
rmSync(platformDir, { recursive: true, force: true });
console.log(`Removed build output ${platformDir}`);
}
}
function buildWindowsNsis(appDir, arch) {
const scriptPath = path.join(rootDir, "scripts/build-windows-nsis.ps1");
const exePath = path.join(
artifactDir,
@ -324,12 +336,8 @@ function buildWindowsNsis(installerFiles, arch) {
"Bypass",
"-File",
scriptPath,
"-SetupExe",
installerFiles.setupExe,
"-SetupArchive",
installerFiles.setupArchive,
"-SetupMetadata",
installerFiles.setupMetadata,
"-AppDir",
appDir,
"-OutputExe",
exePath,
"-Version",
@ -376,19 +384,31 @@ if (targetOs === "macos") {
const arch = getReleaseArch(process.env.ELECTROBUN_ARCH || "x64");
const installerFiles = findWindowsInstallerFiles();
if (!installerFiles) {
if (!installerFiles?.setupArchive) {
throw new Error(
"Could not find the Windows installer files (setup exe, archive, and metadata)",
"Could not find the Windows setup archive (.tar.zst) to expand",
);
}
const published = [buildWindowsNsis(installerFiles, arch)];
const platformDir = path.dirname(installerFiles.setupArchive);
const appDir = expandWindowsAppFromArchive(
installerFiles.setupArchive,
platformDir,
);
if (installerFiles.setupZip) {
published.push(publishArtifact(installerFiles.setupZip, arch, "zip"));
let published;
try {
published = [buildWindowsNsis(appDir, arch)];
if (installerFiles.setupZip) {
published.push(publishArtifact(installerFiles.setupZip, arch, "zip"));
}
} finally {
cleanExpandedWindowsApp(platformDir);
}
cleanStagingArtifacts(published.map((filePath) => path.basename(filePath)));
cleanWinBuildDir(arch);
} else {
console.log(
"finalize-desktop-artifacts: no desktop packaging configured for",