From 935e06a5d7765a90d00b982853f123317a3e0ebf Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Sat, 1 Aug 2026 22:26:24 +0100 Subject: [PATCH] Refactor macOS bundle handling in expand-macos-bundle.mjs and finalize-desktop-artifacts.mjs - Replaced cpSync with ditto for copying files in expand-macos-bundle.mjs to improve reliability and error handling. - Introduced a new copyMacAppBundle function in finalize-desktop-artifacts.mjs to streamline app bundle copying and signing processes. - Enhanced error handling for ditto and codesign commands to provide clearer feedback on failures. - Updated DMG creation logic to conditionally use direct source folder, optimizing the build process. --- scripts/expand-macos-bundle.mjs | 24 +++++--- scripts/finalize-desktop-artifacts.mjs | 76 ++++++++++++++++++++------ 2 files changed, 77 insertions(+), 23 deletions(-) diff --git a/scripts/expand-macos-bundle.mjs b/scripts/expand-macos-bundle.mjs index 13b494f..6b79417 100644 --- a/scripts/expand-macos-bundle.mjs +++ b/scripts/expand-macos-bundle.mjs @@ -1,5 +1,4 @@ import { - cpSync, existsSync, mkdirSync, readFileSync, @@ -111,7 +110,13 @@ const innerContentsPath = path.join(resolvedInnerApp, "Contents"); function replaceDirectory(sourceDir, destinationDir) { rmSync(destinationDir, { recursive: true, force: true }); - cpSync(sourceDir, destinationDir, { recursive: true, dereference: true }); + const result = spawnSync("ditto", [sourceDir, destinationDir], { + stdio: "inherit", + }); + if (result.status !== 0) { + console.error("expand-macos-bundle: ditto copy failed"); + process.exit(result.status ?? 1); + } } replaceDirectory( @@ -120,10 +125,7 @@ replaceDirectory( ); rmSync(resourcesPath, { recursive: true, force: true }); -cpSync(path.join(innerContentsPath, "Resources"), resourcesPath, { - recursive: true, - dereference: true, -}); +replaceDirectory(path.join(innerContentsPath, "Resources"), resourcesPath); const innerFrameworks = path.join(innerContentsPath, "Frameworks"); const wrapperFrameworks = path.join(contentsPath, "Frameworks"); @@ -131,7 +133,15 @@ if (existsSync(innerFrameworks)) { replaceDirectory(innerFrameworks, wrapperFrameworks); } -cpSync(path.join(innerContentsPath, "Info.plist"), path.join(contentsPath, "Info.plist")); +const infoPlistResult = spawnSync( + "ditto", + [path.join(innerContentsPath, "Info.plist"), path.join(contentsPath, "Info.plist")], + { stdio: "inherit" }, +); +if (infoPlistResult.status !== 0) { + console.error("expand-macos-bundle: Info.plist copy failed"); + process.exit(infoPlistResult.status ?? 1); +} rmSync(workDir, { recursive: true, force: true }); diff --git a/scripts/finalize-desktop-artifacts.mjs b/scripts/finalize-desktop-artifacts.mjs index cea5976..24d51e0 100644 --- a/scripts/finalize-desktop-artifacts.mjs +++ b/scripts/finalize-desktop-artifacts.mjs @@ -190,18 +190,48 @@ function cleanStagingArtifacts(keepNames) { const CREATE_DMG_PATH = "/usr/local/bin/create-dmg"; -function buildMacDmgWithCreateDmg( - createDmg, - appBundlePath, - dmgPath, - stagingDir, -) { - const appName = path.basename(appBundlePath); - cpSync(appBundlePath, path.join(stagingDir, appName), { - recursive: true, - dereference: true, +function copyMacAppBundle(sourcePath, destinationPath) { + rmSync(destinationPath, { recursive: true, force: true }); + mkdirSync(path.dirname(destinationPath), { recursive: true }); + + const result = spawnSync("ditto", [sourcePath, destinationPath], { + stdio: "inherit", }); + if (result.status !== 0) { + throw new Error(`ditto failed with exit code ${result.status ?? 1}`); + } +} + +function resignMacAppBundle(appBundlePath) { + if (process.platform !== "darwin") { + return; + } + + const identity = process.env.ELECTROBUN_DEVELOPER_ID || "-"; + const result = spawnSync( + "codesign", + ["--force", "--deep", "--sign", identity, appBundlePath], + { stdio: "inherit" }, + ); + + if (result.status !== 0) { + throw new Error(`codesign failed with exit code ${result.status ?? 1}`); + } +} + +function canUseDirectDmgSourceFolder(appBundlePath) { + const platformDir = path.dirname(appBundlePath); + const appName = path.basename(appBundlePath); + const entries = readdirSync(platformDir).filter( + (entry) => + !entry.startsWith(".") && entry !== ".finalize-dmg-staging", + ); + + return entries.length === 1 && entries[0] === appName; +} + +function buildMacDmgWithCreateDmg(createDmg, dmgPath, sourceFolder) { rmSync(dmgPath, { force: true }); const result = spawnSync( @@ -215,7 +245,7 @@ function buildMacDmgWithCreateDmg( "--format", "UDZO", dmgPath, - stagingDir, + sourceFolder, ], { stdio: "inherit" }, ); @@ -247,20 +277,34 @@ function buildMacDmg(appBundlePath, arch) { path.dirname(appBundlePath), ".finalize-dmg-staging", ); - rmSync(stagingDir, { recursive: true, force: true }); - mkdirSync(stagingDir, { recursive: true }); + const useDirectSource = canUseDirectDmgSourceFolder(appBundlePath); + const sourceFolder = useDirectSource + ? path.dirname(appBundlePath) + : stagingDir; + + if (!useDirectSource) { + rmSync(stagingDir, { recursive: true, force: true }); + mkdirSync(stagingDir, { recursive: true }); + const stagedAppPath = path.join( + stagingDir, + path.basename(appBundlePath), + ); + copyMacAppBundle(appBundlePath, stagedAppPath); + resignMacAppBundle(stagedAppPath); + } let builtDmgPath; try { builtDmgPath = buildMacDmgWithCreateDmg( CREATE_DMG_PATH, - appBundlePath, dmgPath, - stagingDir, + sourceFolder, ); } finally { - rmSync(stagingDir, { recursive: true, force: true }); + if (!useDirectSource) { + rmSync(stagingDir, { recursive: true, force: true }); + } } console.log(`Published ${builtDmgPath}`);