Add App Store deployment pipeline for iOS and macOS#126
Conversation
Match the iOS and macOS PRODUCT_BUNDLE_IDENTIFIER (and RunnerTests suffixes) to the App ID registered in the Apple Developer portal (com.cattrall.daccord, Team 8MVM4FMRRC). Android applicationId (com.daccord_projects.daccord) is unchanged, as it is a separate Google Play identifier. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds Fastlane lanes and release-workflow jobs that build the signed Apple "store" variants and upload them to App Store Connect, plus notarize the existing macOS DMG for Gatekeeper-clean direct downloads. - fastlane/Fastfile: `ios beta` (TestFlight), `mac appstore` (sandboxed .pkg → App Store Connect), `mac dmg` (Developer ID + hardened runtime + notarize). Auth + upload via an App Store Connect API key; manual signing forced via gym xcargs so the committed project stays on Automatic for local dev. - release.yml: `ios-appstore` + `mac-appstore` jobs (run on tag push and workflow_dispatch with deploy_ios/deploy_mac toggles); the macOS matrix entry now produces a notarized DMG; `verify` gated to tag pushes. - macos/Runner/AppStore.entitlements: sandboxed entitlements for the Mac App Store build (network, mic, camera, user-selected files); the DMG build keeps the non-sandboxed Release.entitlements. - kAppStoreBuild (--dart-define=APP_STORE=true) disables the in-app GitHub self-updater in store builds (guideline 2.4.5 / sandbox); DMG keeps it. - docs/app-store-deploy.md: secrets + one-time setup runbook. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Developer ID Application cert can only be created by the account holder, not the App Store Connect API key, so it may not be configured. Gate the signing + notarization on DEVELOPER_ID_CERT_P12 being set and fall back to the previous unsigned hdiutil DMG otherwise, so tagged releases never fail for a missing notarization cert. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
build_app passes xcargs to xcodebuild as a raw shell string, so the unescaped space in `CODE_SIGN_IDENTITY=Apple Distribution` split into two tokens — xcodebuild took "Apple" as the identity and choked on the stray remainder, failing the iOS App Store build with `Unknown build action 'iOS'` (exit 65). Shell-escape every xcarg value that can contain a space: the "Apple Distribution" / "Developer ID Application" identities, the provisioning-profile specifier, and the entitlements path. Same latent bug fixed in the `mac dmg` lane's Developer ID xcargs.
Setting PROVISIONING_PROFILE_SPECIFIER/CODE_SIGN_IDENTITY as global xcargs made xcodebuild apply them to every Pod/SwiftPM target, which fail with 'does not support provisioning profiles'. Use update_code_signing_settings on the Runner target (Release config) instead; gym re-signs the whole bundle on export via the export_options profile map. Mac App Store entitlements are swapped in by the workflow (cp AppStore.entitlements over Release.entitlements in that job only). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… controller tests - release.yml: add --no-codesign to mac-appstore flutter build step so Flutter does not attempt to sign during the build phase; gym/Fastlane handles all signing on the archive step (iOS already did this correctly) - Fastfile: move require "tmpdir" to top of file alongside require "shellwords" rather than inside the dmg lane body - Gemfile.lock: commit resolved gem versions so bundler-cache: true works correctly across CI runs and fastlane version is pinned - test/features/updates/update_controller_test.dart: add unit tests covering canInstallInPlace, dismissCurrent, skipCurrent, and UpdateState.updateAvailable; documents compile-time kAppStoreBuild limitation https://claude.ai/code/session_0183v6JCceGijR4eR1XscVoT
Code Review — App Store deployment pipelineReviewed commit Findings ranked by severity🔴 High —
|
- Remove unused accord_settings import in update_controller_test.dart (the unused_import WARNING failed 'flutter analyze --no-fatal-infos'). - iOS/Mac App Store jobs now pass --build-name stripped of any prerelease/build suffix, so a prerelease tag like v0.2.4-rc.1 still uploads a valid numeric CFBundleShortVersionString (0.2.4). - Bump version to 0.2.4-rc.1 for the first end-to-end prerelease run. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
App Store rejected the build for a missing NSPhotoLibraryUsageDescription (the file/image pickers reference the photo library APIs). Add it plus the NSPhotoLibraryAddUsageDescription (saving downloads) and the NSLocationWhenInUseUsageDescription Apple also warned about (referenced by a bundled component; the app does not collect location). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- flutter build macos has no --no-codesign (iOS-only); remove it so the Mac App Store job stops failing with exit 64. - The Developer ID cert is now set, so the notarized-DMG path runs and its failure was sinking the whole release. Wrap it: on failure, fall back to an unsigned DMG (with diagnostics) so the release always completes while the gym developer-id export is iterated on. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
gym exported the .pkg/.app successfully ("Successfully exported and signed the
pkg file") but the lane's Dir["build/..."] glob returned nil (gym's cwd differs
from the lane's), so it aborted with "No .pkg/.app produced". Capture build_app's
return value (absolute path) instead, with the glob only as a fallback.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an android-play job to release.yml (tag push / workflow_dispatch deploy_android toggle) that builds a signed AAB and uploads it to the Play internal testing track via a new fastlane `android play` lane (release_status completed, so testers get it immediately). Add an upload-key release signingConfig to android/app/build.gradle — read from env vars in CI or android/key.properties locally, falling back to the debug key when no keystore is present so secret-less builds (the direct-download APK, flutter run --release) still work. Document the required Play secrets and one-time keystore + service-account setup in docs/app-store-deploy.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Adds a complete App Store deployment pipeline to the release workflow, enabling automated builds and uploads to TestFlight (iOS) and App Store Connect (macOS). Introduces Fastlane configuration for signing, archiving, and notarizing builds, along with support for manual workflow dispatch to test deployments without cutting a real release.
Key Changes
Release workflow enhancements (
.github/workflows/release.yml):workflow_dispatchtrigger withdeploy_iosanddeploy_mactoggles for testing App Store uploads without a tag pushverifyjob to tag pushes only (skipped on workflow_dispatch, leaving only App Store jobs)ios-appstoreandmac-appstorejobs that build store variants and upload to App Store ConnectFastlane configuration (
fastlane/Fastfile,fastlane/Appfile):ios betalane: archives, signs, and uploads iOS build to TestFlightmac appstorelane: archives, signs, and uploads sandboxed macOS.pkgto App Store Connectmac dmglane: re-exports Flutter-built app with Developer ID signing, wraps in universal DMG, and notarizes via App Store Connect APIApp Store entitlements (
macos/Runner/AppStore.entitlements):Bundle ID migration (
com.daccord-projects.daccord→com.cattrall.daccord):App Store build flag (
lib/shared/app_info.dart):kAppStoreBuildconstant (set via--dart-define=APP_STORE=true)Update controller (
lib/features/updates/controllers/update_controller.dart):kAppStoreBuildflagDocumentation (
docs/app-store-deploy.md):Gemfile:
Notable Implementation Details
DEVELOPER_ID_CERT_P12secret is absent, the DMG is built unsigned (previous behavior) with a warning, rather than failing the release.ios-appstore,mac-appstore) compile withAPP_STORE=trueand use sandboxed entitlements (macOS); the DMG build keeps the self-updater and non-sandboxed entitlements.AppStore.entitlementsoverRelease.entitlementsin the store job's checkout only; the DMG job's checkout remains untouched, preserving non-sandboxed behavior.force_manual_signing()targets only the Runner app target to avoid conflicts with Pod/SwiftPM library targets that reject provisioning profiles.https://claude.ai/code/session_01BbSaERwi3UWjqm79JMuzZ7