Skip to main content

max / goingson

6.6 KB · 172 lines History Blame Raw
1 #!/usr/bin/env bash
2 set -euo pipefail
3
4 # GoingsOn iOS Release Script
5 # Builds release archive, exports IPA, and uploads to TestFlight
6 #
7 # Prerequisites:
8 # - Xcode with iOS SDK
9 # - Apple Developer account with PLA accepted
10 # - App ID registered for com.goingson.app
11 # - App Store Connect API credentials in the environment: source
12 # ~/.tauri/passwords.env (APPLE_API_KEY_ID / APPLE_API_ISSUER_ID /
13 # APPLE_API_KEY_PATH). They are not in this file; see Configuration below.
14 #
15 # Usage:
16 # ./dist/release-ios.sh # full flow: build + export + upload
17 # ./dist/release-ios.sh --build-only # build archive only, skip upload
18 # ./dist/release-ios.sh --upload-only # export + upload existing archive
19
20 cd "$(dirname "$0")/.."
21
22 # --- Configuration ---
23 # App Store Connect API credentials come from the environment, never from this
24 # file: this script is version-controlled and the repo is mirrored publicly.
25 # Issuer + key id are two thirds of the notary credential (the .p8 is the third),
26 # so they live in ~/.tauri/passwords.env, which is not in git. Source it first:
27 # source ~/.tauri/passwords.env && ./dist/release-ios.sh
28 API_KEY_ID="${APPLE_API_KEY_ID:?set APPLE_API_KEY_ID -- source ~/.tauri/passwords.env}"
29 API_ISSUER_ID="${APPLE_API_ISSUER_ID:?set APPLE_API_ISSUER_ID -- source ~/.tauri/passwords.env}"
30 API_KEY_PATH="${APPLE_API_KEY_PATH:?set APPLE_API_KEY_PATH -- source ~/.tauri/passwords.env}"
31 ARCHIVE_PATH="src-tauri/gen/apple/build/goingson-desktop_iOS.xcarchive"
32 EXPORT_DIR="dist/ios"
33 EXPORT_PLIST="src-tauri/gen/apple/ExportOptions.plist"
34 PROJECT_YML="src-tauri/gen/apple/project.yml"
35
36 # --- Parse args ---
37 BUILD=true
38 UPLOAD=true
39 for arg in "$@"; do
40 case "$arg" in
41 --build-only) UPLOAD=false ;;
42 --upload-only) BUILD=false ;;
43 -h|--help)
44 echo "Usage: $0 [--build-only | --upload-only]"
45 exit 0
46 ;;
47 esac
48 done
49
50 # --- Read version from tauri.conf.json ---
51 VERSION=$(python3 -c "import json; print(json.load(open('src-tauri/tauri.conf.json'))['version'])")
52 echo "Version: $VERSION"
53
54 # --- Sync version to project.yml ---
55 if grep -q "CFBundleShortVersionString:" "$PROJECT_YML"; then
56 sed -i '' "s/CFBundleShortVersionString: .*/CFBundleShortVersionString: $VERSION/" "$PROJECT_YML"
57 sed -i '' "s/CFBundleVersion: .*/CFBundleVersion: \"$VERSION\"/" "$PROJECT_YML"
58 echo "Synced version $VERSION to $PROJECT_YML"
59 fi
60
61 # --- Verify API key ---
62 if [ ! -f "$API_KEY_PATH" ]; then
63 echo "Error: API key not found at $API_KEY_PATH"
64 echo "Set APPLE_API_KEY_PATH to the correct path"
65 exit 1
66 fi
67
68 # --- Flatten app icons (App Store rejects alpha) ---
69 # `cargo tauri ios init` and ad-hoc icon regenerations have re-seeded the
70 # appiconset with alpha-channel PNGs more than once (v0.3.1 shipped the Tauri
71 # default logo; v0.3.4 upload first failed with "Invalid large app icon ...
72 # can't be transparent or contain an alpha channel"). Re-flatten every build
73 # from media/logo-go-icon.svg so this can't regress silently.
74 ICON_SVG="media/logo-go-icon.svg"
75 ICON_OUT="src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset"
76 ICON_TMP="$(mktemp -t goingson-icon-XXXXXX).png"
77 if [ "$BUILD" = true ] && [ -f "$ICON_SVG" ] && [ -d "$ICON_OUT" ]; then
78 echo ""
79 echo "=== Flattening app icons (no alpha) ==="
80 magick -background white -density 600 "$ICON_SVG" \
81 -resize 1024x1024 -alpha remove -alpha off "$ICON_TMP"
82 icon_pairs=(
83 "AppIcon-20x20@1x.png:20" "AppIcon-20x20@2x.png:40"
84 "AppIcon-20x20@2x-1.png:40" "AppIcon-20x20@3x.png:60"
85 "AppIcon-29x29@1x.png:29" "AppIcon-29x29@2x.png:58"
86 "AppIcon-29x29@2x-1.png:58" "AppIcon-29x29@3x.png:87"
87 "AppIcon-40x40@1x.png:40" "AppIcon-40x40@2x.png:80"
88 "AppIcon-40x40@2x-1.png:80" "AppIcon-40x40@3x.png:120"
89 "AppIcon-60x60@2x.png:120" "AppIcon-60x60@3x.png:180"
90 "AppIcon-76x76@1x.png:76" "AppIcon-76x76@2x.png:152"
91 "AppIcon-83.5x83.5@2x.png:167" "AppIcon-512@2x.png:1024"
92 )
93 for p in "${icon_pairs[@]}"; do
94 name="${p%:*}"; size="${p#*:}"
95 magick "$ICON_TMP" -resize "${size}x${size}" \
96 -background white -alpha remove -alpha off "$ICON_OUT/$name"
97 done
98 rm -f "$ICON_TMP"
99 echo "Flattened 18 icons from $ICON_SVG"
100 fi
101
102 # --- Build ---
103 if [ "$BUILD" = true ]; then
104 echo ""
105 echo "=== Building iOS release archive ==="
106 APPLE_API_KEY_PATH="$API_KEY_PATH" cargo tauri ios build --export-method app-store-connect
107 echo "Archive: $ARCHIVE_PATH"
108 fi
109
110 # --- Upload ---
111 if [ "$UPLOAD" = true ]; then
112 mkdir -p "$EXPORT_DIR"
113
114 # Pick the freshest IPA across both possible locations. Searching only one
115 # location (or stopping at `head -1`) has uploaded stale IPAs in the past.
116 IPA_PATH=$(find "$EXPORT_DIR" src-tauri/gen/apple/build \
117 -name "*.ipa" -type f 2>/dev/null \
118 -exec stat -f '%m %N' {} \; \
119 | sort -rn | head -1 | cut -d' ' -f2- || true)
120
121 # If the freshest IPA is older than the archive, the archive hasn't been
122 # exported yet — export it now.
123 if [ -z "$IPA_PATH" ] || [ "$ARCHIVE_PATH" -nt "$IPA_PATH" ]; then
124 echo ""
125 echo "=== Exporting IPA from archive ==="
126 xcodebuild -exportArchive \
127 -archivePath "$ARCHIVE_PATH" \
128 -exportOptionsPlist "$EXPORT_PLIST" \
129 -exportPath "$EXPORT_DIR" \
130 -allowProvisioningUpdates \
131 -authenticationKeyID "$API_KEY_ID" \
132 -authenticationKeyPath "$API_KEY_PATH" \
133 -authenticationKeyIssuerID "$API_ISSUER_ID"
134 IPA_PATH=$(find "$EXPORT_DIR" -name "*.ipa" -type f \
135 -exec stat -f '%m %N' {} \; \
136 | sort -rn | head -1 | cut -d' ' -f2- || true)
137 fi
138
139 if [ -z "$IPA_PATH" ]; then
140 echo "Error: No IPA found after export"
141 exit 1
142 fi
143
144 echo ""
145 echo "=== Uploading to TestFlight ==="
146 echo "IPA: $IPA_PATH"
147 # altool exits 0 even when it logs "ERROR:" / "Failed to upload" — grep
148 # the output to confirm the success banner before declaring victory.
149 set +e
150 UPLOAD_OUTPUT=$(xcrun altool --upload-app \
151 --type ios \
152 --file "$IPA_PATH" \
153 --apiKey "$API_KEY_ID" \
154 --apiIssuer "$API_ISSUER_ID" 2>&1)
155 UPLOAD_RC=$?
156 set -e
157 echo "$UPLOAD_OUTPUT"
158
159 if [ $UPLOAD_RC -ne 0 ] || ! grep -q "UPLOAD SUCCEEDED" <<<"$UPLOAD_OUTPUT"; then
160 echo ""
161 echo "Error: altool upload failed (exit=$UPLOAD_RC). See output above."
162 exit 1
163 fi
164
165 echo ""
166 echo "Upload complete! Build will appear in TestFlight within ~30 minutes."
167 echo "https://appstoreconnect.apple.com/apps"
168 fi
169
170 echo ""
171 echo "Done. GoingsOn iOS v$VERSION"
172