Refactor finalize-desktop-artifacts script for improved readability and consistency
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good

- Standardized import statements and formatting for better code clarity.
- Simplified logic in functions for determining build environment and artifact paths.
- Enhanced error handling in artifact publishing and cleaning functions.
- Streamlined file walking and searching functions to improve maintainability.
This commit is contained in:
Tom Butcher 2026-08-02 00:38:34 +01:00
parent 47c97a1771
commit 613cb7712d

View File

@ -5,253 +5,250 @@ import {
readdirSync, readdirSync,
readFileSync, readFileSync,
rmSync, rmSync,
statSync, statSync
} from "node:fs"; } from 'node:fs'
import path from "node:path"; import path from 'node:path'
import { spawnSync } from "node:child_process"; import { spawnSync } from 'node:child_process'
import { fileURLToPath } from "node:url"; import { fileURLToPath } from 'node:url'
import { import {
getReleaseArch, getReleaseArch,
getReleaseArtifactName, getReleaseArtifactName,
getReleaseVersion, getReleaseVersion
} from "./release-artifact-utils.mjs"; } from './release-artifact-utils.mjs'
import { import {
cleanExpandedWindowsApp, cleanExpandedWindowsApp,
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'
const rootDir = path.resolve( const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
path.dirname(fileURLToPath(import.meta.url)),
"..",
);
const packageJson = JSON.parse( const packageJson = JSON.parse(
readFileSync(path.join(rootDir, "package.json"), "utf8"), readFileSync(path.join(rootDir, 'package.json'), 'utf8')
); )
const buildEnv = process.env.ELECTROBUN_BUILD_ENV || "stable"; const buildEnv = process.env.ELECTROBUN_BUILD_ENV || 'stable'
const targetOs = const targetOs =
process.env.ELECTROBUN_OS || process.env.ELECTROBUN_OS ||
(process.platform === "darwin" (process.platform === 'darwin'
? "macos" ? 'macos'
: process.platform === "win32" : process.platform === 'win32'
? "win" ? 'win'
: process.platform === "linux" : process.platform === 'linux'
? "linux" ? 'linux'
: null); : null)
const buildArch = getReleaseArch(process.env.ELECTROBUN_ARCH || process.arch); const buildArch = getReleaseArch(process.env.ELECTROBUN_ARCH || process.arch)
const version = const version =
process.env.ELECTROBUN_APP_VERSION || getReleaseVersion(packageJson); process.env.ELECTROBUN_APP_VERSION || getReleaseVersion(packageJson)
const artifactDir = const artifactDir =
process.env.ELECTROBUN_ARTIFACT_DIR || path.join(rootDir, "app_dist"); process.env.ELECTROBUN_ARTIFACT_DIR || path.join(rootDir, 'app_dist')
const identifier = const identifier =
process.env.ELECTROBUN_APP_IDENTIFIER || "com.tombutcher.farmcontrol"; process.env.ELECTROBUN_APP_IDENTIFIER || 'com.tombutcher.farmcontrol'
const artifactPrefix = `farmcontrol-${version}-`; const artifactPrefix = `farmcontrol-${version}-`
function getBuildRoot() { function getBuildRoot() {
const electrobunBuildDir = process.env.ELECTROBUN_BUILD_DIR; const electrobunBuildDir = process.env.ELECTROBUN_BUILD_DIR
if (!electrobunBuildDir) { if (!electrobunBuildDir) {
return path.join(rootDir, "build"); return path.join(rootDir, 'build')
} }
const baseName = path.basename(electrobunBuildDir); const baseName = path.basename(electrobunBuildDir)
if (baseName.startsWith("stable-")) { if (baseName.startsWith('stable-')) {
return path.dirname(electrobunBuildDir); return path.dirname(electrobunBuildDir)
} }
return electrobunBuildDir; return electrobunBuildDir
} }
function walkFiles(dir) { function walkFiles(dir) {
const files = []; const files = []
if (!existsSync(dir)) { if (!existsSync(dir)) {
return files; return files
} }
for (const entry of readdirSync(dir)) { for (const entry of readdirSync(dir)) {
const fullPath = path.join(dir, entry); const fullPath = path.join(dir, entry)
const stats = statSync(fullPath); const stats = statSync(fullPath)
if (stats.isDirectory()) { if (stats.isDirectory()) {
files.push(...walkFiles(fullPath)); files.push(...walkFiles(fullPath))
} else { } else {
files.push(fullPath); files.push(fullPath)
} }
} }
return files; return files
} }
function findByExtension(root, extension) { function findByExtension(root, extension) {
return walkFiles(root).find((filePath) => return walkFiles(root).find((filePath) =>
filePath.toLowerCase().endsWith(extension.toLowerCase()), filePath.toLowerCase().endsWith(extension.toLowerCase())
); )
} }
function findMacAppBundle(arch) { function findMacAppBundle(arch) {
const platformDir = path.join(getBuildRoot(), `stable-macos-${arch}`); const platformDir = path.join(getBuildRoot(), `stable-macos-${arch}`)
if (!existsSync(platformDir)) { if (!existsSync(platformDir)) {
return null; return null
} }
for (const child of readdirSync(platformDir)) { for (const child of readdirSync(platformDir)) {
if (child.endsWith(".app")) { if (child.endsWith('.app')) {
return path.join(platformDir, child); return path.join(platformDir, child)
} }
} }
return null; return null
} }
function findMacDmgSource(arch) { function findMacDmgSource(arch) {
const prefixedArtifact = walkFiles(artifactDir).find( const prefixedArtifact = walkFiles(artifactDir).find(
(filePath) => (filePath) =>
filePath.includes(`stable-macos-${arch}`) && filePath.includes(`stable-macos-${arch}`) &&
filePath.toLowerCase().endsWith(".dmg"), filePath.toLowerCase().endsWith('.dmg')
); )
if (prefixedArtifact) { if (prefixedArtifact) {
return prefixedArtifact; return prefixedArtifact
} }
const buildDmg = findByExtension( const buildDmg = findByExtension(
path.join(getBuildRoot(), `stable-macos-${arch}`), path.join(getBuildRoot(), `stable-macos-${arch}`),
".dmg", '.dmg'
); )
if (buildDmg) { if (buildDmg) {
return buildDmg; return buildDmg
} }
return findByExtension(artifactDir, ".dmg"); return findByExtension(artifactDir, '.dmg')
} }
function findWindowsInstallerFiles() { function findWindowsInstallerFiles() {
const buildRoot = getBuildRoot(); const buildRoot = getBuildRoot()
if (!existsSync(buildRoot)) { if (!existsSync(buildRoot)) {
return null; return null
} }
for (const entry of readdirSync(buildRoot)) { for (const entry of readdirSync(buildRoot)) {
if (!entry.startsWith("stable-win-")) { if (!entry.startsWith('stable-win-')) {
continue; continue
} }
const platformDir = path.join(buildRoot, entry); const platformDir = path.join(buildRoot, entry)
const files = walkFiles(platformDir); const files = walkFiles(platformDir)
const setupExe = files.find((filePath) => /-setup\.exe$/i.test(filePath)); const setupExe = files.find((filePath) => /-setup\.exe$/i.test(filePath))
const setupArchive = files.find((filePath) => const setupArchive = files.find((filePath) =>
/-setup\.tar\.zst$/i.test(filePath), /-setup\.tar\.zst$/i.test(filePath)
); )
const setupMetadata = files.find((filePath) => const setupMetadata = files.find((filePath) =>
/-setup\.metadata\.json$/i.test(filePath), /-setup\.metadata\.json$/i.test(filePath)
); )
const setupZip = files.find((filePath) => /-setup\.zip$/i.test(filePath)); const setupZip = files.find((filePath) => /-setup\.zip$/i.test(filePath))
if (setupExe && setupArchive && setupMetadata) { if (setupExe && setupArchive && setupMetadata) {
return { setupExe, setupArchive, setupMetadata, setupZip }; return { setupExe, setupArchive, setupMetadata, setupZip }
} }
} }
return null; return null
} }
function publishArtifact(sourcePath, arch, ext) { function publishArtifact(sourcePath, arch, ext) {
if (!sourcePath || !existsSync(sourcePath)) { if (!sourcePath || !existsSync(sourcePath)) {
throw new Error( throw new Error(
`Missing source artifact for ${arch}.${ext}: ${sourcePath ?? "not found"}`, `Missing source artifact for ${arch}.${ext}: ${sourcePath ?? 'not found'}`
); )
} }
mkdirSync(artifactDir, { recursive: true }); mkdirSync(artifactDir, { recursive: true })
const destination = path.join( const destination = path.join(
artifactDir, artifactDir,
getReleaseArtifactName(version, arch, ext), getReleaseArtifactName(version, arch, ext)
); )
cpSync(sourcePath, destination); cpSync(sourcePath, destination)
console.log(`Published ${destination}`); console.log(`Published ${destination}`)
return destination; return destination
} }
function cleanStagingArtifacts(keepNames) { function cleanStagingArtifacts(keepNames) {
if (!existsSync(artifactDir)) { if (!existsSync(artifactDir)) {
return; return
} }
for (const entry of readdirSync(artifactDir)) { for (const entry of readdirSync(artifactDir)) {
if (keepNames.includes(entry) || entry.startsWith(artifactPrefix)) { if (keepNames.includes(entry) || entry.startsWith(artifactPrefix)) {
continue; continue
} }
if ( if (
entry.includes("stable-macos-") || entry.includes('stable-macos-') ||
entry.includes("stable-win-") || entry.includes('stable-win-') ||
entry.endsWith("-update.json") || entry.endsWith('-update.json') ||
entry.endsWith(".tar.gz") entry.endsWith('.tar.gz')
) { ) {
rmSync(path.join(artifactDir, entry), { recursive: true, force: true }); rmSync(path.join(artifactDir, entry), { recursive: true, force: true })
} }
} }
} }
const MAC_DMG_ASSETS_DIR = path.join(rootDir, "packaging/macos/dmg"); const MAC_DMG_ASSETS_DIR = path.join(rootDir, 'packaging/macos/dmg')
const MAC_DMG_BACKGROUND_PATH = path.join(rootDir, "assets/dmg/background.png"); const MAC_DMG_BACKGROUND_PATH = path.join(rootDir, 'assets/dmg/background.png')
const MAC_DMG_BACKGROUND_RETINA_PATH = path.join( const MAC_DMG_BACKGROUND_RETINA_PATH = path.join(
rootDir, rootDir,
"assets/dmg/background@2x.png", 'assets/dmg/background@2x.png'
); )
const MAC_DMG_APP_NAME = "Farm Control.app"; const MAC_DMG_APP_NAME = 'Farm Control.app'
const MAC_DMG_WINDOW_WIDTH = 540; const MAC_DMG_WINDOW_WIDTH = 540
const MAC_DMG_WINDOW_HEIGHT = 380; const MAC_DMG_WINDOW_HEIGHT = 380
function ensureMacDmgAssets() { function ensureMacDmgAssets() {
mkdirSync(MAC_DMG_ASSETS_DIR, { recursive: true }); mkdirSync(MAC_DMG_ASSETS_DIR, { recursive: true })
const voliconPath = path.join(MAC_DMG_ASSETS_DIR, "volicon.icns"); const voliconPath = path.join(MAC_DMG_ASSETS_DIR, 'volicon.icns')
const iconsetPath = path.join(rootDir, "assets/icon.iconset"); const iconsetPath = path.join(rootDir, 'assets/icon.iconset')
if (!existsSync(voliconPath) && existsSync(iconsetPath)) { if (!existsSync(voliconPath) && existsSync(iconsetPath)) {
const iconutil = spawnSync( const iconutil = spawnSync(
"iconutil", 'iconutil',
["-c", "icns", "-o", voliconPath, iconsetPath], ['-c', 'icns', '-o', voliconPath, iconsetPath],
{ stdio: "inherit" }, { stdio: 'inherit' }
); )
if (iconutil.status !== 0) { if (iconutil.status !== 0) {
throw new Error( throw new Error(
`iconutil failed to create DMG volicon with exit code ${iconutil.status ?? 1}`, `iconutil failed to create DMG volicon with exit code ${iconutil.status ?? 1}`
); )
} }
} }
if (!existsSync(MAC_DMG_BACKGROUND_PATH)) { if (!existsSync(MAC_DMG_BACKGROUND_PATH)) {
throw new Error(`DMG background not found at ${MAC_DMG_BACKGROUND_PATH}`); throw new Error(`DMG background not found at ${MAC_DMG_BACKGROUND_PATH}`)
} }
if (!existsSync(MAC_DMG_BACKGROUND_RETINA_PATH)) { if (!existsSync(MAC_DMG_BACKGROUND_RETINA_PATH)) {
console.warn( console.warn(
`finalize-desktop-artifacts: retina DMG background not found at ${MAC_DMG_BACKGROUND_RETINA_PATH}`, `finalize-desktop-artifacts: retina DMG background not found at ${MAC_DMG_BACKGROUND_RETINA_PATH}`
); )
} }
return { return {
volicon: existsSync(voliconPath) ? voliconPath : null, volicon: existsSync(voliconPath) ? voliconPath : null,
background: MAC_DMG_BACKGROUND_PATH, background: MAC_DMG_BACKGROUND_PATH
}; }
} }
function findCreateDmgCommand() { function findCreateDmgCommand() {
const which = spawnSync("which", ["create-dmg"], { encoding: "utf8" }); const which = spawnSync('which', ['create-dmg'], { encoding: 'utf8' })
if (which.status === 0 && which.stdout.trim()) { if (which.status === 0 && which.stdout.trim()) {
return which.stdout.trim(); return which.stdout.trim()
} }
for (const candidate of [ for (const candidate of [
"/opt/homebrew/bin/create-dmg", '/opt/homebrew/bin/create-dmg',
"/usr/local/bin/create-dmg", '/usr/local/bin/create-dmg'
]) { ]) {
if (existsSync(candidate)) { if (existsSync(candidate)) {
return candidate; return candidate
} }
} }
return null; return null
} }
function buildMacDmgWithCreateDmg( function buildMacDmgWithCreateDmg(
@ -259,121 +256,121 @@ function buildMacDmgWithCreateDmg(
dmgPath, dmgPath,
sourceFolder, sourceFolder,
appBundleName, appBundleName,
dmgAssets, dmgAssets
) { ) {
rmSync(dmgPath, { force: true }); rmSync(dmgPath, { force: true })
const args = [ const args = [
"--volname", '--volname',
"Farm Control", 'Farm Control',
"--window-pos", '--window-pos',
"200", '200',
"120", '120',
"--window-size", '--window-size',
String(MAC_DMG_WINDOW_WIDTH), String(MAC_DMG_WINDOW_WIDTH),
String(MAC_DMG_WINDOW_HEIGHT), String(MAC_DMG_WINDOW_HEIGHT),
"--icon-size", '--icon-size',
"100", '100',
"--icon", '--icon',
appBundleName, appBundleName,
"130", '130',
"220", '212',
"--hide-extension", '--hide-extension',
appBundleName, appBundleName,
"--app-drop-link", '--app-drop-link',
"410", '410',
"220", '212',
"--format", '--format',
"UDZO", 'UDZO'
]; ]
if (dmgAssets.volicon) { if (dmgAssets.volicon) {
args.push("--volicon", dmgAssets.volicon); args.push('--volicon', dmgAssets.volicon)
} }
if (dmgAssets.background) { if (dmgAssets.background) {
args.push("--background", dmgAssets.background); args.push('--background', dmgAssets.background)
} }
args.push(dmgPath, sourceFolder); args.push(dmgPath, sourceFolder)
const result = spawnSync(createDmg, args, { stdio: "inherit" }); const result = spawnSync(createDmg, args, { stdio: 'inherit' })
if (result.status !== 0) { if (result.status !== 0) {
throw new Error(`create-dmg failed with exit code ${result.status ?? 1}`); throw new Error(`create-dmg failed with exit code ${result.status ?? 1}`)
} }
if (!existsSync(dmgPath)) { if (!existsSync(dmgPath)) {
throw new Error(`create-dmg did not produce ${dmgPath}`); throw new Error(`create-dmg did not produce ${dmgPath}`)
} }
return dmgPath; return dmgPath
} }
function copyMacAppBundle(sourcePath, destinationPath) { function copyMacAppBundle(sourcePath, destinationPath) {
rmSync(destinationPath, { recursive: true, force: true }); rmSync(destinationPath, { recursive: true, force: true })
mkdirSync(path.dirname(destinationPath), { recursive: true }); mkdirSync(path.dirname(destinationPath), { recursive: true })
const result = spawnSync("ditto", [sourcePath, destinationPath], { const result = spawnSync('ditto', [sourcePath, destinationPath], {
stdio: "inherit", stdio: 'inherit'
}); })
if (result.status !== 0) { if (result.status !== 0) {
throw new Error(`ditto failed with exit code ${result.status ?? 1}`); throw new Error(`ditto failed with exit code ${result.status ?? 1}`)
} }
} }
function canUseDirectDmgSourceFolder(appBundlePath) { function canUseDirectDmgSourceFolder(appBundlePath) {
const platformDir = path.dirname(appBundlePath); const platformDir = path.dirname(appBundlePath)
const appName = path.basename(appBundlePath); const appName = path.basename(appBundlePath)
const entries = readdirSync(platformDir).filter( const entries = readdirSync(platformDir).filter(
(entry) => !entry.startsWith(".") && entry !== ".finalize-dmg-staging", (entry) => !entry.startsWith('.') && entry !== '.finalize-dmg-staging'
); )
return entries.length === 1 && entries[0] === appName; return entries.length === 1 && entries[0] === appName
} }
async function buildMacDmg(appBundlePath, arch) { async function buildMacDmg(appBundlePath, arch) {
const createDmg = findCreateDmgCommand(); const createDmg = findCreateDmgCommand()
if (!createDmg) { if (!createDmg) {
throw new Error( throw new Error(
"create-dmg not found. Install with: brew install create-dmg", 'create-dmg not found. Install with: brew install create-dmg'
); )
} }
const appBundleName = path.basename(appBundlePath); const appBundleName = path.basename(appBundlePath)
if (appBundleName !== MAC_DMG_APP_NAME) { if (appBundleName !== MAC_DMG_APP_NAME) {
console.warn( console.warn(
`finalize-desktop-artifacts: expected ${MAC_DMG_APP_NAME}, found ${appBundleName}`, `finalize-desktop-artifacts: expected ${MAC_DMG_APP_NAME}, found ${appBundleName}`
); )
} }
const dmgPath = path.join( const dmgPath = path.join(
artifactDir, artifactDir,
getReleaseArtifactName(version, arch, "dmg"), getReleaseArtifactName(version, arch, 'dmg')
); )
mkdirSync(artifactDir, { recursive: true }); mkdirSync(artifactDir, { recursive: true })
const stagingDir = path.join( const stagingDir = path.join(
path.dirname(appBundlePath), path.dirname(appBundlePath),
".finalize-dmg-staging", '.finalize-dmg-staging'
); )
const useDirectSource = canUseDirectDmgSourceFolder(appBundlePath); const useDirectSource = canUseDirectDmgSourceFolder(appBundlePath)
const sourceFolder = useDirectSource const sourceFolder = useDirectSource
? path.dirname(appBundlePath) ? path.dirname(appBundlePath)
: stagingDir; : stagingDir
if (!useDirectSource) { if (!useDirectSource) {
rmSync(stagingDir, { recursive: true, force: true }); rmSync(stagingDir, { recursive: true, force: true })
mkdirSync(stagingDir, { recursive: true }); mkdirSync(stagingDir, { recursive: true })
const stagedAppPath = path.join(stagingDir, path.basename(appBundlePath)); const stagedAppPath = path.join(stagingDir, path.basename(appBundlePath))
copyMacAppBundle(appBundlePath, stagedAppPath); copyMacAppBundle(appBundlePath, stagedAppPath)
codesignMacAppBundle(stagedAppPath); codesignMacAppBundle(stagedAppPath)
} }
let builtDmgPath; let builtDmgPath
const dmgAssets = ensureMacDmgAssets(); const dmgAssets = ensureMacDmgAssets()
try { try {
builtDmgPath = buildMacDmgWithCreateDmg( builtDmgPath = buildMacDmgWithCreateDmg(
@ -381,228 +378,228 @@ async function buildMacDmg(appBundlePath, arch) {
dmgPath, dmgPath,
sourceFolder, sourceFolder,
appBundleName, appBundleName,
dmgAssets, dmgAssets
); )
} finally { } finally {
if (!useDirectSource) { if (!useDirectSource) {
rmSync(stagingDir, { recursive: true, force: true }); rmSync(stagingDir, { recursive: true, force: true })
} }
} }
console.log(`Published ${builtDmgPath}`); console.log(`Published ${builtDmgPath}`)
return builtDmgPath; return builtDmgPath
} }
function cleanMacBuildDir(arch) { function cleanMacBuildDir(arch) {
const platformDir = path.join(getBuildRoot(), `stable-macos-${arch}`); const platformDir = path.join(getBuildRoot(), `stable-macos-${arch}`)
if (existsSync(platformDir)) { if (existsSync(platformDir)) {
rmSync(platformDir, { recursive: true, force: true }); rmSync(platformDir, { recursive: true, force: true })
console.log(`Removed build output ${platformDir}`); console.log(`Removed build output ${platformDir}`)
} }
} }
function buildMacPkg(appBundlePath, arch) { function buildMacPkg(appBundlePath, arch) {
const pkgPath = path.join( const pkgPath = path.join(
artifactDir, artifactDir,
getReleaseArtifactName(version, arch, "pkg"), getReleaseArtifactName(version, arch, 'pkg')
); )
const result = spawnSync( const result = spawnSync(
"pkgbuild", 'pkgbuild',
[ [
"--component", '--component',
appBundlePath, appBundlePath,
"--install-location", '--install-location',
"/Applications", '/Applications',
"--identifier", '--identifier',
identifier, identifier,
"--version", '--version',
version, version,
pkgPath, pkgPath
], ],
{ stdio: "inherit" }, { stdio: 'inherit' }
); )
if (result.status !== 0) { if (result.status !== 0) {
throw new Error(`pkgbuild failed with exit code ${result.status ?? 1}`); throw new Error(`pkgbuild failed with exit code ${result.status ?? 1}`)
} }
console.log(`Published ${pkgPath}`); console.log(`Published ${pkgPath}`)
return pkgPath; return pkgPath
} }
function cleanWinBuildDir(arch) { function cleanWinBuildDir(arch) {
const platformDir = path.join(getBuildRoot(), `stable-win-${arch}`); const platformDir = path.join(getBuildRoot(), `stable-win-${arch}`)
if (existsSync(platformDir)) { if (existsSync(platformDir)) {
rmSync(platformDir, { recursive: true, force: true }); rmSync(platformDir, { recursive: true, force: true })
console.log(`Removed build output ${platformDir}`); console.log(`Removed build output ${platformDir}`)
} }
} }
function buildWindowsNsis(appDir, arch) { function buildWindowsNsis(appDir, arch) {
const scriptPath = path.join(rootDir, "scripts/build-windows-nsis.ps1"); const scriptPath = path.join(rootDir, 'scripts/build-windows-nsis.ps1')
const exePath = path.join( const exePath = path.join(
artifactDir, artifactDir,
getReleaseArtifactName(version, arch, "exe"), getReleaseArtifactName(version, arch, 'exe')
); )
const powershell = process.env.SystemRoot const powershell = process.env.SystemRoot
? path.join( ? path.join(
process.env.SystemRoot, process.env.SystemRoot,
"System32", 'System32',
"WindowsPowerShell", 'WindowsPowerShell',
"v1.0", 'v1.0',
"powershell.exe", 'powershell.exe'
) )
: "powershell.exe"; : 'powershell.exe'
const result = spawnSync( const result = spawnSync(
powershell, powershell,
[ [
"-NoProfile", '-NoProfile',
"-ExecutionPolicy", '-ExecutionPolicy',
"Bypass", 'Bypass',
"-File", '-File',
scriptPath, scriptPath,
"-AppDir", '-AppDir',
appDir, appDir,
"-OutputExe", '-OutputExe',
exePath, exePath,
"-Version", '-Version',
version, version
], ],
{ stdio: "inherit" }, { stdio: 'inherit' }
); )
if (result.status !== 0) { if (result.status !== 0) {
throw new Error( throw new Error(
`build-windows-nsis.ps1 failed with exit code ${result.status ?? 1}`, `build-windows-nsis.ps1 failed with exit code ${result.status ?? 1}`
); )
} }
const minInstallerBytes = 10 * 1024 * 1024; const minInstallerBytes = 10 * 1024 * 1024
const installerSize = statSync(exePath).size; const installerSize = statSync(exePath).size
if (installerSize < minInstallerBytes) { if (installerSize < minInstallerBytes) {
throw new Error( throw new Error(
`Windows installer ${path.basename(exePath)} is only ${(installerSize / 1024).toFixed(1)} KiB; expected at least ${minInstallerBytes / (1024 * 1024)} MiB`, `Windows installer ${path.basename(exePath)} is only ${(installerSize / 1024).toFixed(1)} KiB; expected at least ${minInstallerBytes / (1024 * 1024)} MiB`
); )
} }
console.log(`Published ${exePath}`); console.log(`Published ${exePath}`)
return exePath; return exePath
} }
function buildWindowsMsi(setupExePath, arch) { function buildWindowsMsi(setupExePath, arch) {
const scriptPath = path.join(rootDir, "scripts/build-windows-msi.ps1"); const scriptPath = path.join(rootDir, 'scripts/build-windows-msi.ps1')
const msiPath = path.join( const msiPath = path.join(
artifactDir, artifactDir,
getReleaseArtifactName(version, arch, "msi"), getReleaseArtifactName(version, arch, 'msi')
); )
const powershell = process.env.SystemRoot const powershell = process.env.SystemRoot
? path.join( ? path.join(
process.env.SystemRoot, process.env.SystemRoot,
"System32", 'System32',
"WindowsPowerShell", 'WindowsPowerShell',
"v1.0", 'v1.0',
"powershell.exe", 'powershell.exe'
) )
: "powershell.exe"; : 'powershell.exe'
const result = spawnSync( const result = spawnSync(
powershell, powershell,
[ [
"-NoProfile", '-NoProfile',
"-ExecutionPolicy", '-ExecutionPolicy',
"Bypass", 'Bypass',
"-File", '-File',
scriptPath, scriptPath,
"-SetupExe", '-SetupExe',
setupExePath, setupExePath,
"-OutputMsi", '-OutputMsi',
msiPath, msiPath,
"-Version", '-Version',
version, version
], ],
{ stdio: "inherit" }, { stdio: 'inherit' }
); )
if (result.status !== 0) { if (result.status !== 0) {
throw new Error( throw new Error(
`build-windows-msi.ps1 failed with exit code ${result.status ?? 1}`, `build-windows-msi.ps1 failed with exit code ${result.status ?? 1}`
); )
} }
console.log(`Published ${msiPath}`); console.log(`Published ${msiPath}`)
return msiPath; return msiPath
} }
if (buildEnv === "dev") { if (buildEnv === 'dev') {
console.log("finalize-desktop-artifacts: skipping dev build"); console.log('finalize-desktop-artifacts: skipping dev build')
process.exit(0); process.exit(0)
} }
async function main() { async function main() {
if (targetOs === "macos") { if (targetOs === 'macos') {
const appBundle = findMacAppBundle(buildArch); const appBundle = findMacAppBundle(buildArch)
if (!appBundle) { if (!appBundle) {
console.log( console.log(
`finalize-desktop-artifacts: no macOS ${buildArch} app bundle found, skipping`, `finalize-desktop-artifacts: no macOS ${buildArch} app bundle found, skipping`
); )
process.exit(0); process.exit(0)
} }
codesignMacAppBundle(appBundle); codesignMacAppBundle(appBundle)
const existingDmg = findMacDmgSource(buildArch); const existingDmg = findMacDmgSource(buildArch)
const dmgPath = existingDmg const dmgPath = existingDmg
? publishArtifact(existingDmg, buildArch, "dmg") ? publishArtifact(existingDmg, buildArch, 'dmg')
: await buildMacDmg(appBundle, buildArch); : await buildMacDmg(appBundle, buildArch)
const published = [dmgPath, buildMacPkg(appBundle, buildArch)]; const published = [dmgPath, buildMacPkg(appBundle, buildArch)]
cleanStagingArtifacts(published.map((filePath) => path.basename(filePath))); cleanStagingArtifacts(published.map((filePath) => path.basename(filePath)))
cleanMacBuildDir(buildArch); cleanMacBuildDir(buildArch)
return; return
} }
if (targetOs === "win") { if (targetOs === 'win') {
const arch = getReleaseArch(process.env.ELECTROBUN_ARCH || "x64"); const arch = getReleaseArch(process.env.ELECTROBUN_ARCH || 'x64')
const installerFiles = findWindowsInstallerFiles(); const installerFiles = findWindowsInstallerFiles()
if (!installerFiles?.setupArchive) { if (!installerFiles?.setupArchive) {
throw new Error( throw new Error(
"Could not find the Windows setup archive (.tar.zst) to expand", 'Could not find the Windows setup archive (.tar.zst) to expand'
); )
} }
const platformDir = path.dirname(installerFiles.setupArchive); const platformDir = path.dirname(installerFiles.setupArchive)
const appDir = expandWindowsAppFromArchive( const appDir = expandWindowsAppFromArchive(
installerFiles.setupArchive, installerFiles.setupArchive,
platformDir, platformDir
); )
let published; let published
try { try {
const setupExe = buildWindowsNsis(appDir, arch); const setupExe = buildWindowsNsis(appDir, arch)
published = [setupExe, buildWindowsMsi(setupExe, arch)]; published = [setupExe, buildWindowsMsi(setupExe, arch)]
if (installerFiles.setupZip) { if (installerFiles.setupZip) {
published.push(publishArtifact(installerFiles.setupZip, arch, "zip")); published.push(publishArtifact(installerFiles.setupZip, arch, 'zip'))
} }
} finally { } finally {
cleanExpandedWindowsApp(platformDir); cleanExpandedWindowsApp(platformDir)
} }
cleanStagingArtifacts(published.map((filePath) => path.basename(filePath))); cleanStagingArtifacts(published.map((filePath) => path.basename(filePath)))
cleanWinBuildDir(arch); cleanWinBuildDir(arch)
return; return
} }
console.log( console.log(
"finalize-desktop-artifacts: no desktop packaging configured for", 'finalize-desktop-artifacts: no desktop packaging configured for',
targetOs ?? "unknown target", targetOs ?? 'unknown target'
); )
process.exit(0); process.exit(0)
} }
await main(); await main()