Files
zeekayandHanzo Dev 69c6e860d7 transplant onto tamagui v2.6.0, brand as a re-runnable transform
This repo could never sync. There was NO merge-base with tamagui — 372 commits
here against 15,791 there, unrelated histories — because it began as a
copy-paste rebrand with a flattened layout. Every "sync" since was a hand
re-application that drifted, which is why the tree sat on 7.3.1 while upstream
shipped fixes we then rediscovered by hand.

This commit's PARENT IS TAMAGUI. A future sync is `git merge tama/main` then
`node scripts/rebrand.mjs` — a real merge, because the history is now shared.

174 packages: 166 upstream, our 8 restored (chrome, shell, telemetry, recipes,
get, font-geist-{sans,mono}, @hanzo/booker), bento-get gained. 170/170 build.

Our patches to files UPSTREAM ALSO OWNS are carried, which the package-level
delta missed and which was where the value actually was:
  - useMedia's server snapshot (`initState ?? getMedia()`). Upstream still
    returns undefined there, so every Tamagui Dialog kills a prerender — the
    exact crash hanzo.app spent a day on.
  - data-slot on web host nodes, absent upstream entirely.
Both applied ONTO upstream's newer files (it rewrote useMedia +150/-63 and
createComponent +160/-55), marked `PATCH (ours, carried across the transplant)`.

Seven traps are encoded in scripts/rebrand.mjs, each found by a gate:
skip itself and lockfiles; special-case the double-branded names in BOTH scoped
and bare form; map path strings, not just paths; drop a level for the flattened
group; check disk rather than a stale index.

Rust build artifacts are NOT committed. An earlier draft of this commit swept
apps/desktop/src-tauri/target/debug in via `git add -A` — 3,859 files including
a 272MB .a, over GitHub's 100MB cap, so the push could never have landed. They
are gitignored now.

Co-Authored-By: Hanzo Dev <[email protected]>
2026-07-30 18:25:06 -07:00

370 lines
9.6 KiB
Bash
Executable File

#!/bin/bash
#
# run-detox.sh - Complete Detox E2E test runner
#
# Usage:
# bun run detox:run:ios # run all iOS tests
# bun run detox:run:ios "Sheet" # run tests matching "Sheet"
# bun run detox:run:android # run all Android tests
# bun run detox:run:android "Select" # run tests matching "Select"
#
# This script handles the full workflow:
# 1. Checks if pods need installing (iOS only)
# 2. Detects if app needs rebuilding (compares sources vs binary)
# 3. Builds if needed
# 4. Starts Metro if not running
# 5. Runs Detox tests
# 6. Cleans up Metro if we started it
#
# Environment variables:
# FORCE_BUILD=1 - Force rebuild even if app exists
# SKIP_BUILD=1 - Skip build check entirely
# SKIP_METRO=1 - Don't start Metro (assume it's running)
# DETOX_DEVICE=name - iOS simulator device type (default: iPhone 16)
# DETOX_METRO_PORT=n - Metro port reserved for Detox (default: 8082 on iOS, 8081 on Android)
#
set -e
PLATFORM="${1:-ios}" # ios or android
# dedicated detox metro port - non-default to avoid colliding with other dev servers.
# Android side: see android/app/src/main/res/values/integers.xml (react_native_dev_server_port)
DEFAULT_DETOX_METRO_PORT=9034
export DETOX_METRO_PORT="${DETOX_METRO_PORT:-$DEFAULT_DETOX_METRO_PORT}"
detect_android_sdk() {
if [ -n "$ANDROID_SDK_ROOT" ] && [ -d "$ANDROID_SDK_ROOT" ]; then
return
fi
for candidate in \
"$HOME/Library/Android/sdk" \
"$HOME/Android/Sdk"
do
if [ -d "$candidate" ]; then
export ANDROID_SDK_ROOT="$candidate"
export ANDROID_HOME="$candidate"
return
fi
done
}
# --- Prerequisites check ---
check_prerequisites() {
local missing=false
if [ "$PLATFORM" = "android" ]; then
detect_android_sdk
fi
if ! command -v detox &> /dev/null; then
echo "detox-cli not found. Install with: npm install -g detox-cli"
missing=true
fi
if [ "$PLATFORM" = "ios" ] && ! command -v applesimutils &> /dev/null; then
echo "applesimutils not found. Install with: brew tap wix/brew && brew install applesimutils"
missing=true
fi
if [ "$PLATFORM" = "android" ] && [ -z "$ANDROID_SDK_ROOT" ]; then
echo "ANDROID_SDK_ROOT is not set and no local Android SDK was found"
missing=true
fi
if [ "$missing" = true ]; then
echo ""
echo "Install missing dependencies and try again."
exit 1
fi
# Detox framework cache (needed after Xcode updates)
if [ "$PLATFORM" = "ios" ] && [ ! -d "$HOME/Library/Detox/ios/framework" ]; then
echo "Detox framework cache not found. Building..."
npx detox clean-framework-cache && npx detox build-framework-cache
echo ""
fi
# iOS directory (expo prebuild)
if [ "$PLATFORM" = "ios" ] && [ ! -d "ios" ]; then
echo "No ios/ directory found. Running expo prebuild..."
npx expo prebuild --platform ios
echo ""
fi
}
TEST_FILTER="${2:-}" # optional test name filter
CONFIG=""
APP_PATH=""
BUILD_CMD=""
case "$PLATFORM" in
ios)
CONFIG="ios.sim.debug"
APP_PATH="ios/build/Build/Products/Debug-iphonesimulator/hanzoguikitchensink.app"
BUILD_CMD="bun run detox:build:ios"
;;
android)
CONFIG="android.emu.debug"
APP_PATH="android/app/build/outputs/apk/debug/app-debug.apk"
BUILD_CMD="bun run detox:build:android"
;;
*)
echo "Unknown platform: $PLATFORM"
echo "Usage: $0 [ios|android] [test-filter]"
exit 1
;;
esac
METRO_PID=""
STARTED_METRO=false
# invoked by the EXIT trap below
# shellcheck disable=SC2329
cleanup() {
if [ "$STARTED_METRO" = true ] && [ -n "$METRO_PID" ]; then
echo ""
echo "=== Cleanup: Stopping Metro (PID: $METRO_PID) ==="
kill "$METRO_PID" 2>/dev/null || true
# also kill child processes
pkill -P "$METRO_PID" 2>/dev/null || true
fi
}
trap cleanup EXIT
is_metro_running() {
curl -s "http://localhost:${DETOX_METRO_PORT}/status" > /dev/null 2>&1
}
DID_BUILD=false
ios_bundle_id() {
if [ -e "$APP_PATH/Info.plist" ]; then
/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$APP_PATH/Info.plist" 2>/dev/null && return
fi
node -e "console.log(require('./app.json').expo.ios.bundleIdentifier)" 2>/dev/null
}
android_package_name() {
node -e "console.log(require('./app.json').expo.android.package)" 2>/dev/null
}
adb_command() {
if command -v adb > /dev/null 2>&1; then
command adb "$@"
return
fi
"$ANDROID_SDK_ROOT/platform-tools/adb" "$@"
}
shell_quote() {
printf "'%s'" "$(printf "%s" "$1" | sed "s/'/'\\\\''/g")"
}
is_app_installed() {
if [ "$PLATFORM" = "ios" ]; then
local bundle_id
bundle_id="$(ios_bundle_id)"
if [ -z "$bundle_id" ]; then
return 1
fi
local booted_simulators
if [ -n "$DETOX_DEVICE_UDID" ]; then
booted_simulators="$DETOX_DEVICE_UDID"
else
booted_simulators="$(xcrun simctl list devices booted | sed -n 's/.*(\([A-F0-9-]\{36\}\)) (Booted).*/\1/p')"
fi
if [ -z "$booted_simulators" ]; then
return 1
fi
local booted_simulator_count
booted_simulator_count="$(printf "%s\n" "$booted_simulators" | awk 'NF { count++ } END { print count + 0 }')"
if [ "$booted_simulator_count" -ne 1 ]; then
return 1
fi
while IFS= read -r udid; do
if [ -n "$udid" ] && xcrun simctl get_app_container "$udid" "$bundle_id" app > /dev/null 2>&1; then
return 0
fi
done <<< "$booted_simulators"
return 1
fi
local package_name
package_name="$(android_package_name)"
if [ -z "$package_name" ]; then
return 1
fi
local android_devices
android_devices="$(adb_command devices 2>/dev/null | awk 'NR > 1 && $2 == "device" { print $1 }')"
if [ -z "$android_devices" ]; then
return 1
fi
local android_device_count
android_device_count="$(printf "%s\n" "$android_devices" | awk 'NF { count++ } END { print count + 0 }')"
if [ "$android_device_count" -ne 1 ]; then
return 1
fi
while IFS= read -r serial; do
if [ -n "$serial" ] && adb_command -s "$serial" shell pm path "$package_name" 2>/dev/null | grep -q '^package:'; then
return 0
fi
done <<< "$android_devices"
return 1
}
needs_rebuild() {
# if app doesn't exist, needs build
if [ ! -e "$APP_PATH" ]; then
echo "App not found at $APP_PATH"
return 0
fi
# if FORCE_BUILD is set, needs build
if [ "$FORCE_BUILD" = "1" ]; then
echo "FORCE_BUILD=1 set"
return 0
fi
# check if Podfile.lock is newer than app (pods changed)
if [ "$PLATFORM" = "ios" ]; then
if [ "ios/Podfile.lock" -nt "$APP_PATH" ]; then
echo "Podfile.lock is newer than app"
return 0
fi
fi
# check if package.json is newer (deps changed)
if [ "package.json" -nt "$APP_PATH" ]; then
echo "package.json is newer than app"
return 0
fi
return 1
}
echo "========================================"
echo " Detox Test Runner"
echo "========================================"
echo "Platform: $PLATFORM"
echo "Config: $CONFIG"
if [ "$PLATFORM" = "ios" ]; then
echo "Device: ${DETOX_DEVICE:-iPhone 16}"
else
echo "Device: ${DETOX_AVD_NAME:-auto-detected Android emulator}"
fi
echo "Metro port: $DETOX_METRO_PORT"
if [ "$PLATFORM" = "android" ]; then
echo "Android SDK: ${ANDROID_SDK_ROOT:-<unset>}"
fi
echo "Test filter: ${TEST_FILTER:-<all tests>}"
echo ""
# step 0: check prerequisites
echo "=== Step 0: Checking prerequisites ==="
check_prerequisites
echo ""
# step 1: check if build needed
if [ "$SKIP_BUILD" != "1" ]; then
echo "=== Step 1: Checking if build needed ==="
if needs_rebuild; then
echo "Building app..."
$BUILD_CMD
DID_BUILD=true
else
echo "App is up to date, skipping build"
fi
echo ""
fi
# step 2: start metro if needed
if [ "$SKIP_METRO" != "1" ]; then
echo "=== Step 2: Metro bundler ==="
if is_metro_running; then
echo "Metro already running on port $DETOX_METRO_PORT"
else
echo "Starting Metro on port $DETOX_METRO_PORT..."
EXPO_NO_TELEMETRY=true npx expo start --dev-client --offline --port "$DETOX_METRO_PORT" > /tmp/metro-detox.log 2>&1 &
METRO_PID=$!
STARTED_METRO=true
# wait for metro to be ready (up to 60s)
echo -n "Waiting for Metro to start"
for _ in {1..60}; do
if is_metro_running; then
echo " ready!"
break
fi
echo -n "."
sleep 1
done
if ! is_metro_running; then
echo " FAILED"
echo "Metro failed to start. Check /tmp/metro-detox.log:"
tail -20 /tmp/metro-detox.log
exit 1
fi
fi
echo ""
fi
# step 3: run detox tests
echo "=== Step 3: Running Detox tests ==="
# use --reuse only if the app is already installed on the active device
if [ "$DID_BUILD" = true ]; then
echo "Fresh build detected, will reinstall app on simulator"
DETOX_ARGS=(-c "$CONFIG" --maxWorkers 1)
elif is_app_installed; then
DETOX_ARGS=(-c "$CONFIG" --reuse --maxWorkers 1)
else
echo "App is not installed on the active test device, will install before testing"
DETOX_ARGS=(-c "$CONFIG" --maxWorkers 1)
fi
# add headless flag if requested
if [ "$HEADLESS" = "1" ]; then
DETOX_ARGS+=(--headless)
fi
if [ -n "$TEST_FILTER" ]; then
QUOTED_TEST_FILTER="$(shell_quote "$TEST_FILTER")"
if [ -f "$TEST_FILTER" ] || [[ "$TEST_FILTER" == *".test."* ]] || [[ "$TEST_FILTER" == e2e/* ]]; then
DETOX_ARGS+=("$QUOTED_TEST_FILTER")
else
DETOX_ARGS+=(-t "$QUOTED_TEST_FILTER")
fi
fi
set +e
DETOX_METRO_PORT="$DETOX_METRO_PORT" npx detox test "${DETOX_ARGS[@]}"
EXIT_CODE=$?
set -e
echo ""
echo "========================================"
if [ $EXIT_CODE -eq 0 ]; then
echo " Tests passed!"
else
echo " Tests failed (exit code: $EXIT_CODE)"
fi
echo "========================================"
exit $EXIT_CODE