#!/bin/bash # Exit on error set -e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color # 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" } # Validation functions check_command() { if ! command -v $1 &> /dev/null; then # Try rbenv shims for pod command if [ "$1" = "pod" ] && [ -f "$HOME/.rbenv/shims/pod" ]; then log_info "Found pod in rbenv shims" return 0 fi 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_sqlite_conflicts() { local PLATFORM=$1 if [ "$PLATFORM" != "ios" ] && [ "$PLATFORM" != "all" ]; then return 0 fi # Check for pkgx SQLite that might interfere with iOS builds if [ -d "$HOME/.pkgx" ]; then # Look for pkgx SQLite installations PKGX_SQLITE=$(find "$HOME/.pkgx" -name "libsqlite3*.dylib" 2>/dev/null | head -1) if [ -n "$PKGX_SQLITE" ]; then log_warn "⚠️ pkgx SQLite detected: $PKGX_SQLITE" log_warn " This may cause iOS build failures (linking macOS dylib for iOS simulator)" log_warn " The build script will unset problematic environment variables" # Check if library paths point to pkgx if [ -n "$DYLD_LIBRARY_PATH" ] && echo "$DYLD_LIBRARY_PATH" | grep -q "\.pkgx"; then log_warn " DYLD_LIBRARY_PATH contains pkgx paths - will be unset for build" fi if [ -n "$LD_LIBRARY_PATH" ] && echo "$LD_LIBRARY_PATH" | grep -q "\.pkgx"; then log_warn " LD_LIBRARY_PATH contains pkgx paths - will be unset for build" fi fi fi # Check for system SQLite (from Command Line Tools) if command -v sqlite3 &> /dev/null; then SQLITE_PATH=$(which sqlite3) if [ "$SQLITE_PATH" = "/usr/bin/sqlite3" ]; then log_info "✅ System SQLite found (from Command Line Tools): $SQLITE_PATH" else log_warn "⚠️ SQLite found at non-standard location: $SQLITE_PATH" log_warn " Ensure this is compatible with iOS builds" fi else log_warn "⚠️ sqlite3 command not found - Command Line Tools may not be installed" log_warn " Install with: xcode-select --install" fi } check_command_line_tools() { if [ "$(uname)" != "Darwin" ]; then return 0 fi # Check if xcode-select points to a valid developer directory if ! xcode-select -p &> /dev/null; then log_error "Xcode Command Line Tools not configured" log_error " Run: xcode-select --install" exit 1 fi # Check if xcodebuild is available if ! command -v xcodebuild &> /dev/null; then log_error "xcodebuild not found - Command Line Tools may be incomplete" log_error " Run: xcode-select --install" exit 1 fi # Verify sqlite3 is available (part of Command Line Tools) if ! command -v sqlite3 &> /dev/null; then log_warn "⚠️ sqlite3 not found - Command Line Tools may be incomplete" log_warn " This may cause iOS build issues" log_warn " Run: xcode-select --install" fi } check_environment() { local PLATFORM=$1 # Check for required tools (always needed) check_command "node" check_command "npm" # Check Node.js version NODE_VERSION=$(node -v | cut -d. -f1 | tr -d 'v') if [ "$NODE_VERSION" -lt 14 ]; then log_error "Node.js version 14 or higher is required" exit 1 fi # Platform-specific checks if [ "$PLATFORM" = "android" ] || [ "$PLATFORM" = "all" ]; then check_command "java" # Check for Gradle Wrapper instead of system gradle if [ ! -f "android/gradlew" ]; then log_error "Gradle wrapper not found at android/gradlew" exit 1 fi # Check Java version JAVA_VERSION=$(java -version 2>&1 | head -n 1 | cut -d'"' -f2 | cut -d. -f1) if [ "$JAVA_VERSION" -lt 11 ]; then log_error "Java version 11 or higher is required" exit 1 fi # Check for Android SDK if [ -z "$ANDROID_HOME" ]; then log_error "ANDROID_HOME environment variable is not set" exit 1 fi fi if [ "$PLATFORM" = "ios" ] || [ "$PLATFORM" = "all" ]; then # Check Command Line Tools check_command_line_tools # Check for SQLite conflicts check_sqlite_conflicts "$PLATFORM" # iOS-specific checks are done in build_ios() function # to avoid failing if iOS tools aren't available when building Android only : fi } # Build functions build_typescript() { log_info "Building TypeScript..." npm run clean if ! npm run build; then log_error "TypeScript build failed" exit 1 fi } build_plugin_for_test_app() { log_info "Building plugin for test app..." # Ensure symlink is in place FIRST (before building) # The test app expects the plugin at node_modules/@timesafari/daily-notification-plugin SYMLINK="test-apps/daily-notification-test/node_modules/@timesafari/daily-notification-plugin" if [ ! -L "$SYMLINK" ] || [ ! -e "$SYMLINK" ]; then log_info "Creating symlink to plugin..." mkdir -p "$(dirname "$SYMLINK")" rm -f "$SYMLINK" ln -sf "../../../" "$SYMLINK" fi # Navigate to test app directory cd test-apps/daily-notification-test || exit 1 # Sync Capacitor Android (creates necessary project structure) log_info "Syncing Capacitor Android..." if ! npm run cap:sync:android; then log_error "Capacitor Android sync failed" exit 1 fi # Navigate to android directory cd android || exit 1 # Fix capacitor.build.gradle if it references missing cordova.variables.gradle # This file is auto-generated by Capacitor and may reference files that don't exist if [ -f "app/capacitor.build.gradle" ]; then if grep -q "^apply from: \"../capacitor-cordova-android-plugins/cordova.variables.gradle\"" "app/capacitor.build.gradle"; then # Check if the referenced file actually exists if [ ! -f "capacitor-cordova-android-plugins/cordova.variables.gradle" ]; then log_info "🔧 Applying fix to capacitor.build.gradle..." log_info " Reason: Referenced cordova.variables.gradle doesn't exist" log_info " Fix: Commenting out problematic 'apply from' line" # Apply the fix by commenting out the problematic line # Handle macOS vs Linux sed differences if [[ "$OSTYPE" == "darwin"* ]]; then # macOS sed requires empty string after -i # Use a simpler approach: just comment out the line sed -i '' 's|^apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"|// &|' "app/capacitor.build.gradle" else # Linux sed sed -i 's|^apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"|// &|' "app/capacitor.build.gradle" fi log_info "✅ Fix applied successfully" log_warn "⚠️ Note: This fix will be lost if you run 'npx cap sync' or 'npx cap update'" fi fi fi # Build the plugin and test app from within the test app's Android project context # This is necessary because Capacitor Android is only available as a project dependency # The plugin will be built automatically as a dependency of the app log_info "Building plugin and test app from test app context..." # Build test app (this will automatically build the plugin as a dependency) if ! ./gradlew clean assembleDebug; then log_error "Build failed" exit 1 fi # Verify plugin was built (optional check) PLUGIN_AAR="$(find . -name "*.aar" -path "*/timesafari-daily-notification-plugin/*" 2>/dev/null | head -1)" if [ -n "$PLUGIN_AAR" ]; then log_info "Plugin AAR built: $PLUGIN_AAR" fi # Verify APK was built APK_FILE="app/build/outputs/apk/debug/app-debug.apk" if [ ! -f "$APK_FILE" ]; then log_error "APK file not found at $APK_FILE" exit 1 fi log_info "Build successful: $APK_FILE" log_info "Install with: adb install -r $APK_FILE" cd ../../.. } build_plugin_for_test_app_ios() { log_info "Building iOS test app..." # Fix pkgx interference with SQLite linking # pkgx installs SQLite built for macOS, which causes linker errors when building for iOS simulator if [ -n "$PKGX_DIR" ] || [ -d "$HOME/.pkgx" ]; then log_warn "⚠️ pkgx detected - fixing SQLite linking conflicts..." # Check for pkgx SQLite specifically PKGX_SQLITE=$(find "$HOME/.pkgx" -name "libsqlite3*.dylib" 2>/dev/null | head -1) if [ -n "$PKGX_SQLITE" ]; then log_warn " Found pkgx SQLite: $PKGX_SQLITE" log_warn " This is built for macOS and will cause iOS simulator build failures" fi log_warn " Temporarily unsetting PKGX_DIR and library paths for build..." unset PKGX_DIR unset DYLD_LIBRARY_PATH unset LD_LIBRARY_PATH # Also unset any pkgx-related paths that might be in PATH export PATH=$(echo "$PATH" | tr ':' '\n' | grep -v "\.pkgx" | tr '\n' ':' | sed 's/:$//') fi # Ensure symlink is in place FIRST (before building) # The test app expects the plugin at node_modules/@timesafari/daily-notification-plugin SYMLINK="test-apps/daily-notification-test/node_modules/@timesafari/daily-notification-plugin" if [ ! -L "$SYMLINK" ] || [ ! -e "$SYMLINK" ]; then log_info "Creating symlink to plugin..." mkdir -p "$(dirname "$SYMLINK")" rm -f "$SYMLINK" ln -sf "../../../" "$SYMLINK" fi # Navigate to test app directory cd test-apps/daily-notification-test || exit 1 # Install dependencies if node_modules doesn't exist or is missing key packages if [ ! -d "node_modules" ] || [ ! -f "node_modules/.bin/run-p" ]; then log_info "Installing test app dependencies..." if ! npm install; then log_error "Failed to install test app dependencies" exit 1 fi fi # Build Vue app log_info "Building Vue app..." if ! npm run build; then log_error "Vue app build failed" exit 1 fi # Sync Capacitor iOS (handles Podfile fixes and pod install) log_info "Syncing Capacitor iOS..." if ! npm run cap:sync:ios; then log_error "Capacitor iOS sync failed" exit 1 fi # Build with xcodebuild cd ios/App || exit 1 # Find workspace if [ -d "App.xcworkspace" ]; then WORKSPACE="App.xcworkspace" SCHEME="App" elif [ -d "App.xcodeproj" ]; then PROJECT="App.xcodeproj" SCHEME="App" else log_error "No Xcode workspace or project found in ios/App" log_info "Expected: App.xcworkspace or App.xcodeproj" exit 1 fi # Auto-detect available iPhone simulator log_info "Detecting available iPhone simulator..." SIMULATOR_LINE=$(xcrun simctl list devices available 2>&1 | grep -i "iPhone" | head -1) if [ -n "$SIMULATOR_LINE" ]; then # Extract device ID (UUID in parentheses) SIMULATOR_ID=$(echo "$SIMULATOR_LINE" | sed -E 's/.*\(([A-F0-9-]+)\).*/\1/') # Extract device name (everything before the first parenthesis) SIMULATOR_NAME=$(echo "$SIMULATOR_LINE" | sed -E 's/^[[:space:]]*([^(]+).*/\1/' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') if [ -n "$SIMULATOR_ID" ] && [ "$SIMULATOR_ID" != "Shutdown" ] && [ "$SIMULATOR_ID" != "Booted" ]; then DESTINATION="platform=iOS Simulator,id=$SIMULATOR_ID" log_info "Building for iOS Simulator ($SIMULATOR_NAME, ID: $SIMULATOR_ID)..." elif [ -n "$SIMULATOR_NAME" ]; then DESTINATION="platform=iOS Simulator,name=$SIMULATOR_NAME" log_info "Building for iOS Simulator ($SIMULATOR_NAME)..." else DESTINATION="platform=iOS Simulator,name=iPhone 14" log_warn "Using default simulator destination: iPhone 14" fi else DESTINATION="platform=iOS Simulator,name=iPhone 14" log_warn "No iPhone simulators found, using default: iPhone 14" fi # Build for simulator log_info "Building iOS app for simulator..." if [ -n "$WORKSPACE" ]; then if ! xcodebuild build \ -workspace "$WORKSPACE" \ -scheme "$SCHEME" \ -configuration Debug \ -sdk iphonesimulator \ -destination "$DESTINATION" \ CODE_SIGN_IDENTITY="" \ CODE_SIGNING_REQUIRED=NO \ CODE_SIGNING_ALLOWED=NO; then log_error "iOS build failed" exit 1 fi else if ! xcodebuild build \ -project "$PROJECT" \ -scheme "$SCHEME" \ -configuration Debug \ -sdk iphonesimulator \ -destination "$DESTINATION" \ CODE_SIGN_IDENTITY="" \ CODE_SIGNING_REQUIRED=NO \ CODE_SIGNING_ALLOWED=NO; then log_error "iOS build failed" exit 1 fi fi log_info "iOS build successful!" log_info "App built in DerivedData. To run: xcrun simctl boot && xcrun simctl install booted " cd ../../.. } build_android() { log_info "Building Android..." # Detect project type if [ -d "test-apps/daily-notification-test" ]; then log_info "Detected test app. Building plugin and test app together..." build_plugin_for_test_app return 0 fi cd android || exit 1 # Check if this is a plugin development project if [ ! -f "capacitor-cordova-android-plugins/cordova.variables.gradle" ]; then log_warn "This appears to be a plugin development project" log_warn "Android test app not properly initialized" # ============================================================================= # AUTOMATIC FIX: capacitor.build.gradle for Plugin Development Projects # ============================================================================= # # PROBLEM: The capacitor.build.gradle file is auto-generated by Capacitor CLI # and includes a line that tries to load a file that doesn't exist in plugin # development projects: # apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" # # WHY THIS HAPPENS: # - This file is generated by 'npx cap sync', 'npx cap update', etc. # - It assumes a full Capacitor app with proper plugin integration # - Plugin development projects don't have the full Capacitor setup # # THE FIX: # - Comment out the problematic line to prevent build failures # - Add explanatory comment about why it's commented out # - This fix gets applied automatically every time the build script runs # # WHEN THIS FIX GETS OVERWRITTEN: # - Running 'npx cap sync' will regenerate the file and remove our fix # - Running 'npx cap update' will regenerate the file and remove our fix # - Running 'npx cap add android' will regenerate the file and remove our fix # # HOW TO RESTORE THE FIX: # - Run this build script again (it will reapply the fix automatically) # - Or run: ./scripts/fix-capacitor-build.sh # - Or manually comment out the problematic line # # ============================================================================= if [ -f "app/capacitor.build.gradle" ]; then if grep -q "^apply from: \"../capacitor-cordova-android-plugins/cordova.variables.gradle\"" "app/capacitor.build.gradle"; then log_info "🔧 Applying automatic fix to capacitor.build.gradle..." log_info " Reason: Plugin development project missing Capacitor integration files" log_info " Fix: Commenting out problematic 'apply from' line" # Apply the fix by commenting out the problematic line # Handle macOS vs Linux sed differences if [[ "$OSTYPE" == "darwin"* ]]; then # macOS sed requires empty string after -i sed -i '' 's/^apply from: "\.\.\/capacitor-cordova-android-plugins\/cordova\.variables\.gradle"/\/\/ Plugin development project - no Capacitor integration files needed\n\/\/ &/' "app/capacitor.build.gradle" else # Linux sed sed -i 's/^apply from: "\.\.\/capacitor-cordova-android-plugins\/cordova\.variables\.gradle"/\/\/ Plugin development project - no Capacitor integration files needed\n\/\/ &/' "app/capacitor.build.gradle" fi log_info "✅ Fix applied successfully" log_warn "⚠️ Note: This fix will be lost if you run 'npx cap sync' or 'npx cap update'" log_info "💡 To restore the fix later, run this build script again or use: ./scripts/fix-capacitor-build.sh" else log_info "ℹ️ capacitor.build.gradle already has the fix applied" fi else log_warn "⚠️ capacitor.build.gradle not found - this may indicate a different project structure" fi log_warn "Plugin source code has been built successfully" log_warn "To test the plugin, use it in a Capacitor app instead" cd .. return 0 fi # Check for gradle wrapper if [ ! -f "./gradlew" ]; then log_error "Gradle wrapper not found" exit 1 fi # Clean and build if ! ./gradlew clean; then log_error "Android clean failed" exit 1 fi if ! ./gradlew assembleRelease; then log_error "Android build failed" exit 1 fi # Verify AAR file exists AAR_FILE="capacitor-cordova-android-plugins/build/outputs/aar/capacitor-cordova-android-plugins-release.aar" if [ ! -f "$AAR_FILE" ]; then log_error "AAR file not found at $AAR_FILE" exit 1 fi log_info "Android build successful: $AAR_FILE" cd .. } build_ios() { log_info "Building iOS..." # Check iOS-specific requirements check_command "xcodebuild" check_command "pod" # Check for Xcode if ! xcodebuild -version &> /dev/null; then log_error "Xcode is not installed or not properly configured" exit 1 fi # Check for SQLite conflicts (already done in check_environment, but remind here) if [ -d "$HOME/.pkgx" ]; then PKGX_SQLITE=$(find "$HOME/.pkgx" -name "libsqlite3*.dylib" 2>/dev/null | head -1) if [ -n "$PKGX_SQLITE" ]; then log_warn "⚠️ pkgx SQLite detected - build script will handle this automatically" log_warn " If you still see linker errors, manually run:" log_warn " unset PKGX_DIR DYLD_LIBRARY_PATH LD_LIBRARY_PATH && ./scripts/build-native.sh --platform ios" fi fi # Detect project type if [ -d "test-apps/daily-notification-test" ]; then log_info "Detected test app. Building plugin and test app together..." build_plugin_for_test_app_ios return 0 fi # For plugin-only builds, navigate to iOS directory cd ios || exit 1 log_warn "This appears to be a plugin development project" log_warn "iOS plugin source code has been built successfully" log_warn "To test the plugin, use it in a Capacitor app instead" # Install CocoaPods dependencies if Podfile exists if [ -f "Podfile" ]; then log_info "Installing CocoaPods dependencies..." POD_CMD=$(get_pod_command) if ! $POD_CMD install; then log_error "CocoaPods installation failed" exit 1 fi fi # Build plugin framework (if workspace exists) if [ -d "DailyNotificationPlugin.xcworkspace" ]; then log_info "Building iOS plugin framework..." if ! xcodebuild build \ -workspace DailyNotificationPlugin.xcworkspace \ -scheme DailyNotificationPlugin \ -configuration Release \ -sdk iphoneos \ CODE_SIGN_IDENTITY="" \ CODE_SIGNING_REQUIRED=NO \ CODE_SIGNING_ALLOWED=NO; then log_error "iOS plugin build failed" exit 1 fi log_info "iOS plugin build successful" else log_warn "No Xcode workspace found - plugin may need to be built in a host app" fi cd .. } # Main build process main() { log_info "Starting build process..." # Parse command line arguments BUILD_PLATFORM="all" while [[ $# -gt 0 ]]; do case $1 in --platform) BUILD_PLATFORM="$2" shift 2 ;; *) log_error "Unknown option: $1" exit 1 ;; esac done # Build TypeScript (needed for all platforms) build_typescript # Check environment (after parsing platform so we can check conditionally) check_environment "$BUILD_PLATFORM" # Build based on platform case $BUILD_PLATFORM in "android") build_android ;; "ios") build_ios ;; "all") build_android build_ios ;; *) log_error "Invalid platform: $BUILD_PLATFORM. Use 'android', 'ios', or 'all'" exit 1 ;; esac log_info "Build completed successfully!" } # Run main function with all arguments main "$@"