diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml new file mode 100644 index 0000000..8845458 --- /dev/null +++ b/.github/workflows/prepare-release.yml @@ -0,0 +1,32 @@ +name: Prepare App Store Version + +on: + workflow_dispatch: + inputs: + version: + description: 'Version to prepare (defaults to the branch MARKETING_VERSION)' + required: false + build: + description: 'TestFlight build number to attach (defaults to the latest for the version)' + required: false + +jobs: + prepare: + name: Prepare App Store Version + runs-on: macos-15 + environment: release + if: github.actor == 'adborbas' + steps: + - uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + + - name: Prepare App Store version + env: + ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }} + ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }} + ASC_KEY_P8: ${{ secrets.ASC_KEY_P8 }} + run: bundle exec fastlane prepare_release version:"${{ inputs.version }}" build:"${{ inputs.build }}" diff --git a/RELEASING.md b/RELEASING.md index 88c32fa..d754695 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -2,14 +2,17 @@ Hemera uses release branches to allow development to continue on `main` while a release stabilizes (e.g., during Apple review). -The release is automated with [fastlane](fastlane/Fastfile) and GitHub Actions. Each of the three steps below has a matching lane you can run locally and a `workflow_dispatch` workflow you can trigger from the GitHub **Actions** tab: +The release is automated with [fastlane](fastlane/Fastfile) and GitHub Actions. Each step below has a matching lane you can run locally and a `workflow_dispatch` workflow you can trigger from the GitHub **Actions** tab: | Step | Lane | Workflow | |---|---|---| | Cut a release | `bundle exec fastlane cut_release version:1.3.0` | **Cut Release** | | Upload to TestFlight | `bundle exec fastlane beta` | **Upload to TestFlight** | +| Prepare App Store version | `bundle exec fastlane prepare_release version:1.3.0` | **Prepare App Store Version** | | Publish | `bundle exec fastlane publish version:1.3.0` | **Publish Release** | +**Prepare App Store version** creates/updates the App Store version, attaches a TestFlight build (the latest processed one by default, or a specific build via the `build` input), sets the release notes (from `fastlane/metadata/en-US/release_notes.txt`), and marks it for automatic release once approved. It stops short of submitting — you click **Submit for Review** in App Store Connect. Release notes are a fixed string; edit that file (or override in ASC) to change them. Screenshots and all other metadata are managed manually in App Store Connect (the lane leaves them untouched). + The manual git/`gh` steps below are what each lane does under the hood, kept for reference and for one-off manual releases. ## Cutting a release diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 2b2fae2..b4a1ffa 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -136,6 +136,51 @@ platform :ios do UI.success("Published #{tag}. Opened a back-merge PR for #{branch} → main.") end + + # --------------------------------------------------------------------------- + # 4. Prepare an App Store version for review + # Creates/updates the App Store version, attaches the latest processed + # build, sets the release notes (from fastlane/metadata), and marks it for + # automatic release — everything except the final Submit, which stays a + # manual click in App Store Connect. + # Usage: bundle exec fastlane prepare_release [version:1.4.0] [build:12] + # version defaults to the branch MARKETING_VERSION; build to the latest. + # --------------------------------------------------------------------------- + desc "Prepare an App Store version for review (does not submit)" + lane :prepare_release do |options| + version = options[:version] + version = nil if version.to_s.strip.empty? + version ||= get_xcconfig_value(key: "MARKETING_VERSION") + + api_key = app_store_connect_api_key( + key_id: ENV.fetch("ASC_KEY_ID"), + issuer_id: ENV.fetch("ASC_ISSUER_ID"), + key_content: ENV.fetch("ASC_KEY_P8"), + is_key_content_base64: true, + in_house: false + ) + + # Attach a specific build if given, otherwise the latest build already + # uploaded to TestFlight for this version. + build_number = options[:build] + build_number = nil if build_number.to_s.strip.empty? + build_number ||= latest_testflight_build_number(api_key: api_key, version: version) + + deliver( + api_key: api_key, + app_version: version, + build_number: build_number.to_s, + skip_binary_upload: true, # the build is uploaded by the beta lane + skip_screenshots: true, # screenshots are managed manually in ASC + skip_metadata: false, # push release_notes from fastlane/metadata + automatic_release: true, # go live automatically once approved + submit_for_review: false, # prepare only — submit stays a manual step + run_precheck_before_submit: false, + force: true # no interactive HTML preview on CI + ) + + UI.success("Prepared App Store version #{version} (build #{build_number}) for review. Submit it in App Store Connect when ready.") + end end # Set a KEY = VALUE line in Config/Shared.xcconfig in place. diff --git a/fastlane/metadata/en-US/release_notes.txt b/fastlane/metadata/en-US/release_notes.txt new file mode 100644 index 0000000..f51a6dc --- /dev/null +++ b/fastlane/metadata/en-US/release_notes.txt @@ -0,0 +1 @@ +Bug fixes and small improvements to make Hemera a little more useful every day.