Files
daily-notification-plugin/scripts/build-ios-test-app.sh
Matthew 9f26588331 fix(ios): iOS 13.0 compatibility and test app UI unification
Fixed iOS 13.0 compatibility issue in test harness by replacing Logger
(iOS 14+) with os_log (iOS 13+). Fixed build script to correctly detect
and sync Capacitor config from App subdirectory. Unified both Android
and iOS test app UIs to use www/index.html as the canonical source.

Changes:
- DailyNotificationBackgroundTaskTestHarness: Replace Logger with os_log
  for iOS 13.0 deployment target compatibility
- build-ios-test-app.sh: Fix Capacitor sync path detection to check
  both current directory and App/ subdirectory for config files
- test-apps: Update both Android and iOS test apps to use www/index.html
  as the canonical UI source for consistency

This ensures the plugin builds on iOS 13.0+ and both test apps provide
the same testing experience across platforms.
2025-11-18 21:25:14 -08:00

502 lines
19 KiB
Bash
Executable File

#!/bin/bash
# Exit on error
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
# 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
# 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() {
log_step "Checking environment..."
# Check for required tools
check_command "xcodebuild"
check_command "pod"
check_command "node"
check_command "npm"
# Check for Xcode
if ! xcodebuild -version &> /dev/null; then
log_error "Xcode is not installed or not properly configured"
exit 1
fi
# 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
log_info "Environment check passed"
}
# Parse arguments
TARGET="simulator"
BUILD_CONFIG="Debug"
while [[ $# -gt 0 ]]; do
case $1 in
--simulator)
TARGET="simulator"
shift
;;
--device)
TARGET="device"
shift
;;
--release)
BUILD_CONFIG="Release"
shift
;;
--help)
echo "Usage: $0 [--simulator|--device] [--release]"
echo ""
echo "Options:"
echo " --simulator Build for iOS Simulator (default)"
echo " --device Build for physical device"
echo " --release Build Release configuration (default: Debug)"
echo " --help Show this help message"
exit 0
;;
*)
log_error "Unknown option: $1"
echo "Use --help for usage information"
exit 1
;;
esac
done
# Check if iOS test app exists
TEST_APP_DIR="test-apps/ios-test-app"
if [ ! -d "$TEST_APP_DIR" ]; then
log_error "iOS test app not found at $TEST_APP_DIR"
log_info "The iOS test app needs to be created first."
log_info "See doc/directives/0003-iOS-Android-Parity-Directive.md for requirements."
exit 1
fi
# Main build function
build_ios_test_app() {
log_step "Building iOS test app..."
# Navigate to iOS App directory (where workspace is located)
IOS_APP_DIR="$TEST_APP_DIR/ios/App"
if [ ! -d "$IOS_APP_DIR" ]; then
log_error "iOS App directory not found: $IOS_APP_DIR"
exit 1
fi
cd "$IOS_APP_DIR" || exit 1
# Check for workspace or project (these are directories, not files)
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_DIR"
log_info "Expected: App.xcworkspace or App.xcodeproj"
log_info "Found files: $(ls -la | head -10)"
exit 1
fi
# Install CocoaPods dependencies
log_step "Installing CocoaPods dependencies..."
POD_CMD=$(get_pod_command)
if [ -f "Podfile" ]; then
if ! $POD_CMD install; then
log_error "CocoaPods installation failed"
exit 1
fi
log_info "CocoaPods dependencies installed"
else
log_warn "No Podfile found, skipping pod install"
fi
# Build TypeScript/JavaScript if package.json exists
if [ -f "package.json" ]; then
log_step "Building web assets..."
if [ -f "package.json" ] && grep -q "\"build\"" package.json; then
if ! npm run build; then
log_error "Web assets build failed"
exit 1
fi
log_info "Web assets built"
fi
# Sync Capacitor if needed
# Check for config in current directory or App subdirectory
CAP_CONFIG_DIR=""
if [ -f "capacitor.config.ts" ] || [ -f "capacitor.config.json" ]; then
CAP_CONFIG_DIR="."
elif [ -f "App/capacitor.config.json" ] || [ -f "App/capacitor.config.ts" ]; then
CAP_CONFIG_DIR="App"
fi
if command -v npx &> /dev/null && [ -n "$CAP_CONFIG_DIR" ]; then
log_step "Syncing Capacitor..."
# Run sync from directory containing config
if [ "$CAP_CONFIG_DIR" != "." ]; then
cd "$CAP_CONFIG_DIR" || exit 1
fi
if ! npx cap sync ios; then
log_error "Capacitor sync failed"
exit 1
fi
# Return to ios/App directory if we changed
if [ "$CAP_CONFIG_DIR" != "." ]; then
cd .. || exit 1
fi
log_info "Capacitor synced"
fi
fi
# Determine SDK and destination
if [ "$TARGET" = "simulator" ]; then
SDK="iphonesimulator"
# Initialize simulator variables
SIMULATOR_ID=""
SIMULATOR_NAME=""
# Auto-detect available iPhone simulator using device ID (more reliable)
log_step "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
# Use device ID (most reliable)
DESTINATION="platform=iOS Simulator,id=$SIMULATOR_ID"
log_info "Building for iOS Simulator ($SIMULATOR_NAME, ID: $SIMULATOR_ID)..."
elif [ -n "$SIMULATOR_NAME" ]; then
# Fallback to device name
DESTINATION="platform=iOS Simulator,name=$SIMULATOR_NAME"
log_info "Building for iOS Simulator ($SIMULATOR_NAME)..."
else
# Last resort: generic destination
DESTINATION="platform=iOS Simulator,name=Any iOS Simulator Device"
log_warn "Using generic simulator destination"
fi
else
# No iPhone simulators found, use generic
DESTINATION="platform=iOS Simulator,name=Any iOS Simulator Device"
log_warn "No iPhone simulators found, using generic destination"
fi
ARCHIVE_PATH="build/ios-test-app-simulator.xcarchive"
else
SDK="iphoneos"
DESTINATION="generic/platform=iOS"
ARCHIVE_PATH="build/ios-test-app-device.xcarchive"
fi
# Clean build folder
log_step "Cleaning build folder..."
if [ -n "$WORKSPACE" ]; then
xcodebuild clean -workspace "$WORKSPACE" -scheme "$SCHEME" -configuration "$BUILD_CONFIG" -sdk "$SDK" || true
else
xcodebuild clean -project "$PROJECT" -scheme "$SCHEME" -configuration "$BUILD_CONFIG" -sdk "$SDK" || true
fi
# Build
log_step "Building for $TARGET ($BUILD_CONFIG)..."
if [ -n "$WORKSPACE" ]; then
if ! xcodebuild build \
-workspace "$WORKSPACE" \
-scheme "$SCHEME" \
-configuration "$BUILD_CONFIG" \
-sdk "$SDK" \
-destination "$DESTINATION" \
CODE_SIGN_IDENTITY="" \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGNING_ALLOWED=NO; then
log_error "Build failed"
exit 1
fi
else
if ! xcodebuild build \
-project "$PROJECT" \
-scheme "$SCHEME" \
-configuration "$BUILD_CONFIG" \
-sdk "$SDK" \
-destination "$DESTINATION" \
CODE_SIGN_IDENTITY="" \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGNING_ALLOWED=NO; then
log_error "Build failed"
exit 1
fi
fi
log_info "Build successful!"
# Find the built app in DerivedData
if [ "$TARGET" = "simulator" ]; then
# Xcode builds to DerivedData, find the app there
DERIVED_DATA_PATH="$HOME/Library/Developer/Xcode/DerivedData"
APP_PATH=$(find "$DERIVED_DATA_PATH" -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"
log_info ""
# Boot simulator if not already booted
log_step "Checking simulator status..."
if [ -n "$SIMULATOR_ID" ]; then
SIMULATOR_STATE=$(xcrun simctl list devices | grep "$SIMULATOR_ID" | grep -o "(Booted\|Shutdown)" | head -1)
if [ "$SIMULATOR_STATE" != "Booted" ]; then
log_step "Booting simulator ($SIMULATOR_NAME)..."
xcrun simctl boot "$SIMULATOR_ID" 2>/dev/null || log_warn "Simulator may already be booting"
# Open Simulator app if not already open
if ! pgrep -x "Simulator" > /dev/null; then
log_step "Opening Simulator app..."
open -a Simulator
fi
# Wait for simulator to fully boot (up to 60 seconds)
log_step "Waiting for simulator to boot (this may take up to 60 seconds)..."
BOOT_TIMEOUT=60
ELAPSED=0
CURRENT_STATE="Shutdown"
while [ $ELAPSED -lt $BOOT_TIMEOUT ]; do
CURRENT_STATE=$(xcrun simctl list devices | grep "$SIMULATOR_ID" | grep -o "(Booted\|Shutdown)" | head -1)
if [ "$CURRENT_STATE" = "Booted" ]; then
log_info "Simulator booted successfully (took ${ELAPSED}s)"
# Give it a few more seconds to fully initialize
sleep 3
break
fi
if [ $((ELAPSED % 5)) -eq 0 ] && [ $ELAPSED -gt 0 ]; then
log_info "Still waiting... (${ELAPSED}s elapsed)"
fi
sleep 1
ELAPSED=$((ELAPSED + 1))
done
if [ "$CURRENT_STATE" != "Booted" ]; then
log_warn "Simulator may not have finished booting (waited ${ELAPSED}s)"
log_warn "You may need to manually boot the simulator and try again"
else
# Verify simulator is actually ready (not just booted)
log_info "Verifying simulator is ready..."
READY_ATTEMPTS=0
MAX_READY_ATTEMPTS=10
while [ $READY_ATTEMPTS -lt $MAX_READY_ATTEMPTS ]; do
# Try a simple command to verify simulator is responsive
if xcrun simctl list devices | grep "$SIMULATOR_ID" | grep -q "Booted"; then
# Try to get device info to verify it's responsive
if xcrun simctl get_app_container "$SIMULATOR_ID" com.apple.Preferences >/dev/null 2>&1; then
log_info "Simulator is ready"
break
fi
fi
sleep 1
READY_ATTEMPTS=$((READY_ATTEMPTS + 1))
done
if [ $READY_ATTEMPTS -eq $MAX_READY_ATTEMPTS ]; then
log_warn "Simulator may not be fully ready yet"
fi
fi
else
log_info "Simulator already booted"
# Verify it's actually ready
if ! xcrun simctl get_app_container "$SIMULATOR_ID" com.apple.Preferences >/dev/null 2>&1; then
log_warn "Simulator is booted but may not be fully ready"
log_info "Waiting a few seconds for simulator to be ready..."
sleep 5
fi
fi
# Install the app
log_step "Installing app on simulator..."
if xcrun simctl install "$SIMULATOR_ID" "$APP_PATH" 2>&1; then
log_info "App installed successfully"
else
log_warn "Install may have failed (app may already be installed)"
fi
# Wait a moment for install to complete
sleep 1
# Launch the app (try multiple methods)
log_step "Launching app..."
LAUNCH_SUCCESS=false
LAUNCH_ERROR=""
# Wait a moment for simulator to be fully ready
sleep 2
# Method 1: Direct launch (capture output to check for errors)
# Note: Bundle ID is com.timesafari.dailynotification (not .test)
log_info "Attempting to launch app..."
LAUNCH_OUTPUT=$(xcrun simctl launch "$SIMULATOR_ID" com.timesafari.dailynotification 2>&1)
LAUNCH_EXIT_CODE=$?
if [ $LAUNCH_EXIT_CODE -eq 0 ]; then
# Check if output contains process ID (successful launch)
# Format can be either "PID" or "bundle: PID"
if echo "$LAUNCH_OUTPUT" | grep -qE "^[0-9]+$|^[^:]+: [0-9]+$"; then
LAUNCH_SUCCESS=true
# Extract PID (either standalone number or after colon)
APP_PID=$(echo "$LAUNCH_OUTPUT" | sed -E 's/^[^:]*:? *([0-9]+).*/\1/' | head -1)
log_info "✅ App launched successfully! (PID: $APP_PID)"
else
# Launch command succeeded but may not have actually launched
log_warn "Launch command returned success but output unexpected: $LAUNCH_OUTPUT"
fi
else
# Capture error message
LAUNCH_ERROR="$LAUNCH_OUTPUT"
log_warn "Launch failed: $LAUNCH_ERROR"
fi
# Method 2: Verify app is actually running
if [ "$LAUNCH_SUCCESS" = false ]; then
log_info "Checking if app is already running..."
sleep 2
RUNNING_APPS=$(xcrun simctl listapps "$SIMULATOR_ID" 2>/dev/null | grep -A 5 "com.timesafari.dailynotification" || echo "")
if [ -n "$RUNNING_APPS" ]; then
log_info "App appears to be installed. Trying to verify it's running..."
# Try to get app state
APP_STATE=$(xcrun simctl listapps "$SIMULATOR_ID" 2>/dev/null | grep -A 10 "com.timesafari.dailynotification" | grep "ApplicationType" || echo "")
if [ -n "$APP_STATE" ]; then
log_info "App found in simulator. Attempting manual launch..."
# Try opening via Simulator app
open -a Simulator
sleep 1
# Try launch one more time
if xcrun simctl launch "$SIMULATOR_ID" com.timesafari.dailynotification >/dev/null 2>&1; then
LAUNCH_SUCCESS=true
log_info "✅ App launched successfully on retry!"
fi
fi
fi
fi
# Final verification: check if app process is running
if [ "$LAUNCH_SUCCESS" = true ]; then
sleep 2
# Try to verify app is running by checking if we can get its container
if xcrun simctl get_app_container "$SIMULATOR_ID" com.timesafari.dailynotification >/dev/null 2>&1; then
log_info "✅ Verified: App is installed and accessible"
else
log_warn "⚠️ Launch reported success but app verification failed"
log_warn " The app may still be starting. Check the Simulator."
fi
else
log_warn "❌ Automatic launch failed"
log_info ""
log_info "The app is installed. To launch manually:"
log_info " 1. Open Simulator app (if not already open)"
log_info " 2. Find the app icon on the home screen and tap it"
log_info " 3. Or run: xcrun simctl launch $SIMULATOR_ID com.timesafari.dailynotification"
if [ -n "$LAUNCH_ERROR" ]; then
log_info ""
log_info "Launch error details:"
log_info " $LAUNCH_ERROR"
fi
fi
log_info ""
log_info "✅ Build and deployment complete!"
else
log_info ""
log_info "To run on simulator manually:"
log_info " xcrun simctl install booted \"$APP_PATH\""
log_info " xcrun simctl launch booted com.timesafari.dailynotification"
fi
else
log_warn "Could not find built app in DerivedData"
log_info "App was built successfully, but path detection failed."
log_info "You can find it in Xcode's DerivedData folder or run from Xcode directly."
fi
else
log_info ""
log_info "To install on device:"
log_info " Open App.xcworkspace in Xcode"
log_info " Select your device"
log_info " Press Cmd+R to build and run"
fi
cd - > /dev/null
}
# Main execution
main() {
log_info "iOS Test App Build Script"
log_info "Target: $TARGET | Configuration: $BUILD_CONFIG"
log_info ""
check_environment
# Get absolute path to repo root
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
REPO_ROOT="$( cd "$SCRIPT_DIR/.." && pwd )"
cd "$REPO_ROOT"
build_ios_test_app
log_info ""
log_info "✅ Build complete!"
}
main "$@"