160 lines
5.4 KiB
Bash
Executable File
160 lines
5.4 KiB
Bash
Executable File
#!/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
|