#!/usr/bin/env bash # # release.sh — archive Notes and upload to TestFlight / App Store Connect. # # Usage: Scripts/release.sh [ios|mac|all] (default: all) # # No third-party tooling: pure xcodebuild + the App Store Connect API key. # Credentials live in .env.release (gitignored); see .env.release.example. # # What it does, per platform: # 1. Regenerates the Xcode project with XcodeGen. # 2. Stamps CFBundleVersion from the git commit count, platform-partitioned # (iOS even, macOS odd — Scripts/update_build_number.sh, a post-build # phase wired in project.yml). # 3. xcodebuild archive -> .xcarchive # 4. xcodebuild -exportArchive with destination=upload -> App Store Connect. # set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" cd "$ROOT" PROJECT="Notes.xcodeproj" BUILD_DIR="$ROOT/build" PLATFORM="${1:-all}" # ---- credentials ------------------------------------------------------------ ENV_FILE="$ROOT/.env.release" if [ -f "$ENV_FILE" ]; then set -a; . "$ENV_FILE"; set +a fi : "${APPLE_TEAM_ID:?Set APPLE_TEAM_ID (copy .env.release.example -> .env.release)}" : "${ASC_KEY_ID:?Set ASC_KEY_ID in .env.release}" : "${ASC_ISSUER_ID:?Set ASC_ISSUER_ID in .env.release}" : "${ASC_KEY_PATH:?Set ASC_KEY_PATH in .env.release}" [ -f "$ASC_KEY_PATH" ] || { echo "❌ API key not found at: $ASC_KEY_PATH"; exit 1; } AUTH=( -authenticationKeyPath "$ASC_KEY_PATH" -authenticationKeyID "$ASC_KEY_ID" -authenticationKeyIssuerID "$ASC_ISSUER_ID" -allowProvisioningUpdates ) # ---- regenerate project ----------------------------------------------------- if command -v xcodegen >/dev/null 2>&1; then echo "🧩 Generating $PROJECT ..." xcodegen generate elif [ ! -d "$PROJECT" ]; then echo "❌ $PROJECT missing and xcodegen not installed."; exit 1 fi # ---- versioning ------------------------------------------------------------- # The Info.plists hold the literal "$(MARKETING_VERSION)" (resolved at build # time), so read the real value from project.yml. The build number is stamped # per-platform by Scripts/update_build_number.sh during the archive (even for # iOS, odd for macOS — see that script); shown here for reference. BUILD_NUMBER="$(git rev-list HEAD --count)" MARKETING_VERSION="$(grep -m1 'MARKETING_VERSION:' project.yml | sed -E 's/.*"([^"]+)".*/\1/')" echo "📦 Version $MARKETING_VERSION — iOS build $((BUILD_NUMBER * 2)), macOS build $((BUILD_NUMBER * 2 + 1))" mkdir -p "$BUILD_DIR" # ---- per-platform archive + upload ------------------------------------------ # args: archive_and_upload() { local scheme="$1" destination="$2" export_plist="$3" local archive_path="$BUILD_DIR/$scheme.xcarchive" local export_path="$BUILD_DIR/$scheme-export" echo "" echo "──────── $scheme ────────" rm -rf "$archive_path" "$export_path" echo "🛠 Archiving $scheme ..." xcodebuild archive \ -project "$PROJECT" \ -scheme "$scheme" \ -configuration Release \ -destination "$destination" \ -archivePath "$archive_path" \ CODE_SIGN_STYLE=Automatic \ DEVELOPMENT_TEAM="$APPLE_TEAM_ID" \ "${AUTH[@]}" echo "🚀 Exporting + uploading $scheme to App Store Connect ..." xcodebuild -exportArchive \ -archivePath "$archive_path" \ -exportPath "$export_path" \ -exportOptionsPlist "$export_plist" \ "${AUTH[@]}" echo "✅ $scheme uploaded." } do_ios() { archive_and_upload "Notes" "generic/platform=iOS" "$SCRIPT_DIR/ExportOptions-iOS.plist" } do_mac() { archive_and_upload "NotesMac" "generic/platform=macOS" "$SCRIPT_DIR/ExportOptions-macOS.plist" } case "$PLATFORM" in ios) do_ios ;; mac|macos) do_mac ;; all) do_ios; do_mac ;; *) echo "Usage: Scripts/release.sh [ios|mac|all]"; exit 1 ;; esac echo "" echo "🎉 Done. Builds appear in App Store Connect > TestFlight after processing (~5–15 min)."