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 onlyTo work in Xcode, open Package.swift.
Commands
| Command | Description |
|---|---|
swift build | Build the core library and the app |
swift run Brewwery | Run the app without packaging |
BREWWERY_LIVE=0 swift test --skip BrewweryLiveTests | Isolated core, app, and rendering tests, with real Homebrew explicitly excluded |
Scripts/make-app.sh | Build a universal dist/Brewwery.app, ad-hoc signed for local use |
bash Scripts/verify-app.sh dist/Brewwery.app | Verify compiled version/build, bundle metadata, architectures, resources, and signatures |
Scripts/screenshots.sh | Capture the website and docs screenshots in both themes |
Scripts/release.sh --sign "Developer ID Application: …" | Build, sign, notarize, and package a release |
Tests
| Suite | Covers | Real Homebrew |
|---|---|---|
BrewweryCoreTests | Parsers against recorded Homebrew output, validation, detection, streaming, process-group cancellation | No — a scripted brew stand-in |
BrewweryAppTests | Operations and history, search debounce, table navigation and layout, every page rendered in both themes, accessibility checks | No |
BrewweryLiveTests | Reads, install and uninstall, cancelling a real download, services, taps, custom path, offline failures | Yes, 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 BrewweryLiveTestsTo review every page after a UI change, render snapshots to a folder:
BREWWERY_SNAPSHOT_DIR=/tmp/brewwery-snapshots swift test --filter RenderingTestsScreenshots
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/screenshotsThe 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:
- Install a Developer ID Application certificate in your keychain.
- Store notarization credentials:
xcrun notarytool store-credentials "Brewwery" --team-id <TEAM_ID> - Make sure the Sparkle EdDSA key is in the login keychain under the account
brewwery, and that its public key matchesSUPublicEDKeyinPackaging/Info.plist.
For each release:
-
Update
AppInfoversion/build constants,CFBundleShortVersionStringandCFBundleVersioninPackaging/Info.plist,CHANGELOG.md, and the release notes indocs/release-notes/. -
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 localpublic/folder; it does not publish them. -
Verify the signed app inside the DMG, bundle version/build, both architectures, notarization tickets, and Sparkle signature. The release script runs
Scripts/verify-appcast.swiftagainst the exact DMG and the bundle’s public key; offline regression cases run withswift Scripts/test-appcast.swift "$PWD/Scripts/verify-appcast.swift". Commit and tag the matching source, then uploaddist/appcast/Brewwery-<version>.dmgto that GitHub release. -
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.
-
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 YESUse 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 inHomebrewIdentifier. - 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.