Shipping Electron Apps to Mac App Store with Electron Builder
- Folder structure
- Build Process and Terminology
- 1. Certificates
- 2. Provisioning Profile
- 3. Entitlements
- 4. Electron Builder Configuration
- 5. Packaging
- Summary
Shipping Electron apps have changed with time in plenty of ways. The latest that works pretty neatly is Electron Builder, itās also worth watching the Electron Userland space for more developments.
One small warning for anyone dealing with this, is that history shows that the workflow around packaging Electron is happy to break. Building and packaging an Electron app for multiple distribution methods is still no small feat.
Hereās just a few things that can change with time with a real-world Electron appāāāeach can make your build fail.
- Electron version.
- Electron builder version.
- Webpack (you do want an independent bundle).
- Babel (you do want a modern Javascript)
- Native deps and how to package and sign those.
- Xcode tools, macOS changes and electron tooling changes upstream (read: later) to that.
- Certificate voodoo. Expirey, invalidation, management of certificates (which is a pain for iOS too, mitigated with Fastlane match)
With that in mind, if you follow the below instructions, youāll have a rather smooth and uneventful packaging experience.
Folder structure
Hereās how it looks like without Javascript specific stuff. Only packaging related files and directories are here.
We mark each of the topics with a number and cover them later below.
src/
build/
all-certs.p12 <--- (1) all certs exported
entitlements.mac.plist <--- boilerplate, nothing important
entitlements.mas.plist <--- (3) team and dev ids
electronMac{.original}.js <--- (5) fixes bug with builder
electron-builder.yaml <--- (4) contains build information
embedded.provisionprofile <--- (2) provisioning profile
Build Process and Terminology
Electron builder documentation assumes (maybe rightfully?) some terminology, Iām guessing because Electron tooling has come a way now and this is the Nth incarnation of packaging tools and it relies on your having some experience with older tools.
Letās explain a few of the relevant terminology now.
- Build process. Electron-builder will look at
targets
and just the one (1) certificate bundle and build all variants at once, placing results in 'release'. - mas: āMac App Storeā build. Submitting needs a āpkgā package.
- mac: āMac stand-aloneā build, Apple doesnāt supervise and participate in distributing this, but macOS does verify product signatures so that people donāt run things that come from no-where (a verifying check which a power-user can disable through security settings on a macOS). Usually distributed with a ādmgā package.
- pkg, dmg: two packaging formats for the app; each is needed base on the variant packaging that we opt for (mac, mas).
1. Certificates
Electron builderās code-signing section doesnāt tell you much about what you need to prepare before configuring it.
Use Xcode to generate your mac app certificates:
- Open Xcode
- Preferences (Cmd+,) -> Accounts Tab
- Manage certificates (log in if you never did)
Use the small ā+ā dropdown at the button to generate the following:
- Mac App Distribution
- Mac Installer Distribution
- Developer ID Application
- Developer ID Installer
- Mac Development (not a must)
The most important step is to export these into a single p12 file. You might have been used to exporting individually when working with iOS apps.
Launch Keychain, and go to to āMy Certificatesā. Find these certificates and highlight all of them (Cmd+click), right click and export as p12.
Place this p12 file as build/all-certs.p12
.
Super important to realize that if you have additional binaries, whether a node.js
native dependency or just an executable that you include in your resources, that you want your app to use (let's say ffmpeg
or such), they too need to be signed and will be signed by Electron Builder, which is a nice surprise.
If any of this kind of signing is broken or the application doesnāt run properly, be ready to look there.
2. Provisioning Profile
Log into your apple developer account ( developer.apple.com) and go to āCertificates, IDs & Profilesā. Be sure to move to the āmacOSā dropdown or else youāll be manipulating iOS related assets.
Go to āProvisioning Profilesā and create one for distribution named embedded
. Download that into embedded.provisionprofile
at the root of your project (from where ever you'll be running electron-builder
). Place it only at the root and don't try to be smart about it in favor of a cleaner project structure (as I did), the story is that multiple tools are involved with the Electron Builder packaging workflow, one of them relies on this file existing at the root.
3. Entitlements
On an app that doesnāt require special external services from Apple, these files will be almost empty.
entitlements.mac.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
</dict>
</plist>
entitlements.mas.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<string>XXXXX.your.bundle.id</string>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
</dict>
</plist>
In the *.mas.*
variant fill in the value denoted by XXX...
with your bundle ID and prefix. You can take these values from your developer account, where you have allocated a bundle ID for your app.
4. Electron Builder Configuration
It is preferred to keep in a separate YAML file rather than in your package.json
file as an embedded block of configuration because this will let your work with a single package.json
file more easily both for build and development and skip the two-package project layout which is arguably less clean.
Hereās an electron-builder.yaml
file that builds for all platforms and ways of distribution, including Mac App Store.
directories:
output: release
appId: your.bundle.id
asarUnpack: '**/resources/some-native-binary'
productName: MyAwesomeApp
files:
- dist/
- from: dist/
to: "."
filter: index.js
- resources/
- index.html
- embedded.provisionprofile
mac:
category: public.app-category.productivity
entitlements: build/entitlements.mac.plist
icon: resources/app.icns
target:
- pkg
- dmg
- zip
- mas
mas:
type: distribution
category: public.app-category.productivity
entitlements: build/entitlements.mas.plist
icon: resources/app.icns
5. Packaging
We need to specify the environment variable (as documented) CSC_LINK
to be tied to our bundle of certificates.
In addition, due to this recent issue your app will build but wonāt validate on the Mac App Store. To resolve it, you can use the patch provided there before building.
Something like this will work around the validation issue:
"package:standalone":
"cp build/electronMac.original.js node_modules/electron-builder-lib/out/electron/electronMac.js && CSC_LINK=./build/all-certs.p12 electron-builder",
"package:app-store":
"cp build/electronMac.js node_modules/electron-builder-lib/out/electron/electronMac.js && CSC_LINK=./build/all-certs.p12 electron-builder",
Each build process will build with errors for the opposing build variant (either āmacā or āmasā), because of this patch but it will still build the variant it was invoked for properly.
Finally use Mac Application Loader to upload the freshly baked mas/*.pkg build result.
Summary
If you follow these instructions, everything should build smoothly (sans the issue above, which I imagine will resolve with time).
Should you bump into an issue, remember the list of things that can make your build break and check those in order:
- Electron version.
- Electron builder version.
- Webpack (you do want an independent bundle).
- Babel (you do want a modern Javascript)
- Native deps and how to package and sign those.
- Xcode tools, macOS changes and electron tooling changes upstream (read: later) to that.
- Certificate voodoo. Expirey, invalidation, management of certificates (which is a pain for iOS too, mitigated with Fastlane match)
Even though it feels like a lot of work, doing all this to ship a macOS app outside Xcode without tools like Fastlane is simply amazing.