Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/prepare-release.yml
Original file line number Diff line number Diff line change
@@ -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 }}"
5 changes: 4 additions & 1 deletion RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
45 changes: 45 additions & 0 deletions fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions fastlane/metadata/en-US/release_notes.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Bug fixes and small improvements to make Hemera a little more useful every day.
Loading