Skip to content

Submit App Store Review #9

Submit App Store Review

Submit App Store Review #9

name: Submit App Store Review
on:
workflow_dispatch:
inputs:
tag:
description: Release tag to submit
required: true
type: string
dry_run:
description: Exercise the release workflow without submitting to App Review
required: true
type: boolean
default: false
permissions:
contents: read
concurrency:
group: app-store-review-submission
cancel-in-progress: false
env:
FASTLANE_SKIP_UPDATE_CHECK: "1"
jobs:
find-build:
name: Find matching TestFlight build
runs-on: ${{ fromJSON(vars.RUNNER_LABELS_LINUX || '["ubuntu-latest"]') }}
outputs:
build_number: ${{ steps.find.outputs.build_number }}
build_version: ${{ steps.find.outputs.build_version }}
upload_time: ${{ steps.find.outputs.upload_time }}
tag: ${{ steps.resolve-tag.outputs.tag }}
version: ${{ steps.resolve-tag.outputs.version }}
steps:
- uses: actions/checkout@v4
- name: Resolve tag and version
id: resolve-tag
shell: bash
run: |
set -euo pipefail
TAG="${{ inputs.tag }}"
VERSION="${TAG#app/v}"
VERSION="${VERSION#v}"
VERSION="${VERSION%%-rc.*}"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Write App Store Connect credentials
shell: bash
env:
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
ASC_PRIVATE_KEY: ${{ secrets.ASC_PRIVATE_KEY }}
run: |
set -euo pipefail
: "${ASC_KEY_ID:?ASC_KEY_ID secret is required}"
: "${ASC_ISSUER_ID:?ASC_ISSUER_ID secret is required}"
: "${ASC_PRIVATE_KEY:?ASC_PRIVATE_KEY secret is required}"
mkdir -p "$HOME/.appstoreconnect/private_keys"
p8_path="$HOME/.appstoreconnect/private_keys/AuthKey_${ASC_KEY_ID}.p8"
printf '%s' "$ASC_PRIVATE_KEY" > "$p8_path"
chmod 600 "$p8_path"
jq -n \
--arg key_id "$ASC_KEY_ID" \
--arg issuer_id "$ASC_ISSUER_ID" \
--arg key "$ASC_PRIVATE_KEY" \
'{
key_id: $key_id,
issuer_id: $issuer_id,
key: $key,
in_house: false
}' > /tmp/asc_api_key.json
chmod 600 /tmp/asc_api_key.json
{
echo "ASC_KEY_ID=$ASC_KEY_ID"
echo "ASC_ISSUER_ID=$ASC_ISSUER_ID"
} >> "$GITHUB_ENV"
- name: Install Python dependencies
run: |
python3 -m venv .venv-release
.venv-release/bin/python -m pip install --upgrade pip --quiet
.venv-release/bin/python -m pip install PyJWT requests cryptography --quiet
- name: Find build matching release version
id: find
shell: bash
env:
VERSION: ${{ steps.resolve-tag.outputs.version }}
run: |
set +e
.venv-release/bin/python scripts/release/find_qualifying_build.py \
--key-id "$ASC_KEY_ID" \
--issuer-id "$ASC_ISSUER_ID" \
--private-key-path "$HOME/.appstoreconnect/private_keys/AuthKey_${ASC_KEY_ID}.p8" \
--app-id "${{ vars.APPLE_APP_ID }}" \
--latest \
--version "$VERSION" \
--output-format github-output 2> /tmp/find-build.err
status=$?
set -e
if [ "$status" -ne 0 ]; then
echo "::error::No valid TestFlight build found for version $VERSION."
cat /tmp/find-build.err
exit "$status"
fi
approve-release:
name: Approve App Store submission
needs: find-build
runs-on: ${{ fromJSON(vars.RUNNER_LABELS_LINUX || '["ubuntu-latest"]') }}
environment: ${{ vars.APPROVAL_ENVIRONMENT || 'app-store-release' }}
steps:
- name: Release candidate summary
run: |
{
echo "## Release candidate ready for approval"
echo ""
echo "- Version: ${{ needs.find-build.outputs.build_version }}"
echo "- Build: #${{ needs.find-build.outputs.build_number }}"
echo "- Tag: ${{ needs.find-build.outputs.tag }}"
echo "- Uploaded: ${{ needs.find-build.outputs.upload_time }}"
echo "- Dry run: ${{ inputs.dry_run }}"
} >> "$GITHUB_STEP_SUMMARY"
submit:
name: Submit build for App Store review
needs:
- find-build
- approve-release
if: ${{ !inputs.dry_run }}
runs-on: ${{ fromJSON(vars.RUNNER_LABELS_LINUX || '["ubuntu-latest"]') }}
steps:
- uses: actions/checkout@v4
- name: Write App Store Connect credentials
shell: bash
env:
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
ASC_PRIVATE_KEY: ${{ secrets.ASC_PRIVATE_KEY }}
run: |
set -euo pipefail
: "${ASC_KEY_ID:?ASC_KEY_ID secret is required}"
: "${ASC_ISSUER_ID:?ASC_ISSUER_ID secret is required}"
: "${ASC_PRIVATE_KEY:?ASC_PRIVATE_KEY secret is required}"
jq -n \
--arg key_id "$ASC_KEY_ID" \
--arg issuer_id "$ASC_ISSUER_ID" \
--arg key "$ASC_PRIVATE_KEY" \
'{
key_id: $key_id,
issuer_id: $issuer_id,
key: $key,
in_house: false
}' > /tmp/asc_api_key.json
chmod 600 /tmp/asc_api_key.json
- name: Set up Ruby and Bundler
if: ${{ !contains(fromJSON(vars.RUNNER_LABELS_LINUX || '["ubuntu-latest"]'), 'self-hosted') }}
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
bundler-cache: true
working-directory: ${{ vars.IOS_WORKDIR || 'ios' }}
- name: Install Ruby bundle
if: ${{ contains(fromJSON(vars.RUNNER_LABELS_LINUX || '["ubuntu-latest"]'), 'self-hosted') }}
working-directory: ${{ vars.IOS_WORKDIR || 'ios' }}
run: |
export BUNDLE_VERSION=system
echo "BUNDLE_VERSION=system" >> "$GITHUB_ENV"
bundle config set path vendor/bundle
bundle install --jobs 4 --retry 3
- name: Submit build for App Store review
working-directory: ${{ vars.IOS_WORKDIR || 'ios' }}
run: |
if [ -f fastlane/Deliverfile ]; then
mv -f fastlane/Deliverfile fastlane/Deliverfile.release
trap 'mv -f fastlane/Deliverfile.release fastlane/Deliverfile' EXIT
fi
bundle exec fastlane run deliver \
api_key_path:/tmp/asc_api_key.json \
app_identifier:${{ vars.BUNDLE_ID }} \
team_id:${{ vars.TEAM_ID }} \
skip_binary_upload:true \
skip_screenshots:true \
skip_metadata:true \
submit_for_review:true \
run_precheck_before_submit:false \
precheck_include_in_app_purchases:false \
force:true \
phased_release:true \
automatic_release:false \
build_number:"${{ needs.find-build.outputs.build_number }}"
- name: Summary
run: |
{
echo "## App Store submission complete"
echo ""
echo "- Version: ${{ needs.find-build.outputs.build_version }}"
echo "- Build: #${{ needs.find-build.outputs.build_number }}"
echo "- Tag: ${{ needs.find-build.outputs.tag }}"
echo "- Status: Submitted for review"
echo "- Automatic release: disabled"
} >> "$GITHUB_STEP_SUMMARY"