Something went wrong. Try again.
experiments in a post-browser web
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358#!/bin/bash# Start BOTH iOS simulator and Android emulator with HMR.# Usage: ./dev-both.sh## Architecture:# 1. iOS: custom build pipeline + vite HMR server on port 5188# 2. Android: `tauri android dev` (runs its own vite on port 1420, handles build/install/launch)# 3. Ctrl+C cleans up everything
set -eSCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"CONF="$SCRIPT_DIR/src-tauri/tauri.conf.json"TAURI_DIR="$SCRIPT_DIR/src-tauri"PORT=5188ADB=""VITE_PID=""EMULATOR_PID=""TAURI_ANDROID_PID=""IOS_OK=falseANDROID_OK=false
ts() { date "+%H:%M:%S"}
log() { echo "[$(ts)] $1"}
# ── Environment setup ───────────────────────────────────────────────
# Kill any stale vite on our portlsof -ti tcp:$PORT | xargs kill 2>/dev/null || true
# Get host IP (simulator/emulator both need this)HOST_IP=$(ipconfig getifaddr en0 || ipconfig getifaddr en1 || echo "127.0.0.1")log "Host IP: $HOST_IP"log "HMR port: $PORT"
# Android envif [ -z "$ANDROID_HOME" ]; then export ANDROID_HOME="$HOME/Library/Android/sdk"fiif [ -z "$JAVA_HOME" ]; then if [ -d "/Applications/Android Studio.app/Contents/jbr/Contents/Home" ]; then export JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home" fifiif [ -z "$NDK_HOME" ]; then NDK_VERSION=$(ls "$ANDROID_HOME/ndk/" 2>/dev/null | sort -V | tail -1) if [ -n "$NDK_VERSION" ]; then export NDK_HOME="$ANDROID_HOME/ndk/$NDK_VERSION" fifi
if [ -d "$ANDROID_HOME/platform-tools" ]; then ADB="$ANDROID_HOME/platform-tools/adb"fi
# ── Backup and cleanup ─────────────────────────────────────────────
cp "$CONF" "$CONF.bak"
cleanup() { echo "" log "Cleaning up..."
if [ -n "$VITE_PID" ] && kill -0 "$VITE_PID" 2>/dev/null; then log "Stopping vite server..." kill "$VITE_PID" 2>/dev/null wait "$VITE_PID" 2>/dev/null || true fi
if [ -n "$TAURI_ANDROID_PID" ] && kill -0 "$TAURI_ANDROID_PID" 2>/dev/null; then log "Stopping tauri android dev..." kill "$TAURI_ANDROID_PID" 2>/dev/null wait "$TAURI_ANDROID_PID" 2>/dev/null || true fi
log "Restoring tauri.conf.json..." if [ -f "$CONF.bak" ]; then mv "$CONF.bak" "$CONF" fi
log "Done."}trap cleanup EXIT
# ── Patch tauri.conf.json ───────────────────────────────────────────
python3 -c "import jsonwith open('$CONF') as f: c = json.load(f)c['build']['devUrl'] = 'http://$HOST_IP:$PORT'c['build']['beforeBuildCommand'] = ''with open('$CONF', 'w') as f: json.dump(c, f, indent=2) f.write('\n')"log "Patched tauri.conf.json: devUrl=http://$HOST_IP:$PORT, beforeBuildCommand disabled"
# ── Bump build number (once for both) ──────────────────────────────
# Auto-create if missing — fresh clones don't have this fileBUILD_FILE="$SCRIPT_DIR/BUILD_NUMBER"if [ ! -f "$BUILD_FILE" ]; then echo "0" > "$BUILD_FILE"fiBUILD_NUM=$(($(cat "$BUILD_FILE") + 1))echo "$BUILD_NUM" > "$BUILD_FILE"log "Build number: v$BUILD_NUM"
# ── Boot iOS simulator ────────────────────────────────────────────
boot_ios_simulator() { # Check if any simulator is already booted if xcrun simctl list devices booted 2>/dev/null | grep -q "Booted"; then log "[ios] Simulator already booted" return 0 fi
# Find an available iPhone simulator SIM_UDID=$(xcrun simctl list devices available -j 2>/dev/null | python3 -c "import json, sysdata = json.load(sys.stdin)for runtime, devices in data.get('devices', {}).items(): if 'iOS' not in runtime: continue for d in devices: if d.get('isAvailable') and 'iPhone' in d.get('name', ''): print(d['udid']) sys.exit(0)" 2>/dev/null || true)
if [ -z "$SIM_UDID" ]; then log "[ios] ERROR: No available iPhone simulator found" return 1 fi
SIM_NAME=$(xcrun simctl list devices available -j 2>/dev/null | python3 -c "import json, sysdata = json.load(sys.stdin)for runtime, devices in data.get('devices', {}).items(): for d in devices: if d.get('udid') == '$SIM_UDID': print(d['name']) sys.exit(0)" 2>/dev/null || true)
log "[ios] Booting simulator: $SIM_NAME ($SIM_UDID)..." xcrun simctl boot "$SIM_UDID" 2>/dev/null || true open -a Simulator log "[ios] Simulator booted"}
# ── Start Android emulator if needed ────────────────────────────────
start_android_emulator() { if [ -z "$ADB" ] || [ ! -x "$ADB" ]; then log "[android] ERROR: adb not found at $ADB" return 1 fi
DEVICES=$("$ADB" devices 2>/dev/null | grep -c 'device$' || true) if [ "$DEVICES" -eq 0 ]; then log "[android] No Android devices/emulators found, starting emulator..." EMULATOR="$ANDROID_HOME/emulator/emulator" if [ ! -x "$EMULATOR" ]; then log "[android] ERROR: emulator binary not found" return 1 fi AVD=$("$EMULATOR" -list-avds 2>/dev/null | head -1) if [ -z "$AVD" ]; then log "[android] ERROR: No AVDs found. Create one in Android Studio." return 1 fi log "[android] Starting emulator: $AVD" "$EMULATOR" -avd "$AVD" -no-snapshot-load & EMULATOR_PID=$! log "[android] Waiting for emulator to boot..." "$ADB" wait-for-device while [ "$("$ADB" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')" != "1" ]; do sleep 1 done log "[android] Emulator ready" else log "[android] Emulator/device already running ($DEVICES devices)" fi return 0}
# Boot iOS simulator synchronously (fast if already booted)boot_ios_simulator
# Start Android emulator in background (slow to boot)start_android_emulator &EMULATOR_BOOT_PID=$!
# ── Build iOS ───────────────────────────────────────────────────────
build_ios() { log "[ios] Building Rust library with devUrl..." cd "$SCRIPT_DIR" DEV_PORT=$PORT ./build-ios.sh --force
log "[ios] Running xcodebuild..." rm -rf /tmp/peek-xcodebuild-sim cd "$TAURI_DIR/gen/apple" xcodebuild \ -scheme peek-save_iOS \ -configuration Debug \ -sdk iphonesimulator \ -derivedDataPath /tmp/peek-xcodebuild-sim \ -destination 'generic/platform=iOS Simulator' \ clean build 2>&1 | tail -5
log "[ios] Installing to simulator..." xcrun simctl install booted '/tmp/peek-xcodebuild-sim/Build/Products/Debug-iphonesimulator/Peek Save.app' IOS_OK=true log "[ios] Build complete"}
# ── Build Android ───────────────────────────────────────────────────
build_android() { # Validate Android env if [ ! -d "$ANDROID_HOME" ]; then log "[android] ERROR: Android SDK not found at $ANDROID_HOME" return 1 fi if [ -z "$JAVA_HOME" ] || [ ! -d "$JAVA_HOME" ]; then log "[android] ERROR: JAVA_HOME not found" return 1 fi
log "[android] ANDROID_HOME=$ANDROID_HOME" log "[android] JAVA_HOME=$JAVA_HOME" log "[android] NDK_HOME=$NDK_HOME"
# Ensure local.properties exists if [ ! -f "$TAURI_DIR/gen/android/local.properties" ]; then mkdir -p "$TAURI_DIR/gen/android" echo "sdk.dir=$ANDROID_HOME" > "$TAURI_DIR/gen/android/local.properties" log "[android] Created local.properties" fi
# Wait for emulator boot to finish wait $EMULATOR_BOOT_PID 2>/dev/null || true
# Restore original tauri.conf.json — `tauri android dev` needs an unpatched # config so it starts its own vite and sets devUrl itself. # iOS build is already complete by this point, so it's safe to restore. log "[android] Restoring tauri.conf.json for tauri android dev..." cp "$CONF.bak" "$CONF"
# Resolve tauri CLI explicitly — avoids npx resolution failure on fresh clones # and system cargo-tauri version mismatch panic. TAURI_BIN="$SCRIPT_DIR/node_modules/.bin/tauri" if [ ! -x "$TAURI_BIN" ]; then TAURI_BIN="$SCRIPT_DIR/../../node_modules/.bin/tauri" fi if [ ! -x "$TAURI_BIN" ]; then log "[android] ERROR: tauri CLI not found. Run 'yarn install' at repo root first." return 1 fi
# Use `tauri android dev` which handles everything: # - Starts its own Vite server (on default port 1420) # - Sets up adb reverse port forwarding # - Builds Rust + APK # - Installs and launches on emulator # Runs in background so we can also serve iOS. log "[android] Starting tauri android dev (runs its own vite + builds + installs)..." cd "$SCRIPT_DIR" "$TAURI_BIN" android dev & TAURI_ANDROID_PID=$!
ANDROID_OK=true log "[android] tauri android dev started (PID $TAURI_ANDROID_PID)"}
# ── Run builds ──────────────────────────────────────────────────────# iOS uses our custom build pipeline; Android uses tauri android dev# which handles its own vite + build + install + launch.
log "Starting builds..."
# Build iOS firstbuild_ios || log "[ios] BUILD FAILED (continuing with Android)"
# Build Android secondbuild_android || log "[android] BUILD FAILED (continuing)"
if [ "$IOS_OK" = false ] && [ "$ANDROID_OK" = false ]; then log "ERROR: Both builds failed. Exiting." exit 1fi
# ── Start vite HMR server ──────────────────────────────────────────
log "Starting HMR dev server on 0.0.0.0:$PORT..."cd "$SCRIPT_DIR"npx vite --host 0.0.0.0 --port $PORT &VITE_PID=$!
# Wait for vite to be readylog "Waiting for vite server..."for i in $(seq 1 30); do if curl -s -o /dev/null "http://localhost:$PORT/" 2>/dev/null; then log "Vite server ready" break fi if [ "$i" -eq 30 ]; then log "ERROR: Vite server did not start within 15 seconds" exit 1 fi sleep 0.5done
# ── Launch iOS app ──────────────────────────────────────────────────# Android launch is handled by `tauri android dev`
if [ "$IOS_OK" = true ]; then log "[ios] Launching app..." xcrun simctl terminate booted com.dietrich.peek-mobile 2>/dev/null || true if xcrun simctl launch booted com.dietrich.peek-mobile; then log "[ios] App launched" else log "[ios] WARNING: Failed to launch app (is simulator booted?)" fifi
# ── Summary ─────────────────────────────────────────────────────────
echo ""log "================================================"log " Both platforms running with HMR"log " iOS Vite: http://0.0.0.0:$PORT"log " Android Vite: managed by tauri android dev"log " Build: v$BUILD_NUM"if [ "$IOS_OK" = true ]; then log " iOS simulator: RUNNING"else log " iOS simulator: FAILED"fiif [ "$ANDROID_OK" = true ]; then log " Android emulator: RUNNING"else log " Android emulator: FAILED"filog " Press Ctrl+C to stop"log "================================================"echo ""
# Block on both background processes (Ctrl+C kills everything via trap)wait $VITE_PID $TAURI_ANDROID_PID 2>/dev/null