mirror of
https://github.com/we-promise/sure.git
synced 2026-05-08 21:25:00 +00:00
fix: run TestFlight upload on v* tags (#1582)
* fix(ci): trigger TestFlight upload from v tags * fix(ci): archive iOS app with manual signing * fix(ci): avoid applying iOS profile to CocoaPods targets * fix(ci): apply provisioning profile to Runner target only * fix(ci): fix Runner signing patch script path * fix(ci): place App Store auth key where altool expects it * fix(ci): build TestFlight uploads with Xcode 26.4 on macOS 26 * fix(ci): generate unique iOS build number for TestFlight uploads * fix(ci): read iOS marketing version from .sure-version * refactor(ci): remove hardcoded iOS team id anchor * fix(ci): strip prerelease suffix from iOS marketing version --------- Co-authored-by: SureBot <sure-bot@we-promise.com>
This commit is contained in:
98
.github/workflows/ios-testflight.yml
vendored
98
.github/workflows/ios-testflight.yml
vendored
@@ -15,7 +15,7 @@ on:
|
||||
type: string
|
||||
push:
|
||||
tags:
|
||||
- 'ios-v*'
|
||||
- 'v*'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -23,7 +23,7 @@ permissions:
|
||||
jobs:
|
||||
build-and-upload:
|
||||
name: Build signed IPA and upload to TestFlight
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-26
|
||||
timeout-minutes: 120
|
||||
|
||||
steps:
|
||||
@@ -82,6 +82,14 @@ jobs:
|
||||
run: |
|
||||
echo "Skipping TestFlight upload because required credentials are not configured."
|
||||
|
||||
- name: Select Xcode 26
|
||||
if: ${{ steps.check_prereqs.outputs.enabled == 'true' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
sudo xcode-select -s /Applications/Xcode_26.4.app/Contents/Developer
|
||||
xcodebuild -version
|
||||
xcrun --sdk iphoneos --show-sdk-version
|
||||
|
||||
- name: Set up Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
if: ${{ steps.check_prereqs.outputs.enabled == 'true' }}
|
||||
@@ -154,7 +162,44 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
IOS_VERSION="$(tr -d '[:space:]' < ../.sure-version | sed 's/-.*$//')"
|
||||
IOS_BUILD_NUMBER="$(date -u +%Y%m%d%H%M)"
|
||||
ARCHIVE_PATH="$RUNNER_TEMP/Runner.xcarchive"
|
||||
EXPORT_PATH="$PWD/build/ios/ipa"
|
||||
EXPORT_PLIST="$RUNNER_TEMP/ExportOptions.plist"
|
||||
python3 <<'PY'
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
path = Path("ios/Runner.xcodeproj/project.pbxproj")
|
||||
text = path.read_text()
|
||||
|
||||
team = os.environ["IOS_TEAM_ID"]
|
||||
profile = os.environ["PROFILE_NAME"]
|
||||
identity = os.environ["IOS_DISTRIBUTION_CERT_NAME"]
|
||||
|
||||
def patch_block(match):
|
||||
block = match.group(0)
|
||||
if "PRODUCT_BUNDLE_IDENTIFIER = am.sure.mobile;" not in block:
|
||||
return block
|
||||
if "CODE_SIGN_STYLE = Manual;" not in block:
|
||||
block = block.replace("CURRENT_PROJECT_VERSION = \"$(FLUTTER_BUILD_NUMBER)\";", "CURRENT_PROJECT_VERSION = \"$(FLUTTER_BUILD_NUMBER)\";\n\t\t\t\tCODE_SIGN_STYLE = Manual;")
|
||||
if '"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Distribution";' not in block:
|
||||
block = re.sub(r'DEVELOPMENT_TEAM = .*?;', f'"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "{identity}";\n\t\t\t\tDEVELOPMENT_TEAM = {team};', block, count=1)
|
||||
else:
|
||||
block = re.sub(r'"CODE_SIGN_IDENTITY\[sdk=iphoneos\*\]" = ".*?";', f'"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "{identity}";', block)
|
||||
block = re.sub(r'DEVELOPMENT_TEAM = .*?;', f'DEVELOPMENT_TEAM = {team};', block)
|
||||
if "PROVISIONING_PROFILE_SPECIFIER = " in block:
|
||||
block = re.sub(r'PROVISIONING_PROFILE_SPECIFIER = .*?;', f'PROVISIONING_PROFILE_SPECIFIER = "{profile}";', block)
|
||||
else:
|
||||
block = block.replace(f'DEVELOPMENT_TEAM = {team};', f'DEVELOPMENT_TEAM = {team};\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = "{profile}";')
|
||||
return block
|
||||
|
||||
text = re.sub(r'isa = XCBuildConfiguration;\n\s+baseConfigurationReference = .*?;\n\s+buildSettings = \{.*?\n\s+name = (?:Debug|Release|Profile);\n\s+\};', patch_block, text, flags=re.S)
|
||||
path.write_text(text)
|
||||
PY
|
||||
|
||||
cat > "$EXPORT_PLIST" <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
@@ -181,11 +226,37 @@ jobs:
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
CODE_SIGN_IDENTITY="${IOS_DISTRIBUTION_CERT_NAME}" \
|
||||
CODE_SIGN_STYLE=Manual \
|
||||
DEVELOPMENT_TEAM="${IOS_TEAM_ID}" \
|
||||
PROVISIONING_PROFILE_SPECIFIER="${PROFILE_NAME}" \
|
||||
flutter build ipa --release --export-options-plist="$EXPORT_PLIST"
|
||||
if [ -z "$IOS_VERSION" ]; then
|
||||
echo "::error::.sure-version is empty or unreadable"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Using iOS version: $IOS_VERSION"
|
||||
echo "Using iOS build number: $IOS_BUILD_NUMBER"
|
||||
|
||||
flutter build ios \
|
||||
--release \
|
||||
--no-codesign \
|
||||
--build-name="$IOS_VERSION" \
|
||||
--build-number="$IOS_BUILD_NUMBER"
|
||||
|
||||
xcodebuild \
|
||||
-workspace ios/Runner.xcworkspace \
|
||||
-scheme Runner \
|
||||
-configuration Release \
|
||||
-archivePath "$ARCHIVE_PATH" \
|
||||
-destination 'generic/platform=iOS' \
|
||||
MARKETING_VERSION="$IOS_VERSION" \
|
||||
CURRENT_PROJECT_VERSION="$IOS_BUILD_NUMBER" \
|
||||
archive
|
||||
|
||||
mkdir -p "$EXPORT_PATH"
|
||||
|
||||
xcodebuild \
|
||||
-exportArchive \
|
||||
-archivePath "$ARCHIVE_PATH" \
|
||||
-exportPath "$EXPORT_PATH" \
|
||||
-exportOptionsPlist "$EXPORT_PLIST"
|
||||
|
||||
- name: Prepare TestFlight auth key
|
||||
id: testflight_key
|
||||
@@ -195,8 +266,11 @@ jobs:
|
||||
APP_STORE_CONNECT_API_KEY_BASE64: ${{ secrets.APP_STORE_CONNECT_API_KEY_BASE64 }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
KEY_FILE="$RUNNER_TEMP/AuthKey_${APP_STORE_CONNECT_API_KEY_ID}.p8"
|
||||
KEY_DIR="$HOME/.appstoreconnect/private_keys"
|
||||
mkdir -p "$KEY_DIR"
|
||||
KEY_FILE="$KEY_DIR/AuthKey_${APP_STORE_CONNECT_API_KEY_ID}.p8"
|
||||
echo "$APP_STORE_CONNECT_API_KEY_BASE64" | base64 --decode > "$KEY_FILE"
|
||||
chmod 600 "$KEY_FILE"
|
||||
echo "key-file=$KEY_FILE" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Upload IPA to TestFlight
|
||||
@@ -219,8 +293,7 @@ jobs:
|
||||
--file "$IPA_PATH" \
|
||||
--type ios \
|
||||
--apiKey "$APP_STORE_CONNECT_API_KEY_ID" \
|
||||
--apiIssuer "$APP_STORE_CONNECT_API_ISSUER_ID" \
|
||||
--apiPrivateKey "$APP_STORE_CONNECT_API_KEY_FILE"
|
||||
--apiIssuer "$APP_STORE_CONNECT_API_ISSUER_ID"
|
||||
|
||||
- name: Upload build artifact
|
||||
if: ${{ steps.check_prereqs.outputs.enabled == 'true' }}
|
||||
@@ -232,9 +305,14 @@ jobs:
|
||||
|
||||
- name: Cleanup signing keychain
|
||||
if: ${{ always() && steps.check_prereqs.outputs.enabled == 'true' }}
|
||||
env:
|
||||
APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
KEYCHAIN_PATH="${{ steps.signing.outputs.keychain-path }}"
|
||||
if [ -n "$KEYCHAIN_PATH" ] && [ -f "$KEYCHAIN_PATH" ]; then
|
||||
security delete-keychain "$KEYCHAIN_PATH"
|
||||
fi
|
||||
|
||||
KEY_FILE="$HOME/.appstoreconnect/private_keys/AuthKey_${APP_STORE_CONNECT_API_KEY_ID}.p8"
|
||||
rm -f "$KEY_FILE"
|
||||
|
||||
4
.github/workflows/mobile-release.yml
vendored
4
.github/workflows/mobile-release.yml
vendored
@@ -83,10 +83,10 @@ jobs:
|
||||
|
||||
testflight:
|
||||
name: Upload iOS to TestFlight
|
||||
needs: [build, release]
|
||||
needs: [build, prepare_release]
|
||||
uses: ./.github/workflows/ios-testflight.yml
|
||||
with:
|
||||
notes: "Mobile release ${{ needs.release.outputs.tag_name }}"
|
||||
notes: "Mobile release ${{ needs.prepare_release.outputs.tag_name }}"
|
||||
secrets: inherit
|
||||
|
||||
release:
|
||||
|
||||
Reference in New Issue
Block a user