#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' # Ensure UTF-8 locale for macOS terminals export LANG=en_US.UTF-8 export LC_ALL=en_US.UTF-8 # Resolve project directory (script location) PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)" echo "Project dir: $PROJECT_DIR" # To save credentials in macOS Keychain for non-interactive uploads, run: # security add-generic-password -s "TransporterCredentials" -w "you@example.com:app-specific-password" -a transporter -U # The script will read that item and expect the stored password string in the form: "APPLE_ID:APPLE_PASSWORD" force_delete() { local target="$1" local max_retries=3 local retry_delay=1 if [ ! -e "$PROJECT_DIR/$target" ]; then return 0 fi for i in $(seq 1 $max_retries); do echo "Attempting to delete $target (try $i of $max_retries)" rm -rf "$PROJECT_DIR/$target" 2>/dev/null || true if [ ! -e "$PROJECT_DIR/$target" ]; then echo "Successfully deleted $target" return 0 fi if [ "$i" -lt "$max_retries" ]; then sleep $retry_delay retry_delay=$((retry_delay * 2)) fi done echo "Warning: Could not completely delete $target, some files may be in use" return 1 } cd "$PROJECT_DIR" echo "Running flutter clean..." if ! flutter clean; then echo "flutter clean failed" exit 1 fi echo "Removing residual files" force_delete build || true force_delete .dart_tool || true force_delete pubspec.lock || true echo "Running flutter pub get..." if ! flutter pub get; then echo "flutter pub get failed" exit 1 fi echo "Running build_runner (if present)..." if ! flutter packages pub run build_runner build --delete-conflicting-outputs; then echo "build_runner failed or not present — continuing" fi echo "Building iOS IPA (verbose)..." if ! flutter build ipa --release --verbose; then echo "IPA build failed" exit 1 fi # Try to find the generated .ipa (searching under build/) IPA_PATH="$(find "$PROJECT_DIR/build" -type f -name '*.ipa' -print -quit || true)" if [ -z "$IPA_PATH" ]; then echo "No .ipa found in build output" echo "Build finished but no IPA to upload. Exiting." exit 0 fi echo "Found IPA: $IPA_PATH" # Upload via Transporter (if installed). Prefer environment vars APPLE_ID and APPLE_PASSWORD (app-specific password). ITMS_TRANSPORTER="/Applications/Transporter.app/Contents/itms/bin/iTMSTransporter" if [ -x "$ITMS_TRANSPORTER" ]; then echo "Using Transporter at $ITMS_TRANSPORTER" # Read credentials from env, then try macOS Keychain, otherwise prompt if [ -z "${APPLE_ID:-}" ] || [ -z "${APPLE_PASSWORD:-}" ]; then # Try to read combined credentials from Keychain (service: TransporterCredentials) if command -v security >/dev/null 2>&1; then KC_PAIR="$(security find-generic-password -s "TransporterCredentials" -w 2>/dev/null || true)" if [ -n "$KC_PAIR" ]; then # Expect stored value in form: appleid:app-specific-password APPLE_ID="${KC_PAIR%%:*}" APPLE_PASSWORD="${KC_PAIR#*:}" if [ -n "${APPLE_ID}" ] && [ -n "${APPLE_PASSWORD}" ]; then echo "Credentials loaded from Keychain (service: TransporterCredentials)." else # clear if parsing failed APPLE_ID="" APPLE_PASSWORD="" fi fi fi if [ -z "${APPLE_ID:-}" ] || [ -z "${APPLE_PASSWORD:-}" ]; then echo "Provide App Store Connect credentials for upload. You can set APPLE_ID and APPLE_PASSWORD (app-specific password) in the environment or save a keychain item named 'TransporterCredentials' to avoid prompts." read -p "Apple ID (email): " APPLE_ID read -s -p "App-specific password (will not echo): " APPLE_PASSWORD echo fi fi echo "Uploading $IPA_PATH to App Store Connect (this may take a while)..." # Try transporter with a small retry loop UPLOAD_EXIT=0 MAX_UPLOAD_TRIES=2 for try in $(seq 1 $MAX_UPLOAD_TRIES); do echo "Transporter upload attempt $try of $MAX_UPLOAD_TRIES..." if "$ITMS_TRANSPORTER" -m upload -u "$APPLE_ID" -p "$APPLE_PASSWORD" -f "$IPA_PATH"; then echo "Upload succeeded" UPLOAD_EXIT=0 break else UPLOAD_EXIT=$? echo "Transporter upload attempt $try failed (exit $UPLOAD_EXIT)" # small delay before retry sleep $((try * 2)) fi done if [ $UPLOAD_EXIT -ne 0 ]; then echo "Transporter upload failed after $MAX_UPLOAD_TRIES attempts (exit $UPLOAD_EXIT)." echo "Opening Transporter app so you can upload the IPA manually..." # Try to open Transporter GUI with the IPA as argument (may import the file) if command -v open >/dev/null 2>&1; then if open -a Transporter "$IPA_PATH" 2>/dev/null; then echo "Transporter app opened. Please complete the upload there." else echo "Failed to open Transporter with the IPA. Opening Transporter app without file..." open -a Transporter || true echo "Transporter opened. Please drag the IPA ($IPA_PATH) into the app to upload." fi else echo "Cannot open Transporter GUI: 'open' command not found. Please upload IPA manually: $IPA_PATH" fi # Do not exit with error here so the script can finish and user can manually complete upload fi else echo "Transporter not found at $ITMS_TRANSPORTER. Please install Transporter app from the Mac App Store or upload the IPA manually." echo "IPA path: $IPA_PATH" fi echo "All steps completed successfully!" echo "IPA: $IPA_PATH" exit 0