Files
daily-notification-plugin/scripts/build-ios-test-app.sh
Jose Olarte III d8a0eaf413 refactor(android,ios): rename package com.timesafari to org.timesafari.dailynotification
- Android: move plugin source to org/timesafari/dailynotification, update
  namespace, manifest package, and all package/imports; change intent actions
  to org.timesafari.daily.NOTIFICATION and DISMISS
- iOS: update bundle IDs, BGTask identifiers, subsystem labels, and queue
  names in Plugin and Xcode projects
- Capacitor: update plugin class registration and appIds in configs
- Test apps (android-test-app, daily-notification-test, ios-test-app):
  applicationId/bundleId, manifests, ProGuard, scripts, and docs
- Docs: bulk update references; add CONSUMING_APP_MIGRATION_COM_TO_ORG.md
  for consuming app migration

BREAKING CHANGE: Consuming apps must update plugin class to
org.timesafari.dailynotification.DailyNotificationPlugin, manifest
receivers/actions, and iOS BGTask identifiers per migration doc.
2026-03-12 14:26:07 +08:00

576 lines
23 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..."
# Get repo root before changing directories (we're currently in repo root from main())
REPO_ROOT="$(pwd)"
# 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
# Copy canonical UI from www/index.html to test app
log_step "Copying canonical UI from www/index.html..."
# Use REPO_ROOT calculated before changing directories
CANONICAL_UI="$REPO_ROOT/www/index.html"
IOS_UI_SOURCE="$REPO_ROOT/$TEST_APP_DIR/App/App/Public/index.html"
IOS_UI_RUNTIME="$REPO_ROOT/$TEST_APP_DIR/ios/App/App/public/index.html"
if [ -f "$CANONICAL_UI" ]; then
# Copy to source location (for Capacitor sync)
if cp "$CANONICAL_UI" "$IOS_UI_SOURCE"; then
log_info "Copied canonical UI to iOS test app source"
else
log_error "Failed to copy canonical UI to source"
exit 1
fi
# Also copy directly to runtime location (in case sync doesn't run or is cached)
if [ -d "$(dirname "$IOS_UI_RUNTIME")" ]; then
if cp "$CANONICAL_UI" "$IOS_UI_RUNTIME"; then
log_info "Copied canonical UI to iOS test app runtime"
else
log_warn "Failed to copy canonical UI to runtime (may be synced later)"
fi
else
log_warn "Runtime directory not found, will be created by Capacitor sync"
fi
else
log_warn "Canonical UI not found at $CANONICAL_UI, skipping copy"
fi
# Sync Capacitor from test app root (where capacitor.config.json is located)
# This must run from test-apps/ios-test-app, not from ios/App/App
log_step "Syncing Capacitor..."
TEST_APP_ROOT="$REPO_ROOT/$TEST_APP_DIR"
if [ -f "$TEST_APP_ROOT/capacitor.config.json" ] || [ -f "$TEST_APP_ROOT/capacitor.config.ts" ]; then
if command -v npx &> /dev/null; then
# Save current directory
CURRENT_DIR="$(pwd)"
# Change to test app root for sync
cd "$TEST_APP_ROOT" || exit 1
if ! npx cap sync ios; then
log_error "Capacitor sync failed"
cd "$CURRENT_DIR" || exit 1
exit 1
fi
# Return to ios/App directory
cd "$CURRENT_DIR" || exit 1
log_info "Capacitor synced"
else
log_warn "npx not found, skipping Capacitor sync"
fi
else
log_warn "Capacitor config not found at $TEST_APP_ROOT, skipping sync"
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
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
# Ensure UI is copied to runtime location one more time before build
# (in case sync didn't run or was cached)
log_step "Ensuring canonical UI is in runtime location before build..."
if [ -f "$CANONICAL_UI" ] && [ -d "$(dirname "$IOS_UI_RUNTIME")" ]; then
if cp "$CANONICAL_UI" "$IOS_UI_RUNTIME"; then
log_info "Canonical UI copied to runtime location (pre-build)"
fi
fi
# Clean build folder (removes old DerivedData)
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
# Also clean DerivedData for this specific project to remove cached HTML
log_step "Cleaning DerivedData for fresh build..."
DERIVED_DATA_PATH="$HOME/Library/Developer/Xcode/DerivedData"
if [ -d "$DERIVED_DATA_PATH" ]; then
# Find and remove DerivedData folders for this project
find "$DERIVED_DATA_PATH" -maxdepth 1 -type d -name "App-*" -exec rm -rf {} \; 2>/dev/null || true
log_info "DerivedData cleaned"
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"
# Force copy canonical UI directly into built app bundle (ensures latest version)
log_step "Copying canonical UI into built app bundle..."
BUILT_APP_HTML="$APP_PATH/public/index.html"
if [ -f "$CANONICAL_UI" ] && [ -d "$(dirname "$BUILT_APP_HTML")" ]; then
if cp "$CANONICAL_UI" "$BUILT_APP_HTML"; then
log_info "✅ Canonical UI copied directly into app bundle"
else
log_warn "Failed to copy UI into app bundle (may use cached version)"
fi
fi
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
# Uninstall existing app (if present) to ensure clean install
log_step "Uninstalling existing app (if present)..."
APP_BUNDLE_ID="org.timesafari.dailynotification.test"
if xcrun simctl uninstall "$SIMULATOR_ID" "$APP_BUNDLE_ID" 2>&1; then
log_info "Existing app uninstalled"
else
# App may not be installed, which is fine
log_info "No existing app to uninstall (or uninstall failed - continuing anyway)"
fi
# Wait a moment after uninstall
sleep 1
# 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 org.timesafari.dailynotification.test
log_info "Attempting to launch app..."
LAUNCH_OUTPUT=$(xcrun simctl launch "$SIMULATOR_ID" "$APP_BUNDLE_ID" 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 "$APP_BUNDLE_ID" || 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 "$APP_BUNDLE_ID" | 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" "$APP_BUNDLE_ID" >/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" "$APP_BUNDLE_ID" >/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 $APP_BUNDLE_ID"
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 org.timesafari.dailynotification.test"
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 "$@"