chahinebrini 138e45fe0a feat(dev): add 'magic' subcommand to dev.sh for RebreakMagic macOS app
./dev.sh magic            - xcodegen + xcodebuild Release + relaunch
./dev.sh magic --debug    - Debug-Config
./dev.sh magic --xcode    - Only generate project + open Xcode
./dev.sh magic --no-build - Skip build, just relaunch
./dev.sh magic --no-launch - Build only

Auto-checks for ~/.config/rebreak-magic/config.json and points to template
if missing. Pre-kills any running RebreakMagic instance before launch.
2026-06-02 11:07:48 +02:00

569 lines
19 KiB
Bash
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# dev.sh — ReBreak Native Development Tooling
#
# Konsolidiert: dev-ios.sh + dev-iphone.sh + metro.sh (alle gelöscht).
#
# SUBCOMMANDS:
# ./dev.sh default: ios --device (physisches iPhone USB + Build)
# ./dev.sh ios iOS Dev (Default: USB-Device mit Build)
# ./dev.sh android Android Dev (Gradle Build + Install + Launch)
# ./dev.sh metro Nur Metro starten
# ./dev.sh clean iOS: Nuclear clean (Pods, DerivedData, Archives)
# ./dev.sh install ios Build Release + Install auf iPhone USB
# ./dev.sh install android Build Debug APK + Install auf Android Device
# ./dev.sh magic Build + Launch RebreakMagic.app (macOS Wizard)
#
# FLAGS (ios):
# --device Build auf physisches iPhone via USB (DEFAULT)
# --simulator Build auf iOS Simulator
# --xcode Nur Xcode öffnen (manueller Build)
# --wifi Metro mit --host lan (WiFi-Dev, KEIN Native-Build)
# --no-build KEIN Native-Rebuild → nur Metro starten
# (für schnellen UI/JS-Reload — App muss installiert sein)
#
# FLAGS (android):
# --no-build KEIN Gradle-Rebuild → nur Metro starten
# (für schnellen UI/JS-Reload — APK muss installiert sein)
# --no-launch Build+Install, aber kein Auto-Launch
# --wifi Metro mit --host lan (nur in Kombi mit --no-build)
#
# FLAGS (metro):
# --keep Cache behalten (kein --clear)
#
# FLAGS (clean):
# --build + iOS build am Ende
# --xcode + Xcode öffnen am Ende
#
# FLAGS (magic):
# --no-build Nur launchen (App muss schon gebaut sein)
# --xcode Nur Xcode-Projekt generieren + öffnen
# --debug Debug-Konfiguration statt Release
# --no-launch Build, aber App nicht öffnen
#
# BEISPIELE:
# # Default: iPhone USB + Native-Build:
# ./dev.sh
#
# # Schneller UI-Loop (App schon installiert, nur JS-Reload):
# ./dev.sh ios --no-build
# ./dev.sh android --no-build
#
# # WiFi-Dev (Kabel ab, Metro über LAN):
# ./dev.sh ios --wifi
#
# # iOS Simulator:
# ./dev.sh ios --simulator
#
# # Nur Xcode öffnen:
# ./dev.sh ios --xcode
#
# # iOS Clean + Rebuild:
# ./dev.sh clean --build
#
# # Release-Build auf iPhone installieren:
# ./dev.sh install ios
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
IOS_DIR="$SCRIPT_DIR/ios"
ANDROID_DIR="$SCRIPT_DIR/android"
MAGIC_DIR="$SCRIPT_DIR/../rebreak-magic-mac"
# ═══════════════════════════════════════════════════════════════════════════
# Color Output
# ═══════════════════════════════════════════════════════════════════════════
if [[ -t 1 ]]; then
BOLD=$(tput bold)
GREEN=$(tput setaf 2)
YELLOW=$(tput setaf 3)
RED=$(tput setaf 1)
BLUE=$(tput setaf 4)
RESET=$(tput sgr0)
else
BOLD="" GREEN="" YELLOW="" RED="" BLUE="" RESET=""
fi
log() { echo "${BLUE}==>${RESET} ${BOLD}$*${RESET}"; }
ok() { echo "${GREEN}${RESET} $*"; }
warn() { echo "${YELLOW}${RESET} $*" >&2; }
error() { echo "${RED}${RESET} ${BOLD}$*${RESET}" >&2; }
die() { error "$*"; exit 1; }
section() {
echo ""
echo "${BOLD}$*${RESET}"
echo "${BOLD}────────────────────────────────────────────────────────────${RESET}"
}
# ═══════════════════════════════════════════════════════════════════════════
# ENV Defaults
# ═══════════════════════════════════════════════════════════════════════════
export REBREAK_ENABLE_FAMILY_CONTROLS="${REBREAK_ENABLE_FAMILY_CONTROLS:-1}"
export EXPO_PUBLIC_ENABLE_DEBUG="${EXPO_PUBLIC_ENABLE_DEBUG:-1}"
export REBREAK_DEV="${REBREAK_DEV:-0}"
# ═══════════════════════════════════════════════════════════════════════════
# Commands
# ═══════════════════════════════════════════════════════════════════════════
cmd_ios() {
local MODE="device" # Default: physisches iPhone via USB
local WIFI=false
local BUILD=true # Default: nativen Build laufen lassen
while [[ $# -gt 0 ]]; do
case "$1" in
--device) MODE="device"; shift ;;
--simulator) MODE="simulator"; shift ;;
--xcode) MODE="xcode"; shift ;;
--wifi) WIFI=true; shift ;;
--no-build) BUILD=false; shift ;;
*) die "Unbekannter Flag für 'ios': $1" ;;
esac
done
section "iOS Dev Mode"
# ─────────────────────────────────────────────────────────────
# --no-build / WiFi: KEIN Native-Rebuild, nur Metro + Dev-Client
# → schnellster Loop für reine UI/JS-Änderungen
# → App muss schon auf dem Device/Simulator installiert sein
# ─────────────────────────────────────────────────────────────
if ! $BUILD || $WIFI; then
local HOST_FLAG=""
if $WIFI; then
HOST_FLAG="--host lan"
log "Metro: WiFi-Modus (--host lan)"
echo ""
echo "Mac LAN-IP:"
ipconfig getifaddr en0 2>/dev/null || ipconfig getifaddr en1 2>/dev/null || echo " (kein WiFi/Ethernet detected)"
echo ""
echo "Falls dev-client Metro nicht automatisch findet:"
echo " im iPhone-Launcher → 'Enter URL manually' → http://<LAN-IP>:8081"
else
log "Metro: USB/Local-Modus (kein Native-Rebuild)"
echo "App auf Device/Simulator muss schon installiert sein."
echo "Beim Öffnen connected dev-client automatisch zu Metro."
fi
echo ""
log "Killing old Metro on port 8081..."
lsof -ti:8081 2>/dev/null | xargs kill -9 2>/dev/null || true
pkill -f "expo start" 2>/dev/null || true
echo ""
exec pnpm expo start $HOST_FLAG --clear --dev-client
fi
case "$MODE" in
xcode)
log "Opening Xcode Workspace..."
osascript -e 'tell application "Xcode" to close every window whose name contains "Rebreak.xcodeproj"' 2>/dev/null || true
open -a Xcode "$IOS_DIR/ReBreak.xcworkspace"
ok "Xcode geöffnet — Cmd+R für Build & Run"
echo ""
echo " Metro: separat starten via './dev.sh metro' falls noch nicht läuft"
;;
device)
log "Building für physisches iPhone (USB)..."
echo " Für schnellen UI-Reload ohne Rebuild: './dev.sh ios --no-build'"
pnpm expo run:ios --device
;;
simulator)
log "Building für iOS Simulator..."
pnpm expo run:ios
;;
esac
}
cmd_android() {
local BUILD=true
local LAUNCH=true
local WIFI=false
while [[ $# -gt 0 ]]; do
case "$1" in
--no-build) BUILD=false; shift ;;
--no-launch) LAUNCH=false; shift ;;
--wifi) WIFI=true; shift ;;
*) die "Unbekannter Flag für 'android': $1" ;;
esac
done
section "Android Dev Mode"
# ─────────────────────────────────────────────────────────────
# --no-build: KEIN Gradle-Rebuild, nur Metro + Dev-Client
# → schnellster Loop für reine UI/JS-Änderungen
# → APK muss schon auf dem Device installiert sein
# ─────────────────────────────────────────────────────────────
if ! $BUILD; then
local HOST_FLAG=""
if $WIFI; then
HOST_FLAG="--host lan"
log "Metro: WiFi-Modus (--host lan)"
echo ""
echo "Mac LAN-IP:"
ipconfig getifaddr en0 2>/dev/null || ipconfig getifaddr en1 2>/dev/null || echo " (kein WiFi/Ethernet detected)"
else
log "Metro: USB/ADB-Modus (kein Gradle-Rebuild)"
echo "APK muss schon auf dem Device installiert sein."
fi
echo ""
log "Killing old Metro on port 8081..."
lsof -ti:8081 2>/dev/null | xargs kill -9 2>/dev/null || true
pkill -f "expo start" 2>/dev/null || true
echo ""
exec pnpm expo start $HOST_FLAG --clear --dev-client
fi
command -v adb >/dev/null 2>&1 || die "adb nicht gefunden — brew install --cask android-platform-tools"
local DEVICE_COUNT
DEVICE_COUNT=$(adb devices | grep -E '[[:space:]]device$' | grep -c '.' || true)
if [[ "$DEVICE_COUNT" -eq 0 ]]; then
warn "Kein Android-Gerät via ADB verbunden"
echo ""
adb devices
echo ""
echo "Mögliche Ursachen:"
echo " - USB nicht angeschlossen / Kabel nur Strom"
echo " - USB-Debugging auf dem Phone aus"
echo " - 'Diesem Computer vertrauen?' Dialog noch nicht bestätigt"
exit 1
fi
log "Building Debug APK..."
echo " Für schnellen UI-Reload ohne Rebuild: './dev.sh android --no-build'"
(cd "$ANDROID_DIR" && ./gradlew assembleDebug --console=plain)
local APK="$ANDROID_DIR/app/build/outputs/apk/debug/app-debug.apk"
[[ -f "$APK" ]] || die "APK nicht gefunden: $APK"
log "Installing APK..."
adb install -r -d "$APK"
if $LAUNCH; then
log "Launching App..."
adb shell monkey -p org.rebreak.app -c android.intent.category.LAUNCHER 1 >/dev/null 2>&1 || {
warn "Launch via monkey schlug fehl — App ist installiert, manuell öffnen"
}
fi
ok "Android Dev Build abgeschlossen"
}
cmd_metro() {
local CLEAR_FLAG="--clear"
while [[ $# -gt 0 ]]; do
case "$1" in
--keep) CLEAR_FLAG=""; shift ;;
*) die "Unbekannter Flag für 'metro': $1" ;;
esac
done
section "Metro Bundler"
log "Killing existing Metro on port 8081..."
lsof -iTCP:8081 -sTCP:LISTEN -n -P 2>/dev/null | awk 'NR>1 {print $2}' | sort -u | xargs kill -9 2>/dev/null || true
pkill -f "expo start" 2>/dev/null || true
pkill -f "react-native/cli/build" 2>/dev/null || true
if [[ -n "$CLEAR_FLAG" ]]; then
log "Starting Metro mit --clear (Cache reset)..."
else
log "Starting Metro (Cache behalten)..."
fi
exec npx expo start $CLEAR_FLAG
}
cmd_clean() {
local POST_ACTION=""
while [[ $# -gt 0 ]]; do
case "$1" in
--build) POST_ACTION="build"; shift ;;
--xcode) POST_ACTION="xcode"; shift ;;
*) die "Unbekannter Flag für 'clean': $1" ;;
esac
done
section "iOS Nuclear Clean"
log "rm -rf ios/Pods ios/Podfile.lock ios/build"
rm -rf "$IOS_DIR/Pods" "$IOS_DIR/Podfile.lock" "$IOS_DIR/build"
local DERIVED_DATA="$HOME/Library/Developer/Xcode/DerivedData"
if [[ -d "$DERIVED_DATA" ]]; then
log "rm -rf DerivedData/Rebreak-*"
rm -rf "$DERIVED_DATA"/Rebreak-* 2>/dev/null || true
fi
local ARCHIVES_DIR="$HOME/Library/Developer/Xcode/Archives"
if [[ -d "$ARCHIVES_DIR" ]]; then
local before_count
before_count=$(find "$ARCHIVES_DIR" -maxdepth 2 -type d -iname "*rebreak*.xcarchive" 2>/dev/null | wc -l | tr -d ' ')
log "Cleaning Xcode Archives (Rebreak, >24h) — vorher: $before_count Stk"
find "$ARCHIVES_DIR" -maxdepth 2 -type d -iname "*rebreak*.xcarchive" -mtime +1 -exec rm -rf {} + 2>/dev/null || true
find "$ARCHIVES_DIR" -maxdepth 1 -type d -empty -delete 2>/dev/null || true
fi
log "pnpm expo prebuild --clean"
pnpm expo prebuild --clean
log "cd ios && pod install"
(cd "$IOS_DIR" && pod install)
ok "Clean abgeschlossen"
case "$POST_ACTION" in
build)
echo ""
log "Building + Running..."
pnpm ios
;;
xcode)
echo ""
log "Opening Xcode..."
osascript -e 'tell application "Xcode" to close every window whose name contains "Rebreak.xcodeproj"' 2>/dev/null || true
open -a Xcode "$IOS_DIR/ReBreak.xcworkspace"
ok "Xcode geöffnet — Cmd+R für Build & Run"
;;
"")
echo ""
echo "Nächste Schritte:"
echo " ./dev.sh ios # Build & Run"
echo " ./dev.sh ios --xcode # Xcode öffnen"
;;
esac
}
cmd_install_ios() {
section "iOS Standalone Install"
local CONFIGURATION="Release"
command -v xcrun >/dev/null 2>&1 || die "Xcode Command-Line-Tools fehlen — xcode-select --install"
local XCTRACE_OUT
XCTRACE_OUT=$(xcrun xctrace list devices 2>&1)
local ONLINE
ONLINE=$(printf '%s\n' "$XCTRACE_OUT" \
| awk '/^== Devices ==/{f=1; next} /^== /{f=0} f' \
| grep -E "iPhone|iPad" || true)
if [[ -z "$ONLINE" ]]; then
error "Kein iPhone/iPad ONLINE"
echo ""
echo "Setup:"
echo " - iPhone via USB anschließen + entsperren"
echo " - 'Diesem Computer vertrauen?' am iPhone bestätigen"
echo " - Xcode öffnen, dort 'Use for Development' aktivieren"
exit 1
fi
log "Gerät online: $(printf '%s\n' "$ONLINE" | head -1)"
log "Building iOS Release bundle + installing on device..."
echo ""
echo "(Erster Release-Build dauert 5-10 min wegen Pod-Install + Bundle)"
echo ""
npx expo run:ios --device --configuration "$CONFIGURATION"
ok "App läuft jetzt standalone auf deinem iPhone"
echo "Backend: https://staging.rebreak.org"
echo "Free-Account: 7 Tage gültig, danach Skript erneut laufen lassen"
}
cmd_install_android() {
section "Android Standalone Install"
local SKIP_BUILD=false
local LAUNCH=true
while [[ $# -gt 0 ]]; do
case "$1" in
--no-build) SKIP_BUILD=true; shift ;;
--no-launch) LAUNCH=false; shift ;;
*) die "Unbekannter Flag für 'install android': $1" ;;
esac
done
command -v adb >/dev/null 2>&1 || die "adb nicht gefunden — brew install --cask android-platform-tools"
local DEVICE_COUNT
DEVICE_COUNT=$(adb devices | grep -E '[[:space:]]device$' | grep -c '.' || true)
if [[ "$DEVICE_COUNT" -eq 0 ]]; then
error "Kein Android-Gerät via ADB"
adb devices
exit 1
fi
local APK="$ANDROID_DIR/app/build/outputs/apk/debug/app-debug.apk"
if ! $SKIP_BUILD; then
log "Building debug APK..."
(cd "$ANDROID_DIR" && ./gradlew assembleDebug --console=plain)
fi
[[ -f "$APK" ]] || die "APK nicht gefunden: $APK"
log "Installing $APK..."
adb install -r -d "$APK"
if $LAUNCH; then
log "Launching App..."
adb shell monkey -p org.rebreak.app -c android.intent.category.LAUNCHER 1 >/dev/null 2>&1 || {
warn "Launch schlug fehl — App ist installiert, manuell öffnen"
}
fi
ok "Fertig"
}
cmd_magic() {
local BUILD=true
local LAUNCH=true
local XCODE_ONLY=false
local CONFIGURATION="Release"
while [[ $# -gt 0 ]]; do
case "$1" in
--no-build) BUILD=false; shift ;;
--no-launch) LAUNCH=false; shift ;;
--xcode) XCODE_ONLY=true; shift ;;
--debug) CONFIGURATION="Debug"; shift ;;
*) die "Unbekannter Flag für 'magic': $1" ;;
esac
done
section "RebreakMagic (macOS Wizard)"
[[ -d "$MAGIC_DIR" ]] || die "rebreak-magic-mac nicht gefunden: $MAGIC_DIR"
command -v xcodebuild >/dev/null 2>&1 || die "xcodebuild fehlt — xcode-select --install"
cd "$MAGIC_DIR"
# Xcode-Projekt aus project.yml generieren (idempotent)
if command -v xcodegen >/dev/null 2>&1; then
log "xcodegen generate"
xcodegen generate >/dev/null
else
warn "xcodegen nicht installiert — überspringe project-regeneration (brew install xcodegen)"
fi
if $XCODE_ONLY; then
log "Opening Xcode..."
open -a Xcode "$MAGIC_DIR/RebreakMagic.xcodeproj"
ok "Xcode geöffnet — Cmd+R für Build & Run"
return 0
fi
local BUILD_DIR="$MAGIC_DIR/build"
local APP_PATH="$BUILD_DIR/Build/Products/$CONFIGURATION/RebreakMagic.app"
if $BUILD; then
log "xcodebuild ($CONFIGURATION)"
xcodebuild \
-project RebreakMagic.xcodeproj \
-scheme RebreakMagic \
-configuration "$CONFIGURATION" \
-derivedDataPath "$BUILD_DIR" \
build | tail -20
[[ -d "$APP_PATH" ]] || die "Build fehlgeschlagen — $APP_PATH nicht gefunden"
ok "Build erfolgreich: $APP_PATH"
else
[[ -d "$APP_PATH" ]] || die "App nicht vorhanden ($APP_PATH) — ohne --no-build neu starten"
fi
# Config-Check
local CONFIG_FILE="$HOME/.config/rebreak-magic/config.json"
if [[ ! -f "$CONFIG_FILE" ]]; then
warn "Config fehlt: $CONFIG_FILE"
echo " → Template kopieren: cp $MAGIC_DIR/config.example.json $CONFIG_FILE"
echo " → Dann editieren: backendBaseUrl=https://staging.rebreak.org"
fi
if $LAUNCH; then
log "Killing running RebreakMagic instance..."
pkill -x RebreakMagic 2>/dev/null || true
sleep 0.5
log "Launching $APP_PATH"
open "$APP_PATH"
ok "RebreakMagic gestartet"
fi
}
# ═══════════════════════════════════════════════════════════════════════════
# Main
# ═══════════════════════════════════════════════════════════════════════════
COMMAND="${1:-ios}"
shift || true
case "$COMMAND" in
ios)
cmd_ios "$@"
;;
android)
cmd_android "$@"
;;
metro)
cmd_metro "$@"
;;
clean)
cmd_clean "$@"
;;
install)
local PLATFORM="${1:-}"
shift || true
case "$PLATFORM" in
ios) cmd_install_ios "$@" ;;
android) cmd_install_android "$@" ;;
*)
error "Unbekannte install-Platform: $PLATFORM"
echo "Verfügbar: ios, android"
exit 1
;;
esac
;;
magic)
cmd_magic "$@"
;;
-h|--help)
awk '/^#!/{next} /^#/{sub(/^# ?/, ""); print; next} {exit}' "$0"
exit 0
;;
*)
error "Unbekanntes Subcommand: $COMMAND"
echo ""
echo "Verfügbare Commands:"
echo " ios iOS Dev (Metro + Xcode/Simulator/Device)"
echo " android Android Dev (Metro + Gradle + Install)"
echo " metro Nur Metro starten"
echo " clean iOS Nuclear Clean"
echo " install ios Release-Build auf iPhone installieren"
echo " install android Debug-APK auf Android installieren"
echo " magic Build + Launch RebreakMagic.app (macOS)"
echo ""
echo "Nutze --help für Details"
exit 1
;;
esac