#!/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_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 # iOS 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 # 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..." cd test-apps/daily-notification-test/android || exit 1 # 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..." # Warn about pkgx interference with SQLite linking if [ -n "$PKGX_DIR" ] || [ -d "$HOME/.pkgx" ]; then log_warn "âš ī¸ pkgx detected - this may interfere with iOS SQLite linking" log_warn " Temporarily unsetting PKGX_DIR and DYLD_LIBRARY_PATH for build..." unset PKGX_DIR unset DYLD_LIBRARY_PATH unset LD_LIBRARY_PATH 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 # 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 # Warn about pkgx interference with SQLite linking if [ -n "$PKGX_DIR" ] || [ -d "$HOME/.pkgx" ]; then log_warn "âš ī¸ pkgx detected - this may interfere with iOS SQLite linking" log_warn " If you see 'sqlite3' undefined symbol errors, try:" log_warn " unset PKGX_DIR && unset DYLD_LIBRARY_PATH && ./scripts/build-native.sh --platform ios" 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 "$@"