diff --git a/scripts/finalize-desktop-artifacts.mjs b/scripts/finalize-desktop-artifacts.mjs index 8fd1e26..7ddc254 100644 --- a/scripts/finalize-desktop-artifacts.mjs +++ b/scripts/finalize-desktop-artifacts.mjs @@ -189,6 +189,43 @@ function cleanStagingArtifacts(keepNames) { } } +const MAC_DMG_ASSETS_DIR = path.join(rootDir, "packaging/macos/dmg"); +const MAC_DMG_APP_NAME = "Farm Control Server.app"; + +async function ensureMacDmgAssets() { + mkdirSync(MAC_DMG_ASSETS_DIR, { recursive: true }); + + const voliconPath = path.join(MAC_DMG_ASSETS_DIR, "volicon.icns"); + const backgroundPath = path.join(MAC_DMG_ASSETS_DIR, "background.png"); + const iconsetPath = path.join(rootDir, "assets/icon.iconset"); + + if (!existsSync(voliconPath) && existsSync(iconsetPath)) { + const iconutil = spawnSync( + "iconutil", + ["-c", "icns", "-o", voliconPath, iconsetPath], + { stdio: "inherit" }, + ); + + if (iconutil.status !== 0) { + throw new Error( + `iconutil failed to create DMG volicon with exit code ${iconutil.status ?? 1}`, + ); + } + } + + if (!existsSync(backgroundPath)) { + const { Jimp } = await import("jimp"); + const background = new Jimp({ width: 700, height: 450, color: "#1f1f1f" }); + await background.write(backgroundPath); + console.log(`finalize-desktop-artifacts: generated DMG background at ${backgroundPath}`); + } + + return { + volicon: existsSync(voliconPath) ? voliconPath : null, + background: existsSync(backgroundPath) ? backgroundPath : null, + }; +} + function findCreateDmgCommand() { const which = spawnSync("which", ["create-dmg"], { encoding: "utf8" }); if (which.status === 0 && which.stdout.trim()) { @@ -207,24 +244,50 @@ function findCreateDmgCommand() { return null; } -function buildMacDmgWithCreateDmg(createDmg, dmgPath, sourceFolder) { +function buildMacDmgWithCreateDmg( + createDmg, + dmgPath, + sourceFolder, + appBundleName, + dmgAssets, +) { rmSync(dmgPath, { force: true }); - const result = spawnSync( - createDmg, - [ - "--volname", - "Farm Control Server", - "--app-drop-link", - "480", - "170", - "--format", - "UDZO", - dmgPath, - sourceFolder, - ], - { stdio: "inherit" }, - ); + const args = [ + "--volname", + "Farm Control Server", + "--window-pos", + "200", + "120", + "--window-size", + "700", + "450", + "--icon-size", + "120", + "--icon", + appBundleName, + "180", + "200", + "--hide-extension", + appBundleName, + "--app-drop-link", + "520", + "200", + "--format", + "UDZO", + ]; + + if (dmgAssets.volicon) { + args.push("--volicon", dmgAssets.volicon); + } + + if (dmgAssets.background) { + args.push("--background", dmgAssets.background); + } + + args.push(dmgPath, sourceFolder); + + const result = spawnSync(createDmg, args, { stdio: "inherit" }); if (result.status !== 0) { throw new Error(`create-dmg failed with exit code ${result.status ?? 1}`); @@ -269,6 +332,13 @@ async function buildMacDmg(appBundlePath, arch) { ); } + const appBundleName = path.basename(appBundlePath); + if (appBundleName !== MAC_DMG_APP_NAME) { + console.warn( + `finalize-desktop-artifacts: expected ${MAC_DMG_APP_NAME}, found ${appBundleName}`, + ); + } + const dmgPath = path.join( artifactDir, getReleaseArtifactName(version, arch, "dmg"), @@ -297,9 +367,16 @@ async function buildMacDmg(appBundlePath, arch) { } let builtDmgPath; + const dmgAssets = await ensureMacDmgAssets(); try { - builtDmgPath = buildMacDmgWithCreateDmg(createDmg, dmgPath, sourceFolder); + builtDmgPath = buildMacDmgWithCreateDmg( + createDmg, + dmgPath, + sourceFolder, + appBundleName, + dmgAssets, + ); } finally { if (!useDirectSource) { rmSync(stagingDir, { recursive: true, force: true });