This weekend, I decided to try and get Franz published on the Mac App Store. Since I'm already using GitHub Actions to build distributions for my customers, I figured I'd extend that same workflow to handle build submissions to the Mac App Store.
Most of the steps are the same as for manual distribution, but some details are different and documentation is sparse, so I wanted to jot down some notes.
You can find the workflow definition on GitHub.
Apple Developer Certificates
Mac App Store apps are distributed as
.pkg installers, so I generated
a "Mac Installer Distribution" certificate in Xcode and exported it. I
also needed an "Apple Distribution" certificate and a "Mac Development"
certificate. As in my previous post, these are stored as base64-encoded
secrets that are then added to a keychain during the workflow run.
This mode of distribution requires a provisioning profile. I
tried generating a profile from the Certificates, Identifiers &
Profiles section of the Apple Developer site, but I couldn't
get Xcode on the runner to recognize the profile no matter what I tried.
So, I gave up and I copied the provisioning profile Xcode generated
~/Library/MobileDevice/Provisioning Profiles and added it as a
base64-encoded secret under GHA. The workflow writes the secret to the
provisioning profiles folder on the build machine:
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles echo -n "$MAC_PROVISIONING_PROFILE" | \ base64 --decode -o ~/Library/MobileDevice/Provisioning\ Profiles/franz.provisionprofile
Building the App
To build the app, I run the
xcodebuild archive command:
xcodebuild \ archive \ -project FranzCocoa.xcodeproj/ \ -scheme 'Franz MAS' \ -destination 'generic/platform=macOS' \ -archivePath dist/Franz.xcarchive
And to generate the
.pkg, I run
xcodebuild with the
xcodebuild \ -exportArchive \ -archivePath dist/Franz.xcarchive \ -exportOptionsPlist FranzCocoa/MASExportOptions.plist \ -exportPath dist/
exportOptionsPlist file has to have a
method key whose value is
app-store. When this
method is specified,
.pkg instead of an
.app directory. All other options are
the same as in the developer ID method.
Finally, to upload the build to App Store Connect, I use
xcrun altool \ --upload-package dist/Franz.pkg \ --type macos \ --asc-public-id '69a6de7a-5947-47e3-e053-5b8c7c11a4d1' \ --apple-id '6470144907' \ --bundle-id 'io.defn.Franz' \ --bundle-short-version-string "$(/usr/libexec/PlistBuddy -c 'Print ApplicationProperties:CFBundleShortVersionString' dist/Franz.xcarchive/Info.plist)" \ --bundle-version "$(/usr/libexec/PlistBuddy -c 'Print ApplicationProperties:CFBundleVersion' dist/Franz.xcarchive/Info.plist)" \ --username 'firstname.lastname@example.org' \ --password "$APPLE_ID_PASSWORD"
To find my public ID, I had to run
xcrun altool --list-providers.
To get an Apple ID for my app, I had to first create it in App Store
Connect. The ID can be found under "General" -> "App Information".
Since every submitted build must have a unique build number, I made this
part of the workflow optional. It only runs when the workflow is run
manually and the
masBuild flag is set. So, my release process for the
Mac App Store is:
- Bump the build number.
- Run the workflow manually.
- Go to App Store Connect and publish the new version.