Applications
Bundles land in /Applications by default, with nested frameworks, helpers, XPC services and plug-ins signed in the right order.
Parcel 0.1.0 · macOS
A scriptable installer and distribution builder for macOS. Staging, packages, disk images,
code signing, notarization and stapling — from a single .parcel file.
$ parcel build packaging/release.parcel Quark Downloader 1.2.0packaging/release.parcel Staging build/Quark Downloader.app → /Applications/Quark Downloader.app build/quark → /usr/local/bin/quark ✓ 5 items staged Signing ✓ quark ✓ Quark Downloader.app Package ✓ dist/Quark Downloader-1.2.0.pkg Notarizing Quark Downloader-1.2.0.pkg ✓ Notarized Quark Downloader-1.2.0.pkg ✓ Stapled Quark Downloader-1.2.0.pkg ✓ Gatekeeper accepts Quark Downloader-1.2.0.pkg Disk image ✓ dist/Quark Downloader-1.2.0.dmg Built in 48.2s dist/Quark Downloader-1.2.0.pkg 9.4 MB dist/Quark Downloader-1.2.0.dmg 9.1 MBInstead of this
pkgbuild productbuild productsign pkgutil hdiutil codesign notarytool stapler spctl ditto
parcel build packaging/release.parcel
Parcel stages the payload, signs it inside-out, builds the artifacts, signs those, sends them to Apple, staples the tickets and checks the result with Gatekeeper.
Overview
Parcel builds an internal filesystem that mirrors the layout you want on disk, then hands it to the native packaging tools. An application bundle is one kind of payload, not a requirement.
Bundles land in /Applications by default, with nested frameworks, helpers, XPC services and plug-ins signed in the right order.
A single compiled binary into /usr/local/bin, with the mode you ask for and its own signature. No bundle in sight.
Anything with a filesystem destination: shared libraries, audio units, Quick Look generators, developer tools.
Configuration, resources, launch agents and daemons, placed exactly where they belong with the permissions they need.
Configuration
Configurations use the .parcel extension and live wherever you like — a project can
hold as many as it has packaging targets. Paths resolve relative to the file itself, so a build
behaves the same from any directory.
[Package]
Name=Quark Downloader
Identifier=com.example.quark
Version=1.2.0
OutputDirectory=../dist
[Application]
Source=../build/Quark Downloader.app
[Files]
Source=../build/quark
Destination=/usr/local/bin/quark
Mode=755
Sign=true
[Signing]
Enabled=true
TeamID=ABCDE12345
[Notarization]
Enabled=true
Profile=parcel-release
[PKG]
Enabled=true
[DMG]
Enabled=true
Contents=PKG
VolumeName=Quark Downloader
[Package]
Name=MyTool
Identifier=com.example.mytool
Version=1.0.0
[Files]
Source=../build/mytool
Destination=/usr/local/bin/mytool
Mode=755
Sign=true
Source=../config/default.conf
Destination=/Library/Application Support/MyTool/default.conf
Mode=644
Source=../resources/*
Destination=/Library/Application Support/MyTool/Resources/
[Signing]
Enabled=true
TeamID=ABCDE12345
[PKG]
Enabled=true
[Package]
Name=MyApp
Identifier=com.example.myapp
Version=1.0.0
[Application]
Source=../build/MyApp.app
[PKG]
Enabled=false
[DMG]
Enabled=true
VolumeName=MyApp
Layout=DragToApplications
Background=resources/dmg-background.png
WindowWidth=720
WindowHeight=420
AppPosition=180,200
ApplicationsPosition=540,200
[Component "Application"]
Source=../build/MyApp.app
Destination=/Applications/MyApp.app
Required=true
[Component "CLI"]
Source=../build/myapp
Destination=/usr/local/bin/myapp
Mode=755
Required=false
Default=true
[Component "Developer Tools"]
Source=../build/developer/*
Destination=/Library/Application Support/MyApp/Developer/
Required=false
Default=false
[Installer]
Welcome=resources/welcome.md
License=../LICENSE
Conclusion=resources/finished.md
Outputs
Enable a package, a disk image, or a disk image carrying the package. Parcel uses Apple's own Installer rather than shipping an installation runtime of its own, so what your users open is the interface they already know.
This installer places Quark Downloader in your Applications folder and the
quark command-line tool in /usr/local/bin.
A full installer. Multiple components the user can choose between, welcome and licence pages written in Markdown, pre- and post-install scripts, and an install layout that can reach anywhere on the volume.
The drag-to-Applications experience, in three lines of configuration. Or wrap the installer package in a disk image when that suits the release better.
The release flow
Each stage feeds the next, and every stage can be inspected with --verbose or previewed with --dry-run before anything is written.
Payloads are copied into an internal filesystem that mirrors the install layout. Wildcards expand, modes are applied, and your build output is never modified.
Bundles from the inside out — nested frameworks, XPC services, helpers and libraries first, the bundle last. Standalone executables are signed in their own right.
A component package per component, a distribution file describing the installer, then a product archive. Disk images go through hdiutil.
The package with your Developer ID Installer certificate, the disk image with Developer ID Application. Parcel finds both itself.
Submitted to Apple and awaited. If Apple rejects it, you get Apple's own log rather than an exit code.
The ticket is attached to the artifact so it validates without a network connection. The package is stapled before the disk image is built around it.
Gatekeeper is asked the same question it will be asked on a user's machine, so you find out now rather than from a bug report.
Signing and notarization
Give Parcel a team identifier and it discovers the right Developer ID certificates on its own. Bundles are signed depth-first so every seal is computed over content that is already signed, with the hardened runtime and a secure timestamp.
[Signing]
Enabled=true
TeamID=ABCDE12345
Explicit ApplicationIdentity and InstallerIdentity overrides are there
when a machine holds several certificates.
An app-specific password has no business in a file you commit. Store a profile once and the
configuration only ever names it — notarytool prompts you directly, and Parcel
never handles the secret.
parcel credentials add parcel-release
[Notarization]
Enabled=true
Profile=parcel-release
Validation
Nothing is worse than a packaging error that only appears three minutes into a notarization submission. Parcel checks the whole configuration up front and reports every problem in file order, with the line that caused it and what to do about it.
$ parcel check release.parcel error: release.parcel:7: Source '../build/mytool' does not exist. hint: Paths are resolved relative to packaging.error: release.parcel:8: Destination 'bin/mytool' is not an absolute install path. hint: Install paths start at the root of the target volume.error: release.parcel:9: Mode '99x' is not a valid octal permission. hint: Use a value such as 755 or 644.error: release.parcel:13: Destination '/usr/bin/quark' is inside /usr/bin/, which is protected by the system. hint: Command-line tools normally install into /usr/local/bin.warning: release.parcel:24: TeamID 'SHORT' does not look like a team identifier.error: release.parcel:28: [Notarization] is missing Profile. hint: Create one with 'parcel credentials add <name>'. 5 errors found.$ parcel doctor Parcel Doctor System ✓ Supported macOS version ✓ Xcode Command Line Tools Packaging ✓ pkgbuild ✓ productbuild ✓ hdiutil Signing ✓ codesign ✓ Developer ID Application ✓ Developer ID Installer Notarization ✓ notarytool ✓ stapler Parcel is ready.Missing sources, relative destinations, duplicate install paths, invalid modes, malformed bundles and unreadable scripts — caught before a single file is staged.
An install path under /System, /usr/bin or /usr/lib is an error, not a surprise at install time. Command-line tools belong in /usr/local/bin.
If signing is enabled, Parcel confirms the Developer ID certificates exist and match your team before it starts building.
parcel doctor needs no project. It reports the toolchain, the certificates and the notarization tools, and says plainly what is missing.
Rust projects
Parcel is build-system independent and needs no integration to be useful. For Rust there is a
companion subcommand, cargo-parcel, published as the
parcel-cargo crate. It reads Cargo's own metadata and hands it over.
cargo install parcel-cargo
The crate is parcel-cargo. Parcel itself still needs to be on
PATH.
$ cargo parcel pkg Building quark with Cargo Finished `release` profile [optimized] target(s) in 0.10s quark 1.2.0target/parcel/quark.parcel Staging target/release/quark → /usr/local/bin/quark ✓ 1 item staged Package ✓ dist/quark-1.2.0.pkg Built in 0.2s
The package name, version, binary and target directory come from Cargo. The reverse-DNS
identifier is derived from the crate's repository when it can be. Anything Cargo cannot know
goes in Cargo.toml:
[package.metadata.parcel]
identifier = "com.example.quark"
team-id = "ABCDE12345"
sign = true
notarize = true
profile = "parcel-release"
Workspaces are handled with -p. Projects wanting full control keep their own
.parcel file and receive Cargo's values as
{CargoPackageName}, {CargoBinary} and friends.
Commands
parcel build |
Stage, sign, package, notarize and staple a release. |
|---|---|
parcel check |
Validate a configuration without building anything. |
parcel sign |
Stage and sign the payload only. |
parcel notarize |
Notarize and staple artifacts a previous build produced. |
parcel clean |
Remove build intermediates, or artifacts too with --all. |
parcel doctor |
Check the toolchain, certificates and notarization tools. |
parcel credentials |
Manage keychain-backed notarization profiles. |
--dry-run |
Report exactly what a build would do, and do none of it. |
|---|---|
--verbose |
Show every native command, its arguments and its output. |
--no-notarize |
Build and sign, but skip the trip to Apple. |
--output <dir> |
Override where artifacts are written. |
--define <k>=<v> |
Supply a {Variable} to the configuration. |
The configuration argument may be omitted when a project holds exactly one
.parcel file in the current directory, packaging/ or
installer/.
One command builds Parcel from source and puts it on your PATH. Requires macOS 13 or later and the Swift compiler.
curl -fsSL https://parcel.aspenini.com/install.sh | sh
Installs into ~/.parcel. No sudo, and nothing is copied into
/usr/local/bin.