Overview
Add a GitHub Actions workflow that builds the app on every pull request, assembles a .app bundle, and uploads it as a downloadable artifact so reviewers can test changes without building locally.
Signing approach for PR builds
PR artifacts use ad-hoc signing (codesign --sign -) — the same approach the Makefile already uses. This requires no Apple Developer account, no secrets, and no CI configuration beyond the build itself. Reviewers download the artifact, right-click → Open to bypass Gatekeeper on first launch (one-time prompt), and test normally.
Ad-hoc signing is appropriate for PR review builds. Proper Developer ID signing and notarization is tracked separately in #523 and will apply to release builds.
Makefile prerequisite
The current make run target assembles the bundle and immediately kills/relaunches the app — this cannot run in CI. A new make bundle target is needed that assembles the .app without launching it:
## Assemble the app bundle without launching (used by CI)
bundle: build install
@mkdir -p "$(APP_BUNDLE)/Contents/MacOS"
@mkdir -p "$(APP_BUNDLE)/Contents/Resources"
cp $(INFO_PLIST) "$(APP_BUNDLE)/Contents/Info.plist"
cp $(UNIV_DIR)/ControlPlane "$(APP_BINARY)"
cp $(UNIV_DIR)/cpctl "$(APP_BUNDLE)/Contents/MacOS/cpctl"
cp $(ICON) "$(APP_BUNDLE)/Contents/Resources/AppIcon.icns"
codesign --force --deep --sign - --identifier "com.controlplane.app" "$(APP_BUNDLE)"
@echo "Bundle assembled → $(APP_BUNDLE)"
GitHub Actions workflow
File: .github/workflows/pr-build.yml
name: PR Build
on:
pull_request:
branches: [main]
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Select Xcode
run: sudo xcode-select -s /Applications/Xcode.app
- name: Build and assemble bundle
run: make bundle
- name: Zip app bundle
run: |
cd .build/universal/debug
zip -r ControlPlane.zip ControlPlane.app
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ControlPlane-PR${{ github.event.pull_request.number }}
path: .build/universal/debug/ControlPlane.zip
retention-days: 14
Notes
macos-latest runners include Swift and the full Xcode toolchain
- Universal binary build (arm64 + x86_64 via lipo) works on GitHub-hosted runners — both architectures are cross-compiled on the same machine
- Build time will be several minutes on first run (cold SPM cache); subsequent runs benefit from dependency caching via
actions/cache on .build/checkouts
- The artifact is named after the PR number for easy identification
- 14-day retention keeps artifacts available for the review window without accumulating indefinitely
Future work
When #523 (code signing and notarization) is implemented, a separate release.yml workflow can be added that triggers on version tags, uses stored Developer ID certificates, notarizes the bundle, and publishes a GitHub Release.
Overview
Add a GitHub Actions workflow that builds the app on every pull request, assembles a
.appbundle, and uploads it as a downloadable artifact so reviewers can test changes without building locally.Signing approach for PR builds
PR artifacts use ad-hoc signing (
codesign --sign -) — the same approach the Makefile already uses. This requires no Apple Developer account, no secrets, and no CI configuration beyond the build itself. Reviewers download the artifact, right-click → Open to bypass Gatekeeper on first launch (one-time prompt), and test normally.Ad-hoc signing is appropriate for PR review builds. Proper Developer ID signing and notarization is tracked separately in #523 and will apply to release builds.
Makefile prerequisite
The current
make runtarget assembles the bundle and immediately kills/relaunches the app — this cannot run in CI. A newmake bundletarget is needed that assembles the.appwithout launching it:GitHub Actions workflow
File:
.github/workflows/pr-build.ymlNotes
macos-latestrunners include Swift and the full Xcode toolchainactions/cacheon.build/checkoutsFuture work
When #523 (code signing and notarization) is implemented, a separate
release.ymlworkflow can be added that triggers on version tags, uses stored Developer ID certificates, notarizes the bundle, and publishes a GitHub Release.