- Add SafeArea and SharedImage plugins to capacitor.plugins.json - Create restore-local-plugins.js to persist plugins after cap sync - Integrate plugin restoration into build scripts - Improve Android share intent timing with retry logic - Clean up debug logging while keeping essential error handling - Add documentation for local plugin management Fixes issue where SharedImage plugin wasn't recognized, causing "UNIMPLEMENTED" errors. Image sharing now works correctly on Android.
534 lines
18 KiB
Bash
Executable File
534 lines
18 KiB
Bash
Executable File
#!/bin/bash
|
|
# build-android.sh
|
|
# Author: Matthew Raymer
|
|
# Date: 2025-07-11
|
|
# Description: Android build script for TimeSafari application
|
|
# This script handles the complete Android build process including cleanup,
|
|
# web build, Capacitor build, Gradle build, and Android Studio launch.
|
|
#
|
|
# Usage:
|
|
# ./scripts/build-android.sh [options]
|
|
#
|
|
# Options:
|
|
# --dev, --development Build for development environment
|
|
# --test Build for testing environment
|
|
# --prod, --production Build for production environment
|
|
# --debug Build debug APK
|
|
# --release Build release APK
|
|
# --studio Open Android Studio after build
|
|
# --apk Build APK file
|
|
# --aab Build AAB (Android App Bundle)
|
|
# --clean Clean build artifacts only
|
|
# --sync Sync Capacitor only
|
|
# --assets Generate assets only
|
|
# --deploy Deploy APK to connected device
|
|
# --uninstall Uninstall app from connected device
|
|
# -h, --help Show this help message
|
|
# -v, --verbose Enable verbose logging
|
|
#
|
|
# Examples:
|
|
# ./scripts/build-android.sh --dev --studio # Development build + open studio
|
|
# ./scripts/build-android.sh --prod --apk # Production APK build
|
|
# ./scripts/build-android.sh --test --aab # Testing AAB build
|
|
# ./scripts/build-android.sh --clean # Clean only
|
|
# ./scripts/build-android.sh --sync # Sync only
|
|
#
|
|
# Exit Codes:
|
|
# 1 - Android cleanup failed
|
|
# 2 - Web build failed
|
|
# 3 - Capacitor build failed
|
|
# 4 - Gradle clean failed
|
|
# 5 - Gradle assemble failed
|
|
# 6 - Capacitor sync failed
|
|
# 7 - Asset generation failed
|
|
# 8 - Android Studio launch failed
|
|
# 9 - Android asset validation failed
|
|
|
|
# Exit on any error
|
|
set -e
|
|
|
|
# Source common utilities
|
|
source "$(dirname "$0")/common.sh"
|
|
|
|
# Function to validate critical dependencies
|
|
validate_dependencies() {
|
|
log_info "Validating critical dependencies..."
|
|
|
|
# Check if node_modules exists
|
|
if [ ! -d "node_modules" ]; then
|
|
log_error "node_modules directory not found. Please run 'npm install' first."
|
|
exit 1
|
|
fi
|
|
|
|
# Check if tsx is available
|
|
if [ ! -f "node_modules/.bin/tsx" ]; then
|
|
log_error "tsx dependency not found. Please run 'npm install' first."
|
|
exit 1
|
|
fi
|
|
|
|
# Check if capacitor-assets is available
|
|
if [ ! -f "node_modules/.bin/capacitor-assets" ]; then
|
|
log_error "capacitor-assets dependency not found. Please run 'npm install' first."
|
|
exit 1
|
|
fi
|
|
|
|
log_success "All critical dependencies validated successfully"
|
|
}
|
|
|
|
# Function to validate Android assets and resources
|
|
validate_android_assets() {
|
|
log_info "Validating Android assets and resources..."
|
|
|
|
# Check if source assets exist
|
|
local missing_assets=()
|
|
|
|
if [ ! -f "resources/icon.png" ]; then
|
|
missing_assets+=("resources/icon.png")
|
|
fi
|
|
|
|
if [ ! -f "resources/splash.png" ]; then
|
|
missing_assets+=("resources/splash.png")
|
|
fi
|
|
|
|
if [ ! -f "resources/splash_dark.png" ]; then
|
|
missing_assets+=("resources/splash_dark.png")
|
|
fi
|
|
|
|
if [ ${#missing_assets[@]} -gt 0 ]; then
|
|
log_error "Missing source assets:"
|
|
for asset in "${missing_assets[@]}"; do
|
|
log_error " - $asset"
|
|
done
|
|
log_error "Please ensure all required assets are present in the resources/ directory."
|
|
return 1
|
|
fi
|
|
|
|
# Check if Android drawable resources exist
|
|
local missing_drawables=()
|
|
|
|
if [ ! -f "android/app/src/main/res/drawable/splash.png" ]; then
|
|
missing_drawables+=("drawable/splash.png")
|
|
fi
|
|
|
|
# Check if mipmap resources exist
|
|
local missing_mipmaps=()
|
|
local mipmap_dirs=("mipmap-mdpi" "mipmap-hdpi" "mipmap-xhdpi" "mipmap-xxhdpi" "mipmap-xxxhdpi")
|
|
|
|
for dir in "${mipmap_dirs[@]}"; do
|
|
if [ ! -f "android/app/src/main/res/$dir/ic_launcher.png" ]; then
|
|
missing_mipmaps+=("$dir/ic_launcher.png")
|
|
fi
|
|
if [ ! -f "android/app/src/main/res/$dir/ic_launcher_round.png" ]; then
|
|
missing_mipmaps+=("$dir/ic_launcher_round.png")
|
|
fi
|
|
done
|
|
|
|
# If any resources are missing, regenerate them
|
|
if [ ${#missing_drawables[@]} -gt 0 ] || [ ${#missing_mipmaps[@]} -gt 0 ]; then
|
|
log_warn "Missing Android resources detected:"
|
|
for resource in "${missing_drawables[@]}" "${missing_mipmaps[@]}"; do
|
|
log_warn " - $resource"
|
|
done
|
|
|
|
log_info "Regenerating Android assets..."
|
|
|
|
# Create assets directory if it doesn't exist
|
|
mkdir -p assets
|
|
|
|
# Copy source assets to assets directory for capacitor-assets
|
|
cp resources/icon.png assets/ 2>/dev/null || log_warn "Could not copy icon.png"
|
|
cp resources/splash.png assets/ 2>/dev/null || log_warn "Could not copy splash.png"
|
|
cp resources/splash_dark.png assets/ 2>/dev/null || log_warn "Could not copy splash_dark.png"
|
|
|
|
# Generate assets
|
|
if npx @capacitor/assets generate >/dev/null 2>&1; then
|
|
log_success "Android assets regenerated successfully"
|
|
|
|
# Clean up temporary assets
|
|
rm -f assets/icon.png assets/splash.png assets/splash_dark.png
|
|
|
|
# Verify the resources were created
|
|
local verification_failed=false
|
|
|
|
if [ ! -f "android/app/src/main/res/drawable/splash.png" ]; then
|
|
log_error "Failed to generate drawable/splash.png"
|
|
verification_failed=true
|
|
fi
|
|
|
|
for dir in "${mipmap_dirs[@]}"; do
|
|
if [ ! -f "android/app/src/main/res/$dir/ic_launcher.png" ]; then
|
|
log_error "Failed to generate $dir/ic_launcher.png"
|
|
verification_failed=true
|
|
fi
|
|
if [ ! -f "android/app/src/main/res/$dir/ic_launcher_round.png" ]; then
|
|
log_error "Failed to generate $dir/ic_launcher_round.png"
|
|
verification_failed=true
|
|
fi
|
|
done
|
|
|
|
if [ "$verification_failed" = true ]; then
|
|
log_error "Asset generation completed but some resources are still missing."
|
|
log_info "You may need to manually create the missing resources or check the asset generation process."
|
|
return 1
|
|
fi
|
|
else
|
|
log_error "Failed to generate Android assets"
|
|
log_info "You may need to manually create the missing resources:"
|
|
for resource in "${missing_drawables[@]}" "${missing_mipmaps[@]}"; do
|
|
log_info " - android/app/src/main/res/$resource"
|
|
done
|
|
return 1
|
|
fi
|
|
else
|
|
log_success "All Android assets and resources validated successfully"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# Default values
|
|
BUILD_MODE="development"
|
|
BUILD_TYPE="debug"
|
|
OPEN_STUDIO=false
|
|
BUILD_APK=false
|
|
BUILD_AAB=false
|
|
CLEAN_ONLY=false
|
|
SYNC_ONLY=false
|
|
ASSETS_ONLY=false
|
|
DEPLOY_APP=false
|
|
AUTO_RUN=false
|
|
UNINSTALL=false
|
|
CUSTOM_API_IP=""
|
|
|
|
# Function to parse Android-specific arguments
|
|
parse_android_args() {
|
|
local args=("$@")
|
|
local i=0
|
|
|
|
while [ $i -lt ${#args[@]} ]; do
|
|
local arg="${args[$i]}"
|
|
|
|
case $arg in
|
|
--dev|--development)
|
|
BUILD_MODE="development"
|
|
;;
|
|
--test)
|
|
BUILD_MODE="test"
|
|
;;
|
|
--prod|--production)
|
|
BUILD_MODE="production"
|
|
;;
|
|
--debug)
|
|
BUILD_TYPE="debug"
|
|
;;
|
|
--release)
|
|
BUILD_TYPE="release"
|
|
;;
|
|
--studio)
|
|
OPEN_STUDIO=true
|
|
;;
|
|
--apk)
|
|
BUILD_APK=true
|
|
;;
|
|
--aab)
|
|
BUILD_AAB=true
|
|
;;
|
|
--clean)
|
|
CLEAN_ONLY=true
|
|
;;
|
|
--sync)
|
|
SYNC_ONLY=true
|
|
;;
|
|
--assets|--assets-only)
|
|
ASSETS_ONLY=true
|
|
;;
|
|
--deploy)
|
|
DEPLOY_APP=true
|
|
;;
|
|
--auto-run)
|
|
AUTO_RUN=true
|
|
;;
|
|
--uninstall)
|
|
UNINSTALL=true
|
|
;;
|
|
--api-ip)
|
|
if [ $((i + 1)) -lt ${#args[@]} ]; then
|
|
CUSTOM_API_IP="${args[$((i + 1))]}"
|
|
i=$((i + 1)) # Skip the next argument
|
|
else
|
|
log_error "Error: --api-ip requires an IP address"
|
|
exit 1
|
|
fi
|
|
;;
|
|
--api-ip=*)
|
|
CUSTOM_API_IP="${arg#*=}"
|
|
;;
|
|
-h|--help)
|
|
print_android_usage
|
|
exit 0
|
|
;;
|
|
-v|--verbose)
|
|
set -x
|
|
;;
|
|
*)
|
|
log_warn "Unknown argument: $arg"
|
|
;;
|
|
esac
|
|
i=$((i + 1))
|
|
done
|
|
}
|
|
|
|
# Function to print Android-specific usage
|
|
print_android_usage() {
|
|
echo "Usage: $0 [options]"
|
|
echo ""
|
|
echo "Android Build Options:"
|
|
echo " --dev, --development Build for development environment"
|
|
echo " --test Build for testing environment"
|
|
echo " --prod, --production Build for production environment"
|
|
echo " --debug Build debug APK (default)"
|
|
echo " --release Build release APK"
|
|
echo " --studio Open Android Studio after build"
|
|
echo " --apk Build APK file"
|
|
echo " --aab Build AAB (Android App Bundle)"
|
|
echo " --clean Clean build artifacts only"
|
|
echo " --sync Sync Capacitor only"
|
|
echo " --assets Generate assets only"
|
|
echo " --deploy Deploy APK to connected device"
|
|
echo " --auto-run Auto-run app after build"
|
|
echo " --uninstall Uninstall app from connected device"
|
|
echo " --api-ip <ip> Custom IP address for claim API (defaults to 10.0.2.2)"
|
|
echo ""
|
|
echo "Common Options:"
|
|
echo " -h, --help Show this help message"
|
|
echo " -v, --verbose Enable verbose logging"
|
|
echo ""
|
|
echo "Examples:"
|
|
echo " $0 --dev --studio # Development build + open studio"
|
|
echo " $0 --prod --apk # Production APK build"
|
|
echo " $0 --test --aab # Testing AAB build"
|
|
echo " $0 --test --auto-run # Test build + auto-run"
|
|
echo " $0 --clean # Clean only"
|
|
echo " $0 --sync # Sync only"
|
|
echo " $0 --deploy # Build and deploy to device"
|
|
echo " $0 --uninstall # Uninstall app from device"
|
|
echo " $0 --dev # Dev build with default 10.0.2.2"
|
|
echo " $0 --dev --api-ip 192.168.1.100 # Dev build with custom API IP"
|
|
echo ""
|
|
}
|
|
|
|
# Parse command line arguments
|
|
parse_android_args "$@"
|
|
|
|
# Print build header
|
|
print_header "TimeSafari Android Build Process"
|
|
|
|
# Validate dependencies before proceeding
|
|
validate_dependencies
|
|
|
|
# Validate Android assets and resources
|
|
validate_android_assets || {
|
|
log_error "Android asset validation failed. Please fix the issues above and try again."
|
|
exit 9
|
|
}
|
|
|
|
# Log build start
|
|
log_info "Starting Android build process at $(date)"
|
|
log_info "Build mode: $BUILD_MODE"
|
|
log_info "Build type: $BUILD_TYPE"
|
|
|
|
# Setup environment for Capacitor build
|
|
setup_build_env "capacitor" "$BUILD_MODE"
|
|
|
|
# Override API servers for Android development
|
|
if [ "$BUILD_MODE" = "development" ]; then
|
|
if [ -n "$CUSTOM_API_IP" ]; then
|
|
# Use custom IP for physical device development
|
|
export VITE_DEFAULT_ENDORSER_API_SERVER="http://${CUSTOM_API_IP}:3000"
|
|
export VITE_DEFAULT_PARTNER_API_SERVER="http://${CUSTOM_API_IP}:3000"
|
|
log_info "Android development mode: Using custom IP ${CUSTOM_API_IP} for physical device"
|
|
else
|
|
# Use Android emulator IP (10.0.2.2) for Android development
|
|
export VITE_DEFAULT_ENDORSER_API_SERVER="http://10.0.2.2:3000"
|
|
export VITE_DEFAULT_PARTNER_API_SERVER="http://10.0.2.2:3000"
|
|
log_debug "Android development mode: Using 10.0.2.2 for emulator"
|
|
fi
|
|
fi
|
|
|
|
# Setup application directories
|
|
setup_app_directories
|
|
|
|
# Load environment-specific .env file if it exists
|
|
env_file=".env.$BUILD_MODE"
|
|
if [ -f "$env_file" ]; then
|
|
load_env_file "$env_file"
|
|
else
|
|
log_debug "No $env_file file found, using default environment"
|
|
fi
|
|
|
|
# Load .env file if it exists (fallback)
|
|
if [ -f ".env" ]; then
|
|
load_env_file ".env"
|
|
fi
|
|
|
|
# Handle clean-only mode
|
|
if [ "$CLEAN_ONLY" = true ]; then
|
|
log_info "Clean-only mode: cleaning build artifacts"
|
|
safe_execute "Cleaning Android app" "npm run clean:android" || exit 1
|
|
safe_execute "Cleaning dist directory" "clean_build_artifacts dist" || exit 1
|
|
safe_execute "Cleaning Gradle build" "cd android && ./gradlew clean && cd .." || exit 4
|
|
log_success "Clean completed successfully!"
|
|
exit 0
|
|
fi
|
|
|
|
# Handle sync-only mode
|
|
if [ "$SYNC_ONLY" = true ]; then
|
|
log_info "Sync-only mode: syncing with Capacitor"
|
|
safe_execute "Syncing with Capacitor" "npx cap sync android" || exit 6
|
|
safe_execute "Restoring local plugins" "node scripts/restore-local-plugins.js" || exit 7
|
|
log_success "Sync completed successfully!"
|
|
exit 0
|
|
fi
|
|
|
|
# Handle assets-only mode
|
|
if [ "$ASSETS_ONLY" = true ]; then
|
|
log_info "Assets-only mode: generating assets"
|
|
safe_execute "Generating assets" "npx capacitor-assets generate --android" || exit 7
|
|
log_success "Assets generation completed successfully!"
|
|
exit 0
|
|
fi
|
|
|
|
# Handle deploy-app mode
|
|
if [ "$DEPLOY_APP" = true ]; then
|
|
log_info "Deploy-app mode: building APK and deploying to device"
|
|
|
|
# Check for connected device
|
|
if ! adb devices | grep -q $'\tdevice'; then
|
|
log_error "No Android device connected. Please connect a device and try again."
|
|
exit 1
|
|
fi
|
|
|
|
# Build APK
|
|
safe_execute "Building APK" "cd android && ./gradlew assembleDebug && cd .." || exit 5
|
|
|
|
# Install APK on device
|
|
safe_execute "Installing APK on device" "adb install -r android/app/build/outputs/apk/debug/app-debug.apk" || exit 6
|
|
|
|
log_success "APK deployed successfully to device!"
|
|
log_info "You can now run the app with: npx cap run android"
|
|
exit 0
|
|
fi
|
|
|
|
# Step 1: Validate asset configuration
|
|
safe_execute "Validating asset configuration" "npm run assets:validate" || {
|
|
log_warn "Asset validation found issues, but continuing with build..."
|
|
log_info "If you encounter build failures, please run 'npm install' first to ensure all dependencies are available."
|
|
}
|
|
|
|
# Step 2: Uninstall Android app
|
|
if [ "$UNINSTALL" = true ]; then
|
|
log_info "Uninstall: uninstalling app from device"
|
|
safe_execute "Uninstalling Android app" "./scripts/uninstall-android.sh" || exit 1
|
|
log_success "Uninstall completed successfully!"
|
|
exit 0
|
|
fi
|
|
|
|
# Step 3: Clean dist directory
|
|
log_info "Cleaning dist directory..."
|
|
clean_build_artifacts "dist"
|
|
|
|
# Step 4: Run TypeScript type checking for test and production builds
|
|
if [ "$BUILD_MODE" = "production" ] || [ "$BUILD_MODE" = "test" ]; then
|
|
log_info "Running TypeScript type checking for $BUILD_MODE mode..."
|
|
|
|
if ! measure_time npm run type-check; then
|
|
log_error "TypeScript type checking failed for $BUILD_MODE mode!"
|
|
exit 2
|
|
fi
|
|
|
|
log_success "TypeScript type checking completed for $BUILD_MODE mode"
|
|
else
|
|
log_debug "Skipping TypeScript type checking for development mode"
|
|
fi
|
|
|
|
# Step 5: Build Capacitor version with mode
|
|
if [ "$BUILD_MODE" = "development" ]; then
|
|
safe_execute "Building Capacitor version (development)" "npm run build:capacitor" || exit 3
|
|
elif [ "$BUILD_MODE" = "test" ]; then
|
|
safe_execute "Building Capacitor version (test)" "npm run build:capacitor -- --mode test" || exit 3
|
|
elif [ "$BUILD_MODE" = "production" ]; then
|
|
safe_execute "Building Capacitor version (production)" "npm run build:capacitor -- --mode production" || exit 3
|
|
fi
|
|
|
|
# Step 6: Clean Gradle build
|
|
safe_execute "Cleaning Gradle build" "cd android && ./gradlew clean && cd .." || exit 4
|
|
|
|
# Step 7: Build based on type
|
|
if [ "$BUILD_TYPE" = "debug" ]; then
|
|
safe_execute "Assembling debug build" "cd android && ./gradlew assembleDebug && cd .." || exit 5
|
|
elif [ "$BUILD_TYPE" = "release" ]; then
|
|
safe_execute "Assembling release build" "cd android && ./gradlew assembleRelease && cd .." || exit 5
|
|
fi
|
|
|
|
# Step 8: Sync with Capacitor
|
|
safe_execute "Syncing with Capacitor" "npx cap sync android" || exit 6
|
|
|
|
# Step 8.5: Restore local plugins (capacitor.plugins.json gets overwritten by cap sync)
|
|
safe_execute "Restoring local plugins" "node scripts/restore-local-plugins.js" || exit 7
|
|
|
|
# Step 9: Generate assets
|
|
safe_execute "Generating assets" "npx capacitor-assets generate --android" || exit 7
|
|
|
|
# Step 10: Build APK/AAB if requested
|
|
if [ "$BUILD_APK" = true ]; then
|
|
if [ "$BUILD_TYPE" = "debug" ]; then
|
|
safe_execute "Building debug APK" "cd android && ./gradlew assembleDebug && cd .." || exit 5
|
|
else
|
|
safe_execute "Building release APK" "cd android && ./gradlew assembleRelease && cd .." || exit 5
|
|
fi
|
|
fi
|
|
|
|
if [ "$BUILD_AAB" = true ]; then
|
|
safe_execute "Building AAB" "cd android && ./gradlew bundleRelease && cd .." || exit 5
|
|
fi
|
|
|
|
# Step 11: Auto-run app if requested
|
|
if [ "$AUTO_RUN" = true ]; then
|
|
log_step "Auto-running Android app..."
|
|
safe_execute "Launching app" "npx cap run android" || {
|
|
log_error "Failed to launch Android app"
|
|
log_info "You can manually run with: npx cap run android"
|
|
exit 9
|
|
}
|
|
log_success "Android app launched successfully!"
|
|
fi
|
|
|
|
# Step 12: Open Android Studio if requested
|
|
if [ "$OPEN_STUDIO" = true ]; then
|
|
safe_execute "Opening Android Studio" "npx cap open android" || exit 8
|
|
fi
|
|
|
|
# Print build summary
|
|
log_success "Android build completed successfully!"
|
|
log_info "Build mode: $BUILD_MODE"
|
|
log_info "Build type: $BUILD_TYPE"
|
|
if [ "$BUILD_APK" = true ]; then
|
|
log_info "APK build: completed"
|
|
fi
|
|
if [ "$BUILD_AAB" = true ]; then
|
|
log_info "AAB build: completed"
|
|
fi
|
|
if [ "$AUTO_RUN" = true ]; then
|
|
log_info "Auto-run: completed"
|
|
fi
|
|
if [ "$OPEN_STUDIO" = true ]; then
|
|
log_info "Android Studio: opened"
|
|
fi
|
|
|
|
# Reminder about dependency management
|
|
log_info "💡 Tip: If you encounter dependency issues, run 'npm install' to ensure all packages are up to date."
|
|
|
|
print_footer "Android Build"
|
|
|
|
# Exit with success
|
|
exit 0 |