| 1 | #!/usr/bin/env bash |
| 2 | # build-ios.sh |
| 3 | # |
| 4 | # Full App Store pipeline for Siti AI (iOS): |
| 5 | # 1. Clean — rm -rf src-tauri/gen/apple |
| 6 | # 2. Init — cargo tauri ios init (local tauri CLI, prod iOS config) |
| 7 | # 3. Icons — pnpm gim |
| 8 | # 4. Override — copy gen-override/apple into gen/apple |
| 9 | # 5. Build — cargo tauri ios build (app-store-connect, datetime CFBundleVersion) |
| 10 | # 6. Fixplist — rewrite beta toolchain stamps → release values, re-sign |
| 11 | # 7. Upload — xcrun altool (App Store Connect submission) |
| 12 | # |
| 13 | # Every configurable value has a hardcoded default that can be overridden by |
| 14 | # setting the corresponding environment variable before invoking the script. |
| 15 | # |
| 16 | # Usage: |
| 17 | # ./scripts/build-ios.sh # full pipeline |
| 18 | # ./scripts/build-ios.sh clean # rm gen/apple only |
| 19 | # ./scripts/build-ios.sh init # tauri ios init only |
| 20 | # ./scripts/build-ios.sh icons # pnpm gim only |
| 21 | # ./scripts/build-ios.sh override # copy gen-override/apple only |
| 22 | # ./scripts/build-ios.sh build # build only (assumes init already done) |
| 23 | # ./scripts/build-ios.sh fixplist # patch beta toolchain stamps + re-sign |
| 24 | # ./scripts/build-ios.sh upload # upload only (assumes .ipa already built) |
| 25 | # |
| 26 | # make asios # full pipeline via Makefile |
| 27 | # make asios-clean |
| 28 | # make asios-init |
| 29 | # make asios-icons |
| 30 | # make asios-override |
| 31 | # make asios-build |
| 32 | # make asios-fixplist |
| 33 | # make asios-upload |
| 34 | |
| 35 | set -euo pipefail |
| 36 | |
| 37 | # ── Paths ───────────────────────────────────────────────────────────────────── |
| 38 | |
| 39 | REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" |
| 40 | IOS_CONFIG="${REPO_ROOT}/src-tauri/tauri.prod-ios.conf.json" |
| 41 | GEN_APPLE="${REPO_ROOT}/src-tauri/gen/apple" |
| 42 | GEN_OVERRIDE="${REPO_ROOT}/src-tauri/gen-override/apple" |
| 43 | |
| 44 | # Auto-load the untracked .env.appstore (signing identities + ASC creds) so |
| 45 | # `make asios` works without the caller exporting them first. See |
| 46 | # .env.appstore.example for the expected keys. |
| 47 | if [[ -f "${REPO_ROOT}/.env.appstore" ]]; then |
| 48 | set -a |
| 49 | # shellcheck disable=SC1091 |
| 50 | . "${REPO_ROOT}/.env.appstore" |
| 51 | set +a |
| 52 | fi |
| 53 | |
| 54 | # Xcode toolchain. App Store Connect rejects binaries built with a beta Xcode or |
| 55 | # beta SDK. `cargo tauri ios build` resolves the toolchain from the system |
| 56 | # xcode-select (it ignores DEVELOPER_DIR), so the fixplist step below rewrites |
| 57 | # the beta stamps instead — but a pinned release Xcode is still needed to derive |
| 58 | # the release values to stamp in. |
| 59 | # Override: DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer ./scripts/build-ios.sh |
| 60 | pick_release_xcode() { |
| 61 | local active; active="$(xcode-select -p 2>/dev/null)" |
| 62 | if [[ -n "$active" && "$active" != *[Bb]eta* ]]; then |
| 63 | printf '%s\n' "$active"; return |
| 64 | fi |
| 65 | local app |
| 66 | for app in /Applications/Xcode*.app; do |
| 67 | case "$app" in *[Bb]eta*) continue ;; esac |
| 68 | [[ -d "$app/Contents/Developer" ]] && { printf '%s\n' "$app/Contents/Developer"; return; } |
| 69 | done |
| 70 | } |
| 71 | DEVELOPER_DIR="${DEVELOPER_DIR:-$(pick_release_xcode)}" |
| 72 | export DEVELOPER_DIR |
| 73 | |
| 74 | # ── Overridable config ───────────────────────────────────────────────────────── |
| 75 | # |
| 76 | # Path to a local Tauri source checkout. The ios init command is run via |
| 77 | # `cargo run` against this manifest so the CLI version matches the workspace. |
| 78 | # Override: TAURI_CLI_MANIFEST=/path/to/tauri/Cargo.toml ./scripts/build-ios.sh |
| 79 | TAURI_CLI_MANIFEST="${TAURI_CLI_MANIFEST:-${HOME}/Repositories/tauri/Cargo.toml}" |
| 80 | |
| 81 | # App Store Connect API credentials. |
| 82 | # ASC_API_KEY — Key ID shown in App Store Connect → Users & Access → Keys. |
| 83 | # ASC_ISSUER_ID — Issuer UUID on the same page. |
| 84 | # The private key (.p8) must be present at: |
| 85 | # ~/.appstoreconnect/private_keys/AuthKey_<ASC_API_KEY>.p8 |
| 86 | ASC_API_KEY="${ASC_API_KEY:-L84N624YQH}" |
| 87 | ASC_ISSUER_ID="${ASC_ISSUER_ID:-b4e8d369-8b7d-4538-8435-643b73237575}" |
| 88 | |
| 89 | # Beta-macOS submission workaround (non-sensitive; safe default). |
| 90 | # |
| 91 | # App Store Connect rejects binaries whose Info.plist reports a *beta* |
| 92 | # BuildMachineOSBuild (e.g. macOS 27.0 build 26A5353q → "Invalid Binary"). |
| 93 | # The fixplist step rewrites that key to a shipping macOS build and re-signs |
| 94 | # the .app so the code-signature seal stays valid. |
| 95 | # RELEASE_OS_BUILD — a shipping macOS build number. Default 25A354 = macOS 26.0. |
| 96 | RELEASE_OS_BUILD="${RELEASE_OS_BUILD:-25A354}" |
| 97 | |
| 98 | # Distribution identity used to re-sign after the fixplist patch |
| 99 | # (e.g. "Apple Distribution: <Your Org> (<TEAM_ID>)"). Required by fixplist only. |
| 100 | # SIGN_IDENTITY — env-only, not committed (set it in .env.appstore). |
| 101 | |
| 102 | # App name — must match the productName in tauri.prod-ios.conf.json. |
| 103 | APP_NAME="${APP_NAME:-Siti AI}" |
| 104 | |
| 105 | # Derived IPA path produced by `cargo tauri ios build`. |
| 106 | IPA_PATH="${REPO_ROOT}/src-tauri/gen/apple/build/arm64/${APP_NAME}.ipa" |
| 107 | |
| 108 | # ── Helpers ─────────────────────────────────────────────────────────────────── |
| 109 | |
| 110 | step() { echo ""; echo "── $* ──────────────────────────────────────────────"; } |
| 111 | ok() { echo "✅ $*"; } |
| 112 | fail() { printf "❌ %b\n" "$*" >&2; exit 1; } |
| 113 | |
| 114 | # Release toolchain stamps used by the fixplist step to rewrite a beta-built |
| 115 | # binary's Info.plist so App Store Connect reads it as a release-Xcode build. |
| 116 | # Auto-derived from the pinned release Xcode (DEVELOPER_DIR, from pick_release_xcode); |
| 117 | # every value is overridable via env. |
| 118 | # RELEASE_DTXCODE e.g. 2660 (Xcode 26.6 → "2660") |
| 119 | # RELEASE_XCODE_BUILD e.g. 17F113 (Xcode product build) |
| 120 | # RELEASE_SDK_VERSION e.g. 26.5 |
| 121 | # RELEASE_SDK_NAME e.g. iphoneos26.5 |
| 122 | # RELEASE_SDK_BUILD e.g. 23F81a (iPhoneOS SDK product build) |
| 123 | derive_release_toolchain() { |
| 124 | local dd="${DEVELOPER_DIR}" |
| 125 | if [[ -z "$dd" || ! -d "$dd" || "$dd" == *[Bb]eta* ]]; then |
| 126 | [[ -n "${RELEASE_DTXCODE:-}${RELEASE_XCODE_BUILD:-}" ]] \ |
| 127 | || fail "No release Xcode found to derive release stamps.\n Install a release Xcode, or set RELEASE_DTXCODE / RELEASE_XCODE_BUILD / RELEASE_SDK_* by hand." |
| 128 | fi |
| 129 | local xv sv xver xbuild sdkver sdkbuild d |
| 130 | xv="$(DEVELOPER_DIR="$dd" xcodebuild -version 2>/dev/null)" |
| 131 | sv="$(DEVELOPER_DIR="$dd" xcodebuild -version -sdk iphoneos 2>/dev/null)" |
| 132 | xver="$(printf '%s\n' "$xv" | awk '/^Xcode/{print $2}')" |
| 133 | xbuild="$(printf '%s\n' "$xv" | awk '/Build version/{print $3}')" |
| 134 | sdkver="$(printf '%s\n' "$sv" | awk -F': ' '/SDKVersion/{print $2; exit}')" |
| 135 | sdkbuild="$(printf '%s\n' "$sv" | awk -F': ' '/ProductBuildVersion/{print $2; exit}')" |
| 136 | d="${xver//./}"; while [[ -n "$d" && ${#d} -lt 4 ]]; do d="${d}0"; done |
| 137 | REL_DTXCODE="${RELEASE_DTXCODE:-$d}" |
| 138 | REL_XCODE_BUILD="${RELEASE_XCODE_BUILD:-$xbuild}" |
| 139 | REL_SDK_VERSION="${RELEASE_SDK_VERSION:-$sdkver}" |
| 140 | REL_SDK_NAME="${RELEASE_SDK_NAME:-iphoneos${sdkver}}" |
| 141 | REL_SDK_BUILD="${RELEASE_SDK_BUILD:-$sdkbuild}" |
| 142 | [[ -n "$REL_DTXCODE" && -n "$REL_XCODE_BUILD" && -n "$REL_SDK_BUILD" ]] \ |
| 143 | || fail "Could not determine release toolchain stamps from ${dd:-<unset>}." |
| 144 | } |
| 145 | |
| 146 | # True when an Apple build number is a pre-release (beta) seed. Apple stamps |
| 147 | # seeds with a trailing lowercase letter (e.g. 26A5353q); shipping builds never |
| 148 | # carry one (e.g. 25A354). Used to decide whether the Info.plist actually needs |
| 149 | # patching, so the workaround is a no-op on a release macOS. |
| 150 | is_beta_build() { |
| 151 | [[ "${1:-}" =~ [a-z]$ ]] |
| 152 | } |
| 153 | |
| 154 | # ── Steps ───────────────────────────────────────────────────────────────────── |
| 155 | |
| 156 | do_clean() { |
| 157 | step "Clean" |
| 158 | rm -rf "${GEN_APPLE}" |
| 159 | ok "Removed ${GEN_APPLE}" |
| 160 | } |
| 161 | |
| 162 | do_init() { |
| 163 | step "Init" |
| 164 | |
| 165 | [[ -f "$IOS_CONFIG" ]] || fail "Config not found: $IOS_CONFIG" |
| 166 | [[ -f "$TAURI_CLI_MANIFEST" ]] || fail "Tauri CLI manifest not found: $TAURI_CLI_MANIFEST\n Set TAURI_CLI_MANIFEST to the path of your local tauri/Cargo.toml" |
| 167 | |
| 168 | echo "📱 config → ${IOS_CONFIG}" |
| 169 | echo "🦀 cli → ${TAURI_CLI_MANIFEST}" |
| 170 | echo "" |
| 171 | |
| 172 | cargo run --release \ |
| 173 | --manifest-path "${TAURI_CLI_MANIFEST}" \ |
| 174 | -p tauri-cli \ |
| 175 | -- tauri ios init \ |
| 176 | -c "${IOS_CONFIG}" |
| 177 | |
| 178 | ok "Xcode project generated → ${GEN_APPLE}" |
| 179 | } |
| 180 | |
| 181 | do_icons() { |
| 182 | step "Icons" |
| 183 | pnpm gim |
| 184 | ok "Icons generated" |
| 185 | } |
| 186 | |
| 187 | do_override() { |
| 188 | step "Override" |
| 189 | |
| 190 | if [[ -d "${GEN_OVERRIDE}" ]]; then |
| 191 | cp -r "${GEN_OVERRIDE}/." "${GEN_APPLE}/" |
| 192 | ok "Copied ${GEN_OVERRIDE} → ${GEN_APPLE}" |
| 193 | else |
| 194 | echo "ℹ️ No overrides found at ${GEN_OVERRIDE}, skipping." |
| 195 | fi |
| 196 | } |
| 197 | |
| 198 | do_build() { |
| 199 | step "Build" |
| 200 | |
| 201 | [[ -f "$IOS_CONFIG" ]] || fail "Config not found: $IOS_CONFIG" |
| 202 | [[ -d "$GEN_APPLE" ]] || fail "Xcode project not found at ${GEN_APPLE}\n Run 'init' step first." |
| 203 | |
| 204 | # CFBundleVersion: YYYYMMDD.HHMM (UTC) |
| 205 | # Two period-separated integers → valid for Apple |
| 206 | # Monotonically increasing → satisfies App Store requirement |
| 207 | BUNDLE_VERSION="$(date -u +"%Y%m%d.%H%M")" |
| 208 | BUNDLE_VERSION_JSON="{\"bundle\":{\"iOS\":{\"bundleVersion\":\"${BUNDLE_VERSION}\"}}}" |
| 209 | |
| 210 | echo "🏷 bundleVersion → ${BUNDLE_VERSION}" |
| 211 | echo "📦 app config → ${IOS_CONFIG}" |
| 212 | echo "" |
| 213 | |
| 214 | # `cargo tauri ios build` resolves the toolchain from the system xcode-select |
| 215 | # (it ignores DEVELOPER_DIR), so on a beta-Xcode machine it builds with the |
| 216 | # beta Xcode. That is fine: the fixplist step rewrites the beta toolchain |
| 217 | # stamps (DTXcode, DTSDKBuild, …) to release values and re-signs, so the |
| 218 | # uploaded binary reads as a release-Xcode build. App Store Connect accepts it. |
| 219 | cargo tauri ios build \ |
| 220 | --export-method app-store-connect \ |
| 221 | --config "${IOS_CONFIG}" \ |
| 222 | --config "${BUNDLE_VERSION_JSON}" |
| 223 | |
| 224 | [[ -f "$IPA_PATH" ]] || fail "Build succeeded but .ipa not found at: $IPA_PATH" |
| 225 | ok "Build complete → ${IPA_PATH}" |
| 226 | } |
| 227 | |
| 228 | do_fixplist() { |
| 229 | step "Fixplist (beta toolchain → release)" |
| 230 | |
| 231 | [[ -f "$IPA_PATH" ]] || fail ".ipa not found: ${IPA_PATH}\n Run 'build' step first." |
| 232 | |
| 233 | command -v /usr/libexec/PlistBuddy >/dev/null || fail "PlistBuddy not found" |
| 234 | : "${SIGN_IDENTITY:?set SIGN_IDENTITY (distribution identity for the re-sign)}" |
| 235 | |
| 236 | WORK="$(mktemp -d)" |
| 237 | trap 'rm -rf "$WORK"' RETURN |
| 238 | |
| 239 | unzip -q "$IPA_PATH" -d "$WORK" |
| 240 | |
| 241 | local app_dir plist ent os_cur xcb_cur sdk_cur |
| 242 | app_dir="$(find "$WORK/Payload" -maxdepth 1 -name '*.app' -print -quit)" |
| 243 | [[ -n "$app_dir" && -d "$app_dir" ]] || fail "No .app found inside ${IPA_PATH}" |
| 244 | plist="$app_dir/Info.plist" |
| 245 | [[ -f "$plist" ]] || fail "Info.plist not found in $app_dir" |
| 246 | |
| 247 | pl() { /usr/libexec/PlistBuddy -c "Print :$1" "$plist" 2>/dev/null || echo ""; } |
| 248 | setpl() { /usr/libexec/PlistBuddy -c "Set :$1 $2" "$plist" 2>/dev/null \ |
| 249 | || /usr/libexec/PlistBuddy -c "Add :$1 string $2" "$plist"; } |
| 250 | |
| 251 | os_cur="$(pl BuildMachineOSBuild)" # set by the build *machine's* OS (beta macOS) |
| 252 | xcb_cur="$(pl DTXcodeBuild)" # set by the *Xcode* used (beta Xcode) |
| 253 | sdk_cur="$(pl DTSDKBuild)" # set by the *SDK* used (beta SDK) |
| 254 | |
| 255 | # Patch when ANY toolchain stamp is a pre-release seed (trailing lowercase |
| 256 | # letter): a beta macOS (BuildMachineOSBuild), a beta Xcode (DTXcodeBuild) or a |
| 257 | # beta SDK (DTSDKBuild) all make App Store Connect reject the upload. Override |
| 258 | # with FORCE_FIXPLIST=1 to patch regardless, or SKIP_FIXPLIST=1 to never patch. |
| 259 | if [[ -n "${SKIP_FIXPLIST:-}" ]]; then |
| 260 | ok "SKIP_FIXPLIST set — leaving ${IPA_PATH} untouched (DTXcodeBuild=${xcb_cur:-<unset>})." |
| 261 | return 0 |
| 262 | fi |
| 263 | if [[ -z "${FORCE_FIXPLIST:-}" ]] \ |
| 264 | && ! is_beta_build "$os_cur" && ! is_beta_build "$xcb_cur" && ! is_beta_build "$sdk_cur"; then |
| 265 | ok "Toolchain stamps are already release seeds — no patch needed, leaving .ipa untouched." |
| 266 | return 0 |
| 267 | fi |
| 268 | |
| 269 | derive_release_toolchain |
| 270 | echo "📦 ipa → ${IPA_PATH}" |
| 271 | echo "🔏 identity → ${SIGN_IDENTITY}" |
| 272 | echo "🛠 xcode → ${REL_DTXCODE} / ${REL_XCODE_BUILD} sdk → ${REL_SDK_NAME} / ${REL_SDK_BUILD}" |
| 273 | echo "" |
| 274 | |
| 275 | # Capture the entitlements the binary was actually signed with, so the |
| 276 | # re-sign reproduces them exactly (matches the embedded provisioning profile). |
| 277 | ent="$WORK/entitlements.plist" |
| 278 | if ! codesign -d --entitlements :"$ent" "$app_dir" 2>/dev/null || [[ ! -s "$ent" ]]; then |
| 279 | echo "ℹ️ Falling back to gen-override entitlements" |
| 280 | cp "${GEN_OVERRIDE}/sitiai_iOS/sitiai_iOS.entitlements" "$ent" |
| 281 | fi |
| 282 | |
| 283 | # Rewrite every beta toolchain stamp to the release Xcode's values. |
| 284 | echo "🔁 BuildMachineOSBuild: ${os_cur:-<unset>} → ${RELEASE_OS_BUILD}" |
| 285 | echo "🔁 DTXcode/DTXcodeBuild: ${xcb_cur:-<unset>} → ${REL_DTXCODE}/${REL_XCODE_BUILD}" |
| 286 | echo "🔁 DTSDK*: ${sdk_cur:-<unset>} → ${REL_SDK_NAME}/${REL_SDK_BUILD}" |
| 287 | setpl BuildMachineOSBuild "${RELEASE_OS_BUILD}" |
| 288 | setpl DTXcode "${REL_DTXCODE}" |
| 289 | setpl DTXcodeBuild "${REL_XCODE_BUILD}" |
| 290 | setpl DTSDKName "${REL_SDK_NAME}" |
| 291 | setpl DTSDKBuild "${REL_SDK_BUILD}" |
| 292 | setpl DTPlatformBuild "${REL_SDK_BUILD}" |
| 293 | setpl DTPlatformVersion "${REL_SDK_VERSION}" |
| 294 | |
| 295 | # Rewrite the Mach-O LC_BUILD_VERSION load command on every binary in the |
| 296 | # bundle. This is the critical step: App Store Connect's *review-stage* |
| 297 | # validation reads the `sdk` field baked into the executable's load command — |
| 298 | # NOT the Info.plist DTSDK* keys above — so a beta-SDK build (e.g. sdk 27.0) |
| 299 | # is accepted at upload, processes fine, then flags "Invalid Binary" only |
| 300 | # after it is submitted for review. Patching Info.plist alone does not fix it; |
| 301 | # vtool rewrites the binary itself. Each slice's `minos` is preserved; only |
| 302 | # the beta `sdk` is lowered to the release SDK (REL_SDK_VERSION). The re-sign |
| 303 | # below re-seals the modified Mach-O. |
| 304 | local macho |
| 305 | while IFS= read -r macho; do |
| 306 | [[ -z "$macho" ]] && continue |
| 307 | local minos_cur |
| 308 | minos_cur="$(xcrun vtool -show-build "$macho" 2>/dev/null | awk '/minos/{print $2; exit}')" |
| 309 | minos_cur="${minos_cur:-16.0}" |
| 310 | echo "🔁 Mach-O sdk → ${REL_SDK_VERSION} (minos ${minos_cur}): ${macho#$app_dir/}" |
| 311 | xcrun vtool -set-build-version ios "${minos_cur}" "${REL_SDK_VERSION}" \ |
| 312 | -replace -output "${macho}.vt" "$macho" >/dev/null 2>&1 \ |
| 313 | && mv "${macho}.vt" "$macho" \ |
| 314 | || fail "vtool failed to patch LC_BUILD_VERSION on ${macho#$app_dir/}" |
| 315 | done < <(find "$app_dir" -type f -exec sh -c 'file "$1" | grep -q Mach-O' _ {} \; -print) |
| 316 | |
| 317 | # Re-sign nested code first (deepest path → shallowest), then the .app itself. |
| 318 | # Editing Info.plist / the Mach-O load command invalidates the original |
| 319 | # signature; without this the upload is rejected for a broken seal. |
| 320 | local nested |
| 321 | while IFS= read -r nested; do |
| 322 | [[ -z "$nested" ]] && continue |
| 323 | echo "✍️ sign nested → ${nested#$app_dir/}" |
| 324 | codesign --force --timestamp=none --sign "$SIGN_IDENTITY" "$nested" |
| 325 | done < <(find "$app_dir" \( -name '*.framework' -o -name '*.dylib' -o -name '*.appex' \) -print | awk '{ print length, $0 }' | sort -rn | cut -d' ' -f2-) |
| 326 | |
| 327 | codesign --force --timestamp=none \ |
| 328 | --sign "$SIGN_IDENTITY" \ |
| 329 | --entitlements "$ent" \ |
| 330 | "$app_dir" |
| 331 | |
| 332 | codesign --verify --deep --strict "$app_dir" \ |
| 333 | || fail "Re-signed app failed signature verification" |
| 334 | echo "🔁 now: DTXcodeBuild=$(pl DTXcodeBuild) DTSDKBuild=$(pl DTSDKBuild) BuildMachineOSBuild=$(pl BuildMachineOSBuild)" |
| 335 | |
| 336 | # Repack the .ipa in place (zip from the extraction root so Payload/ is at top). |
| 337 | rm -f "$IPA_PATH" |
| 338 | ( cd "$WORK" && zip -qry "$IPA_PATH" Payload ) |
| 339 | [[ -f "$IPA_PATH" ]] || fail "Repack failed: $IPA_PATH" |
| 340 | |
| 341 | ok "Patched + re-signed → ${IPA_PATH}" |
| 342 | } |
| 343 | |
| 344 | do_upload() { |
| 345 | step "Upload" |
| 346 | |
| 347 | [[ -f "$IPA_PATH" ]] || fail ".ipa not found: ${IPA_PATH}\n Run 'build' step first." |
| 348 | |
| 349 | echo "📤 file → ${IPA_PATH}" |
| 350 | echo "🔑 apiKey → ${ASC_API_KEY}" |
| 351 | echo "🏢 issuer → ${ASC_ISSUER_ID}" |
| 352 | echo "" |
| 353 | |
| 354 | xcrun altool \ |
| 355 | --upload-app \ |
| 356 | --type ios \ |
| 357 | --file "${IPA_PATH}" \ |
| 358 | --apiKey "${ASC_API_KEY}" \ |
| 359 | --apiIssuer "${ASC_ISSUER_ID}" |
| 360 | |
| 361 | ok "Uploaded to App Store Connect" |
| 362 | } |
| 363 | |
| 364 | # ── Entry point ─────────────────────────────────────────────────────────────── |
| 365 | |
| 366 | STEP="${1:-all}" |
| 367 | |
| 368 | case "$STEP" in |
| 369 | clean) do_clean ;; |
| 370 | init) do_init ;; |
| 371 | icons) do_icons ;; |
| 372 | override) do_override ;; |
| 373 | build) do_build ;; |
| 374 | fixplist) do_fixplist ;; |
| 375 | upload) do_upload ;; |
| 376 | all) |
| 377 | do_clean |
| 378 | do_init |
| 379 | do_icons |
| 380 | do_override |
| 381 | do_build |
| 382 | do_fixplist |
| 383 | do_upload |
| 384 | echo "" |
| 385 | echo "🎉 Pipeline complete: built and uploaded ${APP_NAME} to App Store Connect." |
| 386 | ;; |
| 387 | *) |
| 388 | echo "Usage: $0 [clean|init|icons|override|build|fixplist|upload|all]" >&2 |
| 389 | exit 1 |
| 390 | ;; |
| 391 | esac |