Package and publish
Create a ZIP with ditto
Package an application while preserving macOS bundle metadata and verify the extracted copy before publishing.
12 minute lesson
A ZIP is the lightest way to ship a direct download. But an app bundle is a directory full of symlinks, extended attributes, and structure that generic ZIP tools can mangle. On macOS, the tool that gets it right is ditto.
Create the archive from the signed, stapled app:
ditto -c -k --sequesterRsrc --keepParent \
Notes.app Notes-1.2.0.zip
-c -k means create a PKZip archive. --keepParent puts Notes.app itself in the archive, so extraction yields the bundle rather than its loose contents. --sequesterRsrc preserves resource metadata the way Archive Utility expects.
Why not zip -r? Frameworks inside your app contain symlinks (Versions/Current and friends). A tool that stores those as duplicate files breaks the bundle structure, and with it the code signature. The app you tested is valid; the app users extract is not.
So the test that matters happens after extraction. Unpack into a fresh directory and verify the extracted copy:
ditto -x -k Notes-1.2.0.zip extracted/
codesign --verify --deep --strict extracted/Notes.app
spctl -a -vv extracted/Notes.app
Then launch it, check the version in the About window, and walk through the first-run permissions. This extracted copy is what users run — it’s the thing to test, not the app in your build folder.
Two packaging rules to close. Ship one archive, not a folder of loose files: one artifact, one checksum, one installation instruction. And name the file with the public version — Notes-1.2.0.zip — never with secrets, usernames, or machine-specific build paths that would leak into public URLs.
Lesson completed