Parcel 0.1.0 · macOS

One file describes the release.
Parcel builds the rest.

A scriptable installer and distribution builder for macOS. Staging, packages, disk images, code signing, notarization and stapling — from a single .parcel file.

quark — parcel build
$ 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 MB

Instead 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

No .app required.
Package anything you install.

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.

Applications

Bundles land in /Applications by default, with nested frameworks, helpers, XPC services and plug-ins signed in the right order.

Command-line tools

A single compiled binary into /usr/local/bin, with the mode you ask for and its own signature. No bundle in sight.

Frameworks and plug-ins

Anything with a filesystem destination: shared libraries, audio units, Quick Look generators, developer tools.

Support files and daemons

Configuration, resources, launch agents and daemons, placed exactly where they belong with the permissions they need.

Configuration

Readable at a glance.
Safe to commit.

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.

packaging/release.parcel
[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

Outputs

Two native formats.
Either one, or both.

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.

Install Quark Downloader

Welcome to the Quark Downloader Installer

This installer places Quark Downloader in your Applications folder and the quark command-line tool in /usr/local/bin.

  • The application needs macOS 13 or later
  • The command-line tool is optional
Go Back Continue

.pkg

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.

  • Component packages assembled into a product archive
  • Markdown installer pages, converted to HTML on the way in
  • Signed with your Developer ID Installer certificate
Quark Downloader
Quark Downloader
Applications

.dmg

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.

  • Background picture, window size and icon positions
  • Compressed, read-only, and signed
  • Falls back to the default layout when a build machine withholds Automation access

The release flow

Seven steps.
One command.

Each stage feeds the next, and every stage can be inspected with --verbose or previewed with --dry-run before anything is written.

  1. Stage

    Payloads are copied into an internal filesystem that mirrors the install layout. Wildcards expand, modes are applied, and your build output is never modified.

  2. Sign the payload

    Bundles from the inside out — nested frameworks, XPC services, helpers and libraries first, the bundle last. Standalone executables are signed in their own right.

  3. Build

    A component package per component, a distribution file describing the installer, then a product archive. Disk images go through hdiutil.

  4. Sign the artifact

    The package with your Developer ID Installer certificate, the disk image with Developer ID Application. Parcel finds both itself.

  5. Notarize

    Submitted to Apple and awaited. If Apple rejects it, you get Apple's own log rather than an exit code.

  6. Staple

    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.

  7. Verify

    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

Certificates it finds.
Secrets it never sees.

Signing that understands bundles

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.

Credentials stay in the keychain

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

It tells you what is wrong.
All of it. At once.

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
$ 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 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.

Before anything runs

Missing sources, relative destinations, duplicate install paths, invalid modes, malformed bundles and unreadable scripts — caught before a single file is staged.

System paths refused

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.

Certificates checked early

If signing is enabled, Parcel confirms the Developer ID certificates exist and match your team before it starts building.

A doctor for the machine

parcel doctor needs no project. It reports the toolchain, the certificates and the notarization tools, and says plainly what is missing.

Rust projects

Cargo already knows.
So stop repeating yourself.

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.

quark — cargo parcel
$ 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

Nothing to configure

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:

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

A small surface.
On purpose.

Parcel 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.

Options worth knowing

Frequently used options
--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/.

Build it once.
Ship it properly.

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.