From 068d9181deefb0b5a3902db1541e2de9de027d23 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Fri, 6 Mar 2026 06:13:49 -0600 Subject: [PATCH] Fix macOS release packaging to use proper .app bundle structure macOS builds were shipping as flat folders of DLLs/binaries that weren't runnable. Now creates a proper .app bundle (Contents/MacOS, Contents/Resources, Info.plist) so the app is double-clickable on macOS. Also adds missing CFBundleExecutable to Info.plist and auto-syncs the plist version from the csproj at build time so it can't drift. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/release.yml | 40 +++++++++++++++++++++++++++++++++-- src/PlanViewer.App/Info.plist | 6 ++++-- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9c142eb..4b9a5ee 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -73,13 +73,49 @@ jobs: run: | New-Item -ItemType Directory -Force -Path releases - $rids = @('win-x64', 'linux-x64', 'osx-x64', 'osx-arm64') - foreach ($rid in $rids) { + # Package Windows and Linux as flat zips + foreach ($rid in @('win-x64', 'linux-x64')) { if (Test-Path 'README.md') { Copy-Item 'README.md' "publish/$rid/" } if (Test-Path 'LICENSE') { Copy-Item 'LICENSE' "publish/$rid/" } Compress-Archive -Path "publish/$rid/*" -DestinationPath "releases/PerformanceStudio-$rid.zip" -Force } + # Package macOS as proper .app bundles + foreach ($rid in @('osx-x64', 'osx-arm64')) { + $appName = "PerformanceStudio.app" + $bundleDir = "publish/$rid-bundle/$appName" + + # Create .app bundle structure + New-Item -ItemType Directory -Force -Path "$bundleDir/Contents/MacOS" + New-Item -ItemType Directory -Force -Path "$bundleDir/Contents/Resources" + + # Copy all published files into Contents/MacOS + Copy-Item -Path "publish/$rid/*" -Destination "$bundleDir/Contents/MacOS/" -Recurse + + # Move Info.plist to Contents/ (it was copied to MacOS/ with the publish output) + if (Test-Path "$bundleDir/Contents/MacOS/Info.plist") { + Move-Item -Path "$bundleDir/Contents/MacOS/Info.plist" -Destination "$bundleDir/Contents/Info.plist" -Force + } + + # Update version in Info.plist to match csproj + $plist = Get-Content "$bundleDir/Contents/Info.plist" -Raw + $plist = $plist -replace '(CFBundleVersion\s*)[^<]*()', "`${1}$env:VERSION`${2}" + $plist = $plist -replace '(CFBundleShortVersionString\s*)[^<]*()', "`${1}$env:VERSION`${2}" + Set-Content -Path "$bundleDir/Contents/Info.plist" -Value $plist -NoNewline + + # Move icon to Contents/Resources + if (Test-Path "$bundleDir/Contents/MacOS/EDD.icns") { + Move-Item -Path "$bundleDir/Contents/MacOS/EDD.icns" -Destination "$bundleDir/Contents/Resources/EDD.icns" -Force + } + + # Add README and LICENSE alongside the .app bundle + $wrapperDir = "publish/$rid-bundle" + if (Test-Path 'README.md') { Copy-Item 'README.md' "$wrapperDir/" } + if (Test-Path 'LICENSE') { Copy-Item 'LICENSE' "$wrapperDir/" } + + Compress-Archive -Path "$wrapperDir/*" -DestinationPath "releases/PerformanceStudio-$rid.zip" -Force + } + # Checksums $checksums = Get-ChildItem releases/*.zip | ForEach-Object { $hash = (Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLower() diff --git a/src/PlanViewer.App/Info.plist b/src/PlanViewer.App/Info.plist index 49ec93f..f8cb129 100644 --- a/src/PlanViewer.App/Info.plist +++ b/src/PlanViewer.App/Info.plist @@ -8,10 +8,12 @@ Performance Studio CFBundleIdentifier com.darlingdata.sqlperformancestudio + CFBundleExecutable + PlanViewer.App CFBundleVersion - 0.7.0 + 0.9.0 CFBundleShortVersionString - 0.7.0 + 0.9.0 CFBundlePackageType APPL NSHumanReadableCopyright