Skip to content

Latest commit

 

History

History
162 lines (112 loc) · 6.84 KB

File metadata and controls

162 lines (112 loc) · 6.84 KB

Releasing TaskMenu

TaskMenu releases are published from GitHub Actions when a vX.Y.Z tag is pushed. The release workflow builds a signed archive, packages it into a DMG, notarizes the DMG with Apple, and uploads the DMG plus a SHA-256 checksum to the GitHub release.

Homebrew cask distribution is maintained in the public crazytan/homebrew-tap repository.

One-time GitHub setup

Add these repository secrets in GitHub under Settings -> Secrets and variables -> Actions -> Secrets:

Secret Value
BUILD_CERTIFICATE_BASE64 Base64-encoded .p12 export of your Developer ID Application certificate
BUILD_CERTIFICATE_PASSWORD Password used when exporting the .p12 file
APP_STORE_CONNECT_API_KEY_BASE64 Base64-encoded App Store Connect API private key (.p8)
GOOGLE_CLIENT_ID Google OAuth client ID used by release builds
APP_STORE_CONNECT_KEY_ID App Store Connect API key ID
APP_STORE_CONNECT_ISSUER_ID App Store Connect issuer ID

Add these repository variables under Settings -> Secrets and variables -> Actions -> Variables:

Variable Value
GOOGLE_REDIRECT_SCHEME Google OAuth redirect scheme, for example com.googleusercontent.apps.<client-id-prefix>
APPLE_TEAM_ID Apple Developer Team ID, for example V82M9YX8BR

The release workflow also accepts GOOGLE_REDIRECT_SCHEME and APPLE_TEAM_ID as secrets for compatibility, but variables are preferred when the values are not sensitive.

TaskMenu uses a native OAuth client with PKCE, so release builds do not embed a Google client secret.

Export the signing certificate

  1. Open Keychain Access on the Mac that has your Developer ID certificate.
  2. Find Developer ID Application: Your Name (TEAMID).
  3. Expand the certificate row and make sure the private key is included.
  4. Select the certificate and private key together, then choose File -> Export Items....
  5. Save as DeveloperIDApplication.p12 and set a strong export password.
  6. Copy the base64 form into the GitHub secret:
base64 -i DeveloperIDApplication.p12 | tr -d '\n' | pbcopy

Paste the clipboard into BUILD_CERTIFICATE_BASE64. Put the export password in BUILD_CERTIFICATE_PASSWORD.

Create the App Store Connect API key

Create an App Store Connect API key in App Store Connect -> Users and Access -> Integrations -> App Store Connect API. A key with Developer access is enough for notarization.

Download the .p8 key once, then copy the base64 form into APP_STORE_CONNECT_API_KEY_BASE64:

base64 -i AuthKey_XXXXXXXXXX.p8 | tr -d '\n' | pbcopy

Store the key ID in APP_STORE_CONNECT_KEY_ID and the issuer ID in APP_STORE_CONNECT_ISSUER_ID.

Notarization troubleshooting

If Apple rejects the submission (status Invalid), the release job prints the full notarization log from notarytool log before failing. The per-file issues in that log are the place to start.

If the release job fails with:

HTTP status code: 401. Unauthenticated.

then Apple rejected the App Store Connect API credentials. Check these values first:

  • APP_STORE_CONNECT_API_KEY_BASE64 must be the base64 text of the downloaded .p8 private key file, not the JSON key metadata.
  • APP_STORE_CONNECT_KEY_ID must match the key ID shown for that same .p8 file.
  • APP_STORE_CONNECT_ISSUER_ID must be the issuer ID from App Store Connect API access.
  • The API key must be active, team-scoped, and have at least Developer access.

You can validate the same credentials locally before pushing a tag:

xcrun notarytool history \
  --key "$HOME/private_keys/AuthKey_XXXXXXXXXX.p8" \
  --key-id "XXXXXXXXXX" \
  --issuer "00000000-0000-0000-0000-000000000000"

Release checklist

  1. Move changelog entries from Unreleased to a version heading:
## vX.Y.Z (YYYY-MM-DD)
  1. Update MARKETING_VERSION and CURRENT_PROJECT_VERSION in project.yml.
  2. Regenerate the project if you want the checked-in .xcodeproj to reflect the new version:
xcodegen generate
  1. Run tests:
xcodebuild test -project TaskMenu.xcodeproj -scheme TaskMenu \
  -configuration Debug \
  -destination "platform=macOS"
  1. Commit and push to main.
  2. Create and push the release tag:
git tag -a vX.Y.Z -m "TaskMenu vX.Y.Z"
git push origin main vX.Y.Z

The release workflow validates that the tag version matches MARKETING_VERSION and that CHANGELOG.md has a matching section before it publishes anything.

  1. After the GitHub release publishes, update the Homebrew cask in crazytan/homebrew-tap:
brew tap crazytan/tap
cd "$(brew --repository crazytan/tap)"

version="${VERSION:-$(gh release view --repo crazytan/TaskMenu --json tagName --jq '.tagName | sub("^v"; "")')}"
sha256="$(curl -LfsS "https://github.com/crazytan/TaskMenu/releases/download/v${version}/TaskMenu-${version}.dmg.sha256" | awk '{print $1}')"

perl -0pi -e "s/version \"[^\"]+\"/version \"${version}\"/" Casks/taskmenu.rb
perl -0pi -e "s/sha256 \"[0-9a-f]+\"/sha256 \"${sha256}\"/" Casks/taskmenu.rb

brew audit --cask --strict crazytan/tap/taskmenu
brew livecheck --cask crazytan/tap/taskmenu

git add Casks/taskmenu.rb
git commit -m "Update TaskMenu to ${version}"
git push

Manual workflow dispatch

You can also run Release manually from GitHub Actions. Enter the version without the leading v and choose whether the GitHub release should be marked as a prerelease. The workflow still expects project.yml and CHANGELOG.md to match that version.

Two guards apply to manual runs:

  • If the tag vX.Y.Z already exists, the workflow must be dispatched from that tag (or a ref pointing at the same commit); it fails if the checked-out commit differs from the tagged commit. If the tag does not exist yet, publishing creates it at the dispatched commit.
  • Release assets are immutable: if the release already has a DMG or checksum with the same name, the upload fails instead of replacing it. Delete the bad asset (or the release) manually first if you really need to republish.

Local DMG packaging

After producing a signed app bundle, you can package it locally:

VERSION="X.Y.Z"
SIGNING_IDENTITY="Developer ID Application" \
APP_STORE_CONNECT_KEY_ID="XXXXXXXXXX" \
APP_STORE_CONNECT_ISSUER_ID="00000000-0000-0000-0000-000000000000" \
APP_STORE_CONNECT_KEY_PATH="$HOME/private_keys/AuthKey_XXXXXXXXXX.p8" \
./scripts/make_dmg.sh \
  --app build/TaskMenu.xcarchive/Products/Applications/TaskMenu.app \
  --version "$VERSION" \
  --output-dir build/artifacts

When the APP_STORE_CONNECT_* variables are set, the script notarizes the DMG, staples it, and runs a Gatekeeper assessment (spctl --assess) that must pass. Without them it skips notarization and prints skipping Gatekeeper assessment (no notarization credentials); such a DMG is fine for local testing but is not distributable.