#!/bin/bash # Build script for daily-notification-test Capacitor app # Supports both Android and iOS with emulator/simulator deployment # # Requirements: # - Node.js 20.19.0+ or 22.12.0+ # - npm # - Plugin must be built (script will auto-build if needed) # - For Android: Java JDK 22.12+, Android SDK (adb) # - For iOS: Xcode, CocoaPods (pod) # # Usage: # ./scripts/build.sh # Build both platforms # ./scripts/build.sh --android # Build Android only # ./scripts/build.sh --ios # Build iOS only # ./scripts/build.sh --run # Build and run both # ./scripts/build.sh --help # Show help set -e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Script directory SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" # Logging functions log_info() { echo -e "${GREEN}[INFO]${NC} $1" } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } log_step() { echo -e "${BLUE}[STEP]${NC} $1" } # Validation functions check_command() { if ! command -v $1 &> /dev/null; then log_error "$1 is not installed. Please install it first." exit 1 fi } # Get pod command (handles rbenv) get_pod_command() { if command -v pod &> /dev/null; then echo "pod" elif [ -f "$HOME/.rbenv/shims/pod" ]; then echo "$HOME/.rbenv/shims/pod" else log_error "CocoaPods (pod) not found. Please install CocoaPods first." exit 1 fi } # Check requirements check_requirements() { log_step "Checking build requirements..." local missing_requirements=false # Check Node.js if ! command -v node &> /dev/null; then log_error "Node.js is not installed. Please install Node.js 20.19.0+ or 22.12.0+" missing_requirements=true else log_info "✅ Node.js: $(node --version)" fi # Check npm if ! command -v npm &> /dev/null; then log_error "npm is not installed" missing_requirements=true else log_info "✅ npm: $(npm --version)" fi # Check plugin is built PLUGIN_ROOT="$(cd "$PROJECT_DIR/../.." && pwd)" if [ ! -d "$PLUGIN_ROOT/dist" ]; then log_warn "Plugin not built. Building plugin now..." cd "$PLUGIN_ROOT" if npm run build; then log_info "✅ Plugin built successfully" else log_error "Failed to build plugin. Please run 'npm run build' in the plugin root directory." missing_requirements=true fi cd "$PROJECT_DIR" else log_info "✅ Plugin built (dist/ exists)" fi # Check Android requirements if building Android if [ "$BUILD_ALL" = true ] || [ "$BUILD_ANDROID" = true ]; then if ! command -v adb &> /dev/null; then log_warn "Android SDK not found (adb not in PATH). Android build will be skipped." else log_info "✅ Android SDK: $(adb version | head -1)" fi if ! command -v java &> /dev/null; then log_warn "Java not found. Android build may fail." else log_info "✅ Java: $(java -version 2>&1 | head -1)" fi fi # Check iOS requirements if building iOS if [ "$BUILD_ALL" = true ] || [ "$BUILD_IOS" = true ]; then if ! command -v xcodebuild &> /dev/null; then log_warn "Xcode not found (xcodebuild not in PATH). iOS build will be skipped." else log_info "✅ Xcode: $(xcodebuild -version | head -1)" fi POD_CMD=$(get_pod_command 2>/dev/null || echo "") if [ -z "$POD_CMD" ]; then log_warn "CocoaPods not found. iOS build will be skipped." else log_info "✅ CocoaPods: $($POD_CMD --version 2>/dev/null || echo 'found')" fi fi if [ "$missing_requirements" = true ]; then log_error "Missing required dependencies. Please install them and try again." exit 1 fi log_info "All requirements satisfied" } # Parse arguments BUILD_ANDROID=false BUILD_IOS=false BUILD_ALL=true RUN_ANDROID=false RUN_IOS=false RUN_ALL=false while [[ $# -gt 0 ]]; do case $1 in --android) BUILD_ANDROID=true BUILD_ALL=false shift ;; --ios) BUILD_IOS=true BUILD_ALL=false shift ;; --run-android) RUN_ANDROID=true BUILD_ANDROID=true BUILD_ALL=false shift ;; --run-ios) RUN_IOS=true BUILD_IOS=true BUILD_ALL=false shift ;; --run) RUN_ALL=true BUILD_ALL=true shift ;; --help|-h) echo "Usage: $0 [OPTIONS]" echo "" echo "Options:" echo " --android Build Android only" echo " --ios Build iOS only" echo " --run-android Build and run Android on emulator" echo " --run-ios Build and run iOS on simulator" echo " --run Build and run both platforms" echo " --help, -h Show this help message" echo "" echo "Default: Build both platforms (no run)" exit 0 ;; *) log_error "Unknown option: $1" echo "Use --help for usage information" exit 1 ;; esac done # Change to project directory cd "$PROJECT_DIR" log_info "Building daily-notification-test app" log_info "Project directory: $PROJECT_DIR" # Check requirements check_requirements # Step 1: Build web assets log_step "Building web assets..." if ! npm run build; then log_error "Web build failed" exit 1 fi log_info "Web assets built successfully" # Step 2: Sync Capacitor log_step "Syncing Capacitor with native projects..." if ! npm run cap:sync; then log_error "Capacitor sync failed" exit 1 fi log_info "Capacitor sync completed" # Step 2.5: Ensure fix script ran (it should have via cap:sync, but verify for iOS) if [ "$BUILD_ALL" = true ] || [ "$BUILD_IOS" = true ]; then if [ -d "$PROJECT_DIR/ios" ]; then log_step "Verifying iOS Podfile configuration..." if node "$PROJECT_DIR/scripts/fix-capacitor-plugins.js" 2>/dev/null; then log_info "iOS Podfile verified" fi fi fi # Android build if [ "$BUILD_ALL" = true ] || [ "$BUILD_ANDROID" = true ]; then log_step "Building Android app..." # Check for Android SDK if ! command -v adb &> /dev/null; then log_warn "adb not found. Android SDK may not be installed." log_warn "Skipping Android build. Install Android SDK to build Android." else cd "$PROJECT_DIR/android" # Build APK if ./gradlew :app:assembleDebug; then log_info "Android APK built successfully" APK_PATH="$PROJECT_DIR/android/app/build/outputs/apk/debug/app-debug.apk" if [ -f "$APK_PATH" ]; then log_info "APK location: $APK_PATH" # Run on emulator if requested if [ "$RUN_ALL" = true ] || [ "$RUN_ANDROID" = true ]; then log_step "Installing and launching Android app..." # Check for running emulator if ! adb devices | grep -q "device$"; then log_warn "No Android emulator/device found" log_info "Please start an Android emulator and try again" log_info "Or use: adb devices to check connected devices" else # Install APK if adb install -r "$APK_PATH"; then log_info "APK installed successfully" # Launch app if adb shell am start -n com.timesafari.dailynotification.test/.MainActivity; then log_info "✅ Android app launched successfully!" else log_warn "Failed to launch app (may already be running)" fi else log_warn "APK installation failed (may already be installed)" fi fi fi else log_error "APK not found at expected location: $APK_PATH" fi else log_error "Android build failed" exit 1 fi cd "$PROJECT_DIR" fi fi # iOS build if [ "$BUILD_ALL" = true ] || [ "$BUILD_IOS" = true ]; then log_step "Building iOS app..." # Check for Xcode if ! command -v xcodebuild &> /dev/null; then log_warn "xcodebuild not found. Xcode may not be installed." log_warn "Skipping iOS build. Install Xcode to build iOS." else IOS_DIR="$PROJECT_DIR/ios/App" if [ ! -d "$IOS_DIR" ]; then log_warn "iOS directory not found. Adding iOS platform..." cd "$PROJECT_DIR" npx cap add ios fi cd "$IOS_DIR" # Install CocoaPods dependencies log_step "Installing CocoaPods dependencies..." POD_CMD=$(get_pod_command) # Check if Podfile exists and has correct plugin reference if [ -f "$IOS_DIR/Podfile" ]; then # Run fix script to ensure Podfile is correct log_step "Verifying Podfile configuration..." if node "$PROJECT_DIR/scripts/fix-capacitor-plugins.js" 2>/dev/null; then log_info "Podfile verified" fi fi if $POD_CMD install; then log_info "CocoaPods dependencies installed" else log_error "CocoaPods install failed" log_info "Troubleshooting:" log_info "1. Check that plugin podspec exists: ls -la $PLUGIN_ROOT/ios/DailyNotificationPlugin.podspec" log_info "2. Verify Podfile references: pod 'DailyNotificationPlugin', :path => '../../node_modules/@timesafari/daily-notification-plugin/ios'" log_info "3. Run fix script: node scripts/fix-capacitor-plugins.js" exit 1 fi # Find workspace WORKSPACE="$IOS_DIR/App.xcworkspace" if [ ! -d "$WORKSPACE" ]; then WORKSPACE="$IOS_DIR/App.xcodeproj" fi if [ ! -d "$WORKSPACE" ]; then log_error "Xcode workspace/project not found at $IOS_DIR" exit 1 fi # Get simulator log_step "Finding available iOS simulator..." # Method 1: Use xcodebuild to get available destinations (most reliable) # This gives us the exact format xcodebuild expects DESTINATION_STRING=$(xcodebuild -workspace "$WORKSPACE" -scheme App -showdestinations 2>/dev/null | \ grep "iOS Simulator" | \ grep -i "iphone" | \ grep -v "iPhone Air" | \ head -1) if [ -n "$DESTINATION_STRING" ]; then # Extract name from destination string # Format: "platform=iOS Simulator,id=...,name=iPhone 17 Pro,OS=26.0.1" SIMULATOR=$(echo "$DESTINATION_STRING" | \ sed -n 's/.*name=\([^,]*\).*/\1/p' | \ sed 's/[[:space:]]*$//') log_info "Found simulator via xcodebuild: $SIMULATOR" fi # Method 2: Fallback to simctl if xcodebuild didn't work if [ -z "$SIMULATOR" ] || [ "$SIMULATOR" = "Shutdown" ] || [ "$SIMULATOR" = "Booted" ]; then # Use simctl list in JSON format for more reliable parsing # This avoids parsing status words like "Shutdown" SIMULATOR_JSON=$(xcrun simctl list devices available --json 2>/dev/null) if [ -n "$SIMULATOR_JSON" ]; then # Extract first iPhone device name using jq if available, or grep/sed if command -v jq &> /dev/null; then SIMULATOR=$(echo "$SIMULATOR_JSON" | \ jq -r '.devices | to_entries[] | .value[] | select(.name | test("iPhone"; "i")) | .name' | \ grep -v "iPhone Air" | \ head -1) else # Fallback: parse text output more carefully # Get line with iPhone, extract name before first parenthesis SIMULATOR_LINE=$(xcrun simctl list devices available 2>/dev/null | \ grep -E "iPhone [0-9]" | \ grep -v "iPhone Air" | \ head -1) if [ -n "$SIMULATOR_LINE" ]; then # Extract device name - everything before first "(" SIMULATOR=$(echo "$SIMULATOR_LINE" | \ sed -E 's/^[[:space:]]*([^(]+).*/\1/' | \ sed 's/[[:space:]]*$//') fi fi # Validate it's not a status word if [ "$SIMULATOR" = "Shutdown" ] || [ "$SIMULATOR" = "Booted" ] || [ "$SIMULATOR" = "Creating" ] || [ -z "$SIMULATOR" ]; then SIMULATOR="" else log_info "Found simulator via simctl: $SIMULATOR" fi fi fi # Method 3: Try to find iPhone 17 Pro specifically (preferred) if [ -z "$SIMULATOR" ] || [ "$SIMULATOR" = "Shutdown" ] || [ "$SIMULATOR" = "Booted" ]; then PRO_LINE=$(xcrun simctl list devices available 2>/dev/null | \ grep -i "iPhone 17 Pro" | \ head -1) if [ -n "$PRO_LINE" ]; then PRO_SIM=$(echo "$PRO_LINE" | \ awk -F'(' '{print $1}' | \ sed 's/^[[:space:]]*//' | \ sed 's/[[:space:]]*$//') if [ -n "$PRO_SIM" ] && [ "$PRO_SIM" != "Shutdown" ] && [ "$PRO_SIM" != "Booted" ] && [ "$PRO_SIM" != "Creating" ]; then SIMULATOR="$PRO_SIM" log_info "Using preferred simulator: $SIMULATOR" fi fi fi # Final fallback to known good simulator if [ -z "$SIMULATOR" ] || [ "$SIMULATOR" = "Shutdown" ] || [ "$SIMULATOR" = "Booted" ] || [ "$SIMULATOR" = "Creating" ]; then # Try common simulator names that are likely to exist for DEFAULT_SIM in "iPhone 17 Pro" "iPhone 17" "iPhone 16" "iPhone 15 Pro" "iPhone 15"; do if xcrun simctl list devices available 2>/dev/null | grep -q "$DEFAULT_SIM"; then SIMULATOR="$DEFAULT_SIM" log_info "Using fallback simulator: $SIMULATOR" break fi done # If still empty, use iPhone 17 Pro as final default if [ -z "$SIMULATOR" ] || [ "$SIMULATOR" = "Shutdown" ] || [ "$SIMULATOR" = "Booted" ]; then log_warn "Could not determine simulator. Using default: iPhone 17 Pro" SIMULATOR="iPhone 17 Pro" fi fi log_info "Selected simulator: $SIMULATOR" # Extract device ID for more reliable targeting # Format: " iPhone 17 Pro (68D19D08-4701-422C-AF61-2E21ACA1DD4C) (Shutdown)" SIMULATOR_ID=$(xcrun simctl list devices available 2>/dev/null | \ grep -i "$SIMULATOR" | \ head -1 | \ sed -n 's/.*(\([A-F0-9-]\{36\}\)).*/\1/p') # Verify simulator exists before building if [ -z "$SIMULATOR_ID" ] && ! xcrun simctl list devices available 2>/dev/null | grep -q "$SIMULATOR"; then log_warn "Simulator '$SIMULATOR' not found in available devices" log_info "Available iPhone simulators:" xcrun simctl list devices available 2>/dev/null | grep -i "iphone" | grep -v "iPhone Air" | head -5 log_warn "Attempting build anyway with: $SIMULATOR" fi # Build iOS app log_step "Building iOS app for simulator..." # Use device ID if available, otherwise use name if [ -n "$SIMULATOR_ID" ]; then DESTINATION="platform=iOS Simulator,id=$SIMULATOR_ID" log_info "Using simulator ID: $SIMULATOR_ID ($SIMULATOR)" else DESTINATION="platform=iOS Simulator,name=$SIMULATOR" log_info "Using simulator name: $SIMULATOR" fi if xcodebuild -workspace "$WORKSPACE" \ -scheme App \ -configuration Debug \ -sdk iphonesimulator \ -destination "$DESTINATION" \ build; then log_info "iOS app built successfully" # Find built app DERIVED_DATA="$HOME/Library/Developer/Xcode/DerivedData" APP_PATH=$(find "$DERIVED_DATA" -name "App.app" -path "*/Build/Products/Debug-iphonesimulator/*" -type d 2>/dev/null | head -1) if [ -n "$APP_PATH" ]; then log_info "App built at: $APP_PATH" # Run on simulator if requested if [ "$RUN_ALL" = true ] || [ "$RUN_IOS" = true ]; then log_step "Installing and launching iOS app on simulator..." # Use the device ID we already extracted, or get it again if [ -z "$SIMULATOR_ID" ]; then SIMULATOR_ID=$(xcrun simctl list devices available 2>/dev/null | \ grep -i "$SIMULATOR" | \ head -1 | \ sed -n 's/.*(\([A-F0-9-]\{36\}\)).*/\1/p') fi # If we have device ID, use it; otherwise try to boot by name if [ -n "$SIMULATOR_ID" ]; then SIMULATOR_UDID="$SIMULATOR_ID" log_info "Using simulator ID: $SIMULATOR_UDID" else # Try to boot simulator by name and get its ID log_step "Booting simulator: $SIMULATOR..." xcrun simctl boot "$SIMULATOR" 2>/dev/null || true sleep 2 SIMULATOR_UDID=$(xcrun simctl list devices 2>/dev/null | \ grep -i "$SIMULATOR" | \ grep -E "\([A-F0-9-]{36}\)" | \ head -1 | \ sed -n 's/.*(\([A-F0-9-]\{36\}\)).*/\1/p') fi if [ -n "$SIMULATOR_UDID" ]; then # Install app if xcrun simctl install "$SIMULATOR_UDID" "$APP_PATH"; then log_info "App installed on simulator" # Launch app APP_BUNDLE_ID="com.timesafari.dailynotification.test" if xcrun simctl launch "$SIMULATOR_UDID" "$APP_BUNDLE_ID"; then log_info "✅ iOS app launched successfully!" else log_warn "Failed to launch app (may already be running)" fi else log_warn "App installation failed (may already be installed)" fi else log_warn "Could not find or boot simulator" log_info "Open Xcode and run manually: open $WORKSPACE" fi fi else log_warn "Could not find built app in DerivedData" log_info "Build succeeded. Open Xcode to run: open $WORKSPACE" fi else log_error "iOS build failed" exit 1 fi cd "$PROJECT_DIR" fi fi log_info "" log_info "✅ Build process complete!" log_info "" # Summary if [ "$BUILD_ANDROID" = true ] || [ "$BUILD_ALL" = true ]; then if [ -f "$PROJECT_DIR/android/app/build/outputs/apk/debug/app-debug.apk" ]; then log_info "Android APK: $PROJECT_DIR/android/app/build/outputs/apk/debug/app-debug.apk" fi fi if [ "$BUILD_IOS" = true ] || [ "$BUILD_ALL" = true ]; then if [ -d "$IOS_DIR/App.xcworkspace" ]; then log_info "iOS Workspace: $IOS_DIR/App.xcworkspace" log_info "Open with: open $IOS_DIR/App.xcworkspace" fi fi