Skip to Content
Development

Development

Requirements

  • macOS 14 or later
  • Xcode 26.6 or later for universal packaging. Isolated tests also pass with Xcode 16.4, but that older SwiftPM toolchain fails to generate this package’s universal build graph. This build requirement does not change the app’s macOS 14 minimum.
  • Homebrew, if you want the app to manage real packages — tests never need it

Quick start

git clone https://github.com/brewwery/brewwery.git cd brewwery swift build # compile swift run Brewwery # run the app BREWWERY_LIVE=0 swift test --skip BrewweryLiveTests # isolated tests only

To work in Xcode, open Package.swift.

Commands

CommandDescription
swift buildBuild the core library and the app
swift run BrewweryRun the app without packaging
BREWWERY_LIVE=0 swift test --skip BrewweryLiveTestsIsolated core, app, and rendering tests, with real Homebrew explicitly excluded
Scripts/make-app.shBuild a universal dist/Brewwery.app, ad-hoc signed for local use
bash Scripts/verify-app.sh dist/Brewwery.appVerify compiled version/build, bundle metadata, architectures, resources, and signatures
Scripts/screenshots.shCapture the website and docs screenshots in both themes
Scripts/release.sh --sign "Developer ID Application: …"Build, sign, notarize, and package a release

Tests

SuiteCoversReal Homebrew
BrewweryCoreTestsParsers against recorded Homebrew output, validation, detection, streaming, process-group cancellationNo — a scripted brew stand-in
BrewweryAppTestsOperations and history, search debounce, table navigation and layout, every page rendered in both themes, accessibility checksNo
BrewweryLiveTestsReads, install and uninstall, cancelling a real download, services, taps, custom path, offline failuresYes, opt-in

The live suite changes your Homebrew installation — it installs and removes small packages and a tap — so it only runs when you ask for it:

BREWWERY_LIVE=1 swift test --filter BrewweryLiveTests

To review every page after a UI change, render snapshots to a folder:

BREWWERY_SNAPSHOT_DIR=/tmp/brewwery-snapshots swift test --filter RenderingTests

Screenshots

Scripts/screenshots.sh launches a debug build in demo mode against Scripts/demo-brew.sh, a scripted Homebrew with realistic sample data, and captures Dashboard, Search, Updates, Settings, Packages, and Services in dark and light themes. Demo mode keeps settings, history, and favorites in a temporary location, so screenshots never show or change your own data.

Scripts/screenshots.sh dist/screenshots SHOTS_ONLY="Packages Settings-Light" Scripts/screenshots.sh dist/screenshots

The terminal needs the Screen Recording permission. Keep the Mac idle while it runs so each window stays in front.

Releasing

Releases are universal, signed with a Developer ID, and notarized.

One-time setup:

  1. Install a Developer ID Application certificate in your keychain.
  2. Store notarization credentials:
    xcrun notarytool store-credentials "Brewwery" --team-id <TEAM_ID>
  3. Make sure the Sparkle EdDSA key is in the login keychain under the account brewwery, and that its public key matches SUPublicEDKey in Packaging/Info.plist.

For each release:

  1. Update AppInfo version/build constants, CFBundleShortVersionString and CFBundleVersion in Packaging/Info.plist, CHANGELOG.md, and the release notes in docs/release-notes/.

  2. Run:

    Scripts/release.sh --sign "Developer ID Application: …" \ --site-public "../site/public"

    The script first runs isolated tests and checks the Sparkle public key. It then builds and verifies a universal app, signs it inside out with the hardened runtime, uploads the app and DMG to Apple for notarization, and staples the returned tickets. Notarization is an external upload of release artifacts; credentials stay in the keychain.

    The appcast is generated in a release-specific directory containing only the current DMG, with embedded notes and delta generation disabled. Reusing a directory of old releases with a new download prefix can produce invalid old URLs. With --site-public, the script copies the DMG and feed into the website’s local public/ folder; it does not publish them.

  3. Verify the signed app inside the DMG, bundle version/build, both architectures, notarization tickets, and Sparkle signature. The release script runs Scripts/verify-appcast.swift against the exact DMG and the bundle’s public key; offline regression cases run with swift Scripts/test-appcast.swift "$PWD/Scripts/verify-appcast.swift". Commit and tag the matching source, then upload dist/appcast/Brewwery-<version>.dmg to that GitHub release.

  4. Download the public artifact and compare its SHA-256 with the signed local DMG. The enclosure URL, byte length, and EdDSA signature must describe this exact file.

  5. Update and deploy the website’s version, changelog, DMG, and appcast together, only after the GitHub enclosure is reachable. Publish matching documentation and check the public URLs. A generated feed is not proof of an installed-app update; test download, install, and relaunch separately.

Keep a backup of the Sparkle private key. Without it, installed copies cannot receive updates.

Clean-profile install and restart QA

The 1.1.1 bundle accepts an isolated profile:

dist/Brewwery.app/Contents/MacOS/Brewwery --qa-profile clean-111 -ApplePersistenceIgnoreState YES

Use a new profile name for a clean run. Favorites and History live under ~/Library/Application Support/Brewwery QA/<name>/; app preferences use com.brewwery.qa.<name>. Quit normally and relaunch with the same name to check persistence. The window identifies the QA profile, window-frame autosaving is disabled, and Sparkle is off.

This does not isolate Homebrew itself. Do not install, remove, upgrade, or change services in a QA profile against a working installation. A clean-profile check also does not replace a fresh-account Gatekeeper test, Intel/macOS 14 testing, or a Sparkle update test.

Guidelines

  • Homebrew runs only through HomebrewCommand: no shell, no free-form arguments, every dynamic value validated in HomebrewIdentifier.
  • Anything that changes Homebrew needs an explicit confirmation.
  • Tests must never reach the real Homebrew unless they are in BrewweryLiveTests.
  • Icon-only buttons use IconButton, so they always have an accessible name.
  • Record deliberate behavior changes in docs/ARCHITECTURE-DECISIONS.md.
  • Do not add telemetry, authentication, cloud sync, or monetization.
Last updated on