super-productivity/docs/apple-release-automation.md
Johannes Millan dc3523b1c7
ci: auto-submit iOS and macOS App Store builds for review (#7857)
* ci: auto-submit iOS and macOS App Store builds for review

The iOS and Mac App Store workflows previously stopped after uploading the
build to App Store Connect via altool, leaving version creation, "What's New"
and submission as manual steps.

Add fastlane lanes (ios/mac release) that upload the prebuilt .ipa / MAS .pkg
using App Store Connect API key auth (reusing the existing notarization key
secrets), push release notes derived from build/release-notes.md, wait for
processing, submit for review and flag automatic release on approval.

Final version tags submit for review; pre-release tags (RC/beta/alpha) and
manual runs only upload the build. Listing metadata and screenshots remain
curated by hand in App Store Connect.

https://claude.ai/code/session_014c1W1mX7tfvFzpZ6wyWzsJ

* ci: wire iOS and Mac Store workflows to fastlane submit lane

The previous commit added the fastlane lanes but the workflow edits were not
applied. Replace the altool validate/upload steps in the iOS and Mac App Store
workflows with the fastlane submit lane: install fastlane, generate the App
Store "What's New" notes and run `fastlane <platform> release` with App Store
Connect API key auth.

Also extend the Mac workflow's harden-runner egress allowlist with the
rubygems and App Store Connect endpoints used by fastlane.

https://claude.ai/code/session_014c1W1mX7tfvFzpZ6wyWzsJ

* fix(ci): harden Apple App Store auto-submission after review

Address review findings on the iOS/macOS App Store automation:

- Tag gating: submit only when the tag has no "-" (final semver), instead of
  denylisting RC/beta/alpha. GitHub Actions contains() is case-sensitive and
  the repo's RC tags are mostly lowercase (-rc.N), so the old guard would have
  auto-submitted release candidates to production review.
- Fastfile: set skip_metadata so deliver no longer reads back and re-uploads
  curated listing fields; push only "What's New" via an inline release_notes
  hash. Warn against verbose mode (can dump the API key).
- Gemfile.lock: add arm64-darwin/x86_64-darwin platforms so bundle install
  works on the macOS runners.
- Workflows: install deps via pinned ruby/setup-ruby (bundler cache), and
  resolve the artifact path with a strict nullglob check (exactly one match)
  instead of ls | head.
- release-notes script: tighten emphasis regexes so stray * / _ (globs,
  snake_case) survive, anchor footer patterns so legitimate "download" lines
  are not dropped, and drop a duplicate mkdir.
- Docs: document the hyphen-based gate, single-use build numbers, automatic
  release behavior and inline validation.

https://claude.ai/code/session_014c1W1mX7tfvFzpZ6wyWzsJ

* fix(ci): correct deliver metadata + setup-ruby version (second review pass)

Two bugs introduced by the previous review-fix commit, both confirmed against
upstream source:

- Fastfile: skip_metadata: true makes deliver's upload_metadata return early
  (verified in fastlane 2.225.0 deliver/lib/deliver/upload_metadata.rb), so the
  "What's New" notes were never uploaded. Revert to metadata_path pointing at a
  dir that contains only <locale>/release_notes.txt; load_from_filesystem reads
  only that file (next unless File.exist?) with no remote read-back, so other
  listing fields stay untouched. Removed the now-unused inline release_notes
  helper.
- Workflows: ruby/setup-ruby throws when ruby-version is unset and no
  .ruby-version file exists (it does not fall back to system Ruby). Pin
  ruby-version: '3.3' in both workflows.

Docs updated to match the corrected metadata approach.

https://claude.ai/code/session_014c1W1mX7tfvFzpZ6wyWzsJ

* fix(ci): remove invalid wait_for_uploaded_build from deliver lanes

wait_for_uploaded_build is a pilot/upload_to_testflight option, not a deliver one. Passing it to upload_to_app_store makes fastlane raise on the unknown key and fail both iOS and macOS release lanes on every run. deliver already waits for the build to finish processing during submit (select_build -> wait_for_build_processing_to_be_complete), so no replacement is needed.

Also pin the ruby/setup-ruby comment to its resolved version (v1.310.0), and slice the App Store release notes by code point so a multi-byte character is never split at the 4000-char cap.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-01 11:42:16 +02:00

5.8 KiB
Raw Blame History

Apple (iOS & macOS) release automation

Pushing a final version tag (vX.Y.Z) builds, signs, uploads and submits the iOS and macOS App Store builds for review, set to release automatically once Apple approves them. The only step that is not automated is Apple's human review.

Pipeline

Target Workflow Output
iOS App Store .github/workflows/build-ios.yml .ipa → App Store Connect
Mac App Store .github/workflows/build-publish-to-mac-store-on-release.yml MAS .pkg → App Store Connect
Mac direct download (notarized DMG/zip, auto-update) .github/workflows/build.yml (mac-bin) GitHub release asset

On a tag push each workflow builds and signs the artifact, then runs a fastlane lane (fastlane/Fastfile, ios release / mac release) that:

  1. Uploads the artifact to App Store Connect. Apple's binary validation runs inline during the upload (this replaces the previous standalone altool --validate-app step).
  2. Pushes only the "What's New" release notes (derived from build/release-notes.md by tools/prepare-appstore-release-notes.js). The lane points metadata_path at a dir containing only <locale>/release_notes.txt; deliver reads just that file and skips every other field (no remote read-back), so the description, keywords, screenshots, … curated by hand in App Store Connect are left untouched. (skip_metadata is intentionally not set — it would make deliver upload no notes at all.)
  3. Waits for App Store Connect to finish processing the build.
  4. Submits the version for review with automatic release on approval.

build/release-notes.md is a committed snapshot regenerated at release time (see tools/release-notes.js). If a tag is pushed without that file refreshed for the new version, stale notes upload silently — make sure the release-notes commit lands before tagging.

Submit vs. upload-only

SUBMIT_FOR_REVIEW is computed per run as startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '-'):

  • Final tag (vX.Y.Z, no hyphen) → upload and submit for review.
  • Pre-release tag (any tag containing -, e.g. v18.0.0-rc.0, v17.0.0-RC.13, -beta.1, -alpha.0) or manual workflow_dispatch → upload only (build lands in App Store Connect / TestFlight, no store submission).

The gate keys on the presence of - rather than denylisting RC/beta/ alpha, because GitHub Actions contains() is case-sensitive and this repo's RC tags are predominantly lowercase -rc.N. Every pre-release tag in the repo's history contains -; no final tag does.

Required secrets

Authentication uses an App Store Connect API key (reused from the notarization secrets), which is more robust in CI than an Apple ID + app-specific password:

Secret Used as Purpose
mac_api_key ASC_KEY_CONTENT Contents of the .p8 key file (raw PEM, including the -----BEGIN/END PRIVATE KEY----- lines)
mac_api_key_id ASC_KEY_ID API key id
mac_api_key_issuer_id ASC_ISSUER_ID API issuer id

Important: the API key must belong to a user with the App Manager role (or higher). A key with only the Developer role can upload/notarize but cannot create a version or submit it for review. If submission fails with a permissions error, mint a new key with the App Manager role and update the three secrets above.

Caveats

  • Apple review is the only manual gate — it is performed by humans (~12 days) and can be rejected. Everything up to and including submission is automated.
  • automatic_release: true ships the version to 100% of users the moment Apple approves it (no manual "Release this version" click, no staged rollout). If you'd prefer a human go-live or phased rollout, set automatic_release: false (and/or phased_release: true for iOS) in fastlane/Fastfile.
  • Build numbers are single-use. If the lane fails after the binary uploads but before the submission completes (network drop, App-Manager-role error, export-compliance pause), simply re-running won't work — App Store Connect rejects a duplicate build number. Recovery means finishing the submission by hand in App Store Connect, or bumping the build number and re-tagging.
  • "What's New" locales: only en-US notes are generated. If the App Store listing has additional active locales, Apple may require "What's New" text for them on submission. Add more release_notes.txt files (or extend tools/prepare-appstore-release-notes.js) as needed.
  • Export compliance: if ios/App/App/Info.plist does not set ITSAppUsesNonExemptEncryption, App Store Connect will pause the submission to ask the encryption question. Set it once to keep submission fully hands-off.
  • Never enable fastlane verbose mode (--verbose / FASTLANE_VERBOSE) in these lanes — verbose output can dump the deliver options hash, which carries the API key material.