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
7 changes: 7 additions & 0 deletions .github/workflows/cut-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:

permissions:
contents: write
pull-requests: write

jobs:
cut:
Expand All @@ -30,5 +31,11 @@ jobs:
with:
bundler-cache: true

# RELEASE_PAT is a PAT with admin rights on the repo (contents + pull
# requests write). It is required to admin-merge the "advance main" PR:
# the default GITHUB_TOKEN is write-only and cannot bypass the branch
# protection (required review + "Unit Tests" check) that main enforces.
- name: Cut release
env:
GH_TOKEN: ${{ secrets.RELEASE_PAT }}
run: bundle exec fastlane cut_release version:${{ inputs.version }}
24 changes: 21 additions & 3 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ The release is automated with [fastlane](fastlane/Fastfile) and GitHub Actions.

The manual git/`gh` steps below are what each lane does under the hood, kept for reference and for one-off manual releases.

### Secrets

`main` is a protected branch (changes must go through a PR, and a review plus the "Unit Tests" check are required). **Cut Release** advances `main` by opening a PR and **admin-merging** it, which the default `GITHUB_TOKEN` cannot do (it is write-only and cannot bypass protection). The **Cut Release** workflow therefore uses a `RELEASE_PAT` repository secret:

- A personal access token owned by a repo admin, with **contents: write** and **pull requests: write** on this repo (a classic token with `repo` scope also works).
- Admin bypass relies on the branch's `enforce_admins` being **off**, so the admin-merge skips the review and "Unit Tests" requirements for the trivial one-line version bump.

The other release workflows use the App Store Connect API key (`ASC_KEY_ID`, `ASC_ISSUER_ID`, `ASC_KEY_P8`) and match (`MATCH_PASSWORD`, `MATCH_DEPLOY_KEY`); see the workflow files for which step needs which.

## Versioning model

`main` always holds the **next in-development version**, so it never sits on a
Expand All @@ -30,13 +39,22 @@ nothing to merge back at all).

## Cutting a release

1. Branch from `main`:
1. Branch from `main` and push it (the release branch is not protected):
```bash
git checkout -b release/1.4.0 main
git push origin release/1.4.0
```
2. `MARKETING_VERSION` in `Config/Shared.xcconfig` already matches (it's what `main` held); only change it if you're cutting a different version.
3. Advance `main`: bump its `MARKETING_VERSION` to the next minor (e.g. `1.5.0`), commit `"Bump main to 1.5.0 for development"`, and push.
4. Push the release branch and submit to Apple for review.
3. Advance `main` **via a PR** — `main` is protected, so it can't be pushed directly. Put the bump on a branch, open a PR, and merge it:
```bash
git checkout -b chore/bump-main-1.5.0 main
# bump MARKETING_VERSION to the next minor (e.g. 1.5.0) in Config/Shared.xcconfig
git commit -am "Bump main to 1.5.0 for development"
git push origin chore/bump-main-1.5.0
gh pr create --base main --head chore/bump-main-1.5.0 --title "Bump main to 1.5.0 for development" --body "Advance main after cutting release/1.4.0."
gh pr merge chore/bump-main-1.5.0 --squash --admin --delete-branch
```
4. Submit the release branch to Apple for review.

## Fixing issues during review

Expand Down
26 changes: 22 additions & 4 deletions fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ platform :ios do
# development version so main never sits on a released version and always
# stays ahead of the release branch. main normally already holds <version>
# (it tracks the in-development version), so the release branch just pins it.
# The main advance goes through an auto-merged PR (main is protected), so
# GH_TOKEN must be a PAT with admin rights — see RELEASING.md.
# Usage: bundle exec fastlane cut_release version:1.4.0
# ---------------------------------------------------------------------------
desc "Cut a release branch and advance main to the next development version"
Expand All @@ -30,13 +32,29 @@ platform :ios do
end
push_to_git_remote(local_branch: branch, remote_branch: branch, force: false)

# Advance main to the next development version.
sh("git", "checkout", "-B", "main", "origin/main")
# Advance main to the next development version. main is protected: direct
# pushes are rejected (changes must go through a PR), so the bump goes on a
# short-lived branch that is opened as a PR and admin-merged. Opening the PR
# satisfies the non-bypassable "changes via PR" rule; admin-merge bypasses the
# required review and "Unit Tests" check (enforce_admins is off) for this
# trivial one-line bump. This requires GH_TOKEN to be a PAT with admin rights
# — the default Actions token is write-only and cannot admin-merge.
bump_branch = "chore/bump-main-#{next_version}"
sh("git", "checkout", "-B", bump_branch, "origin/main")
set_xcconfig_value(key: "MARKETING_VERSION", value: next_version)
git_commit(path: XCCONFIG, message: "Bump main to #{next_version} for development")
push_to_git_remote(local_branch: "main", remote_branch: "main", force: false)
push_to_git_remote(local_branch: bump_branch, remote_branch: bump_branch, force: false)

UI.success("Cut #{branch} at #{version}; advanced main to #{next_version}. Run the Upload to TestFlight lane next.")
sh(
"gh", "pr", "create",
"--base", "main",
"--head", bump_branch,
"--title", "Bump main to #{next_version} for development",
"--body", "Advance main to the next in-development version after cutting #{branch}."
)
sh("gh", "pr", "merge", bump_branch, "--squash", "--admin", "--delete-branch")

UI.success("Cut #{branch} at #{version}; advanced main to #{next_version} via an auto-merged PR. Run the Upload to TestFlight lane next.")
end

# ---------------------------------------------------------------------------
Expand Down
Loading