Files
tty7/mobile/scripts/testflight.sh
T
l0ng-ai f85c0fee76 fix(mobile): number TestFlight builds in UTC (#1050)
A laptop in UTC+8 numbered its builds eight hours ahead of CI, so a CI upload of the same version within eight hours of a local one came out lower and would be refused.
2026-09-30 19:32:02 +08:00

104 lines
4.1 KiB
Bash
Executable File

#!/bin/bash
# Builds the iOS app and uploads it to App Store Connect for TestFlight.
#
# Usage: scripts/testflight.sh [--build-number N] [--no-upload]
# --build-number N the build is <version>.N (default: the time, yyjjjHHMM)
# --no-upload export a signed .ipa into build/testflight/ instead
#
# Signs in one of two ways:
# - Xcode signed in (Settings → Accounts) to an account on team 78SZC3DQ7B.
# - An App Store Connect API key, as CI does: ASC_KEY_ID and ASC_ISSUER_ID set,
# and the key at ASC_KEY_PATH (default
# ~/.appstoreconnect/private_keys/AuthKey_<ASC_KEY_ID>.p8). Xcode then signs
# with a certificate Apple keeps in the cloud, so no keychain is needed.
#
# Why not just `tauri ios build --export-method app-store-connect`:
# - The archive is signed with a development profile first, and the team has
# no registered devices to make one from. So the archive is built unsigned
# and Xcode signs it for the App Store at export, where no device is needed.
#
# The default build number is the time — year, day of the year, hour, minute —
# so each upload is higher than the last without keeping a counter, and it
# stays under 2^31. In UTC, so a laptop and CI agree on the order.
set -euo pipefail
cd "$(dirname "$0")/.."
UPLOAD=1
N=$(date -u +%y%j%H%M)
while [ $# -gt 0 ]; do
case "$1" in
--no-upload) UPLOAD=0 ;;
--build-number) N="${2:?--build-number needs a number}"; shift ;;
*) echo "usage: $0 [--build-number N] [--no-upload]" >&2; exit 2 ;;
esac
shift
done
TEAM=78SZC3DQ7B
PROJECT=src-tauri/gen/apple/tty7-mobile.xcodeproj/project.pbxproj
ARCHIVE=src-tauri/gen/apple/build/tty7-mobile_iOS.xcarchive
OUT=build/testflight
mkdir -p "$OUT"
[ -f "$PROJECT" ] || npm run tauri ios init -- --ci
AUTH=()
if [ -n "${ASC_KEY_ID:-}" ]; then
KEY="${ASC_KEY_PATH:-$HOME/.appstoreconnect/private_keys/AuthKey_$ASC_KEY_ID.p8}"
[ -f "$KEY" ] || { echo "no App Store Connect key at $KEY" >&2; exit 1; }
AUTH=(-authenticationKeyPath "$KEY" -authenticationKeyID "$ASC_KEY_ID"
-authenticationKeyIssuerID "${ASC_ISSUER_ID:?ASC_ISSUER_ID is needed with ASC_KEY_ID}")
fi
# `tauri ios init` fills the asset catalog with Tauri's placeholder icon;
# put ours back so a regenerated gen/ never ships it.
cp src-tauri/icons/ios/*.png src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/
# Release signing off for the archive only; the project is put back however
# this exits.
cp "$PROJECT" "$OUT/project.pbxproj.orig"
trap 'cp "$OUT/project.pbxproj.orig" "$PROJECT"' EXIT
perl -0pi -e 's{(/\* release \*/ = \{\n\t+isa = XCBuildConfiguration;\n\t+buildSettings = \{\n)(?=(?:(?!\n\t+\};).)*?CODE_SIGN_ENTITLEMENTS)}{$1\t\t\t\tCODE_SIGNING_ALLOWED = NO;\n}sg' "$PROJECT"
if [ "$(grep -c 'CODE_SIGNING_ALLOWED = NO' "$PROJECT")" != 1 ]; then
echo "could not find the app target's release configuration in $PROJECT" >&2
exit 1
fi
echo "==> Archiving build number $N"
rm -rf "$ARCHIVE"
npx tauri ios build --archive-only --build-number "$N"
APP=$(ls -d "$ARCHIVE"/Products/Applications/*.app)
VERSION=$(/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$APP/Info.plist")
BUILD=$(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "$APP/Info.plist")
DESTINATION=$([ "$UPLOAD" = 1 ] && echo upload || echo export)
cat > "$OUT/ExportOptions.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">
<plist version="1.0">
<dict>
<key>method</key><string>app-store-connect</string>
<key>destination</key><string>$DESTINATION</string>
<key>teamID</key><string>$TEAM</string>
<key>signingStyle</key><string>automatic</string>
<key>manageAppVersionAndBuildNumber</key><false/>
</dict>
</plist>
EOF
echo "==> Signing$([ "$UPLOAD" = 1 ] && echo " and uploading") $VERSION ($BUILD)"
xcodebuild -exportArchive \
-archivePath "$ARCHIVE" \
-exportOptionsPlist "$OUT/ExportOptions.plist" \
-exportPath "$OUT" \
-allowProvisioningUpdates \
${AUTH[@]+"${AUTH[@]}"}
if [ "$UPLOAD" = 1 ]; then
echo "==> Uploaded $VERSION ($BUILD); it shows in TestFlight once App Store Connect has processed it"
else
echo "==> Signed .ipa in $OUT"
fi