Tag-driven flow. Push vX.Y.Z → CI builds, signs, notarizes, staples, attaches DMG, generates changelog, creates GitHub Release.
Configure under Settings → Secrets and variables → Actions:
| Secret | Source |
|---|---|
BUILD_CERTIFICATE_BASE64 |
base64 -i DeveloperID.p12 (Developer ID Application cert exported from Keychain Access). |
P12_PASSWORD |
Password set when exporting the .p12. |
KEYCHAIN_PASSWORD |
Any random string. CI uses it for an ephemeral keychain. |
NOTARY_APPLE_ID |
Apple ID email used for notarization. |
NOTARY_TEAM_ID |
L23PD654Q3. |
NOTARY_PASSWORD |
App-specific password from appleid.apple.com. |
- Keychain Access → Certificate Assistant → Request Certificate from CA.
- Apple Developer portal → Certificates →
+→ Developer ID Application, upload the CSR. - Download
.cer, double-click to install. Right-click the cert → Export →.p12with a strong password. base64 -i DeveloperID.p12 | pbcopy→ paste intoBUILD_CERTIFICATE_BASE64.
Mirror CI locally to catch signing / notarization failures before burning a tag.
brew install create-dmg jq
# Local .env (gitignored) — same values as GitHub secrets:
cat > .env <<'EOF'
BUILD_CERTIFICATE_BASE64=...
P12_PASSWORD=...
EOF
# Import Developer ID cert into login keychain:
scripts/import-cert.sh
# Store notary credentials in keychain:
xcrun notarytool store-credentials AI_DEVTOOLS_NOTARY \
--apple-id "<apple-id>" \
--team-id "L23PD654Q3" \
--password "<app-specific-password>"scripts/release-local.sh # build + sign + verify + dmg (no Apple round-trip)
scripts/release-local.sh --notarize # full pipeline incl. notary submit
scripts/release-local.sh --notarize --staple # full + staple ticket (matches CI)What it catches:
- Missing Developer ID cert in keychain
- Hardened runtime not applied (fails before submit)
- Secure timestamp missing
codesign --verify --deep --strictfailures (unsigned nested binary)- Notarization rejection — auto-prints Apple log
Output lands in build/local/ (gitignored).
scripts/release.sh 1.1.0 # stable
scripts/release.sh 1.2.0-beta.1 # prerelease (auto-flagged)
git push origin main
git push origin v1.1.0CI on tag push:
- Bumps
MARKETING_VERSION+CURRENT_PROJECT_VERSIONinproject.pbxproj. - Imports cert into ephemeral keychain.
xcodebuild archivewithENABLE_HARDENED_RUNTIME=YES+--timestamp --options=runtime.xcodebuild -exportArchivewithbuild-config/ExportOptions.plist.- Verify signing — fails fast if hardened runtime / timestamp / deep verify fail.
create-dmgproducesAiDevtools.dmg.notarytool submit --wait(JSON output, captures submission ID).- On rejection: dumps
notarytool logautomatically. stapler staple+ validate.- Generates changelog from
git log PREV_TAG..TAG. - Publishes GitHub Release with the DMG attached.
The CI workflow (ci.yml) skips on release commits (chore: release v*) since the release workflow already covers that build.
Actions → Release → Run workflow → tag = vX.Y.Z (must already exist).
The app polls https://api.github.com/repos/techgocodingnow/ai-devtools/releases/latest on launch (6h throttle) and via AiDevtools → Check for Updates… (⌘U). Drafts and prereleases are skipped.
Notarization status: Invalid
Common causes:
| Cause | Fix |
|---|---|
| Hardened runtime not enabled | ENABLE_HARDENED_RUNTIME=YES (already set in workflow) |
| Secure timestamp missing | --timestamp in OTHER_CODE_SIGN_FLAGS (already set) |
get-task-allow=true in entitlements |
Remove from Release-config entitlements |
| Unsigned nested binary / framework | codesign --verify --deep --strict exposes; sign all helpers |
Fetch log for any submission:
xcrun notarytool log <submission-id> --keychain-profile AI_DEVTOOLS_NOTARY