#!/bin/bash set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" cd "$PROJECT_DIR" # Cleanup function to stop containers cleanup() { echo "" echo "๐Ÿ›‘ Shutting down containers..." COMPOSE_FILES="-f docker-compose.test.yml" if [ -f "docker-compose.test.local.yml" ]; then COMPOSE_FILES="$COMPOSE_FILES -f docker-compose.test.local.yml" fi docker compose $COMPOSE_FILES down 2>/dev/null || docker-compose $COMPOSE_FILES down 2>/dev/null || true rm -f docker-compose.test.local.yml exit 0 } # Trap SIGINT (Ctrl+C) and SIGTERM to run cleanup trap cleanup SIGINT SIGTERM # Port configuration (can be overridden via environment variables) SERVICE_HOST_PORT="${BOM_SERVICE_PORT:-8082}" HA_HOST_PORT="${HA_PORT:-8124}" # Parse command-line arguments SKIP_BUILD=0 FORCE_BUILD=0 USE_DOCKER_BUILD="" USE_NPM_BUILD="" SERVICE_PATH="" show_help() { cat < /dev/null; then USE_DOCKER_BUILD=1 elif command -v npm &> /dev/null; then USE_NPM_BUILD=1 else echo "โŒ Error: Neither Docker nor npm found" echo " Please install Docker or Node.js/npm" exit 1 fi fi # Function to build using npm (local) build_with_npm() { echo "๐Ÿ”จ Building card with npm (local)..." if ! command -v npm &> /dev/null; then echo "โŒ Error: npm not found" echo " Install Node.js/npm or use Docker: ./scripts/test.sh --docker-build" exit 1 fi # Check if node_modules exists if [ ! -d "node_modules" ]; then echo "๐Ÿ“ฆ Installing dependencies..." npm install fi npm run build } # Function to build using Docker build_with_docker() { echo "๐Ÿณ Building card using Docker (isolated environment)..." if ! command -v docker &> /dev/null; then echo "โŒ Error: docker not found" echo " Install Docker or use npm: ./scripts/test.sh --npm-build" exit 1 fi # Pass FORCE_REBUILD if FORCE_BUILD is set if [ "$FORCE_BUILD" = "1" ]; then FORCE_REBUILD=1 bash scripts/build-docker.sh else bash scripts/build-docker.sh fi } # Build card if [ "$SKIP_BUILD" = "1" ]; then echo "โญ๏ธ Skipping build (--skip-build)" if [ ! -f "dist/bom-local-radar-card.js" ]; then echo "โŒ Error: dist/bom-local-radar-card.js not found and --skip-build was used" exit 1 fi elif [ "$FORCE_BUILD" = "1" ] || [ ! -f "dist/bom-local-radar-card.js" ]; then # If FORCE_BUILD is set, also force Docker rebuild if [ "$FORCE_BUILD" = "1" ] && [ "$USE_DOCKER_BUILD" = "1" ]; then export FORCE_REBUILD=1 fi if [ "$USE_DOCKER_BUILD" = "1" ]; then build_with_docker elif [ "$USE_NPM_BUILD" = "1" ]; then build_with_npm else # Auto-detect: prefer Docker, fallback to npm if command -v docker &> /dev/null; then build_with_docker else build_with_npm fi fi if [ ! -f "dist/bom-local-radar-card.js" ]; then echo "โŒ Error: dist/bom-local-radar-card.js not found after build" exit 1 fi # Verify build quality FILE_SIZE=$(stat -f%z "dist/bom-local-radar-card.js" 2>/dev/null || stat -c%s "dist/bom-local-radar-card.js" 2>/dev/null || echo "0") if [ "$FILE_SIZE" -lt 50000 ]; then echo "โš ๏ธ Warning: Built file is suspiciously small (${FILE_SIZE} bytes)" echo " This might indicate dependencies weren't bundled correctly" echo " Check rollup.config.js - dependencies should NOT be marked as external" fi # Check for external imports (should not exist if dependencies are bundled) if grep -q "from ['\"]lit['\"]" dist/bom-local-radar-card.js 2>/dev/null || \ grep -q "from ['\"]custom-card-helpers['\"]" dist/bom-local-radar-card.js 2>/dev/null; then echo "โš ๏ธ Warning: Found external import statements in built file" echo " Dependencies may not be bundled. Check rollup.config.js" fi echo "โœ… Build complete (${FILE_SIZE} bytes)" else echo "โœ… Using existing dist/bom-local-radar-card.js" echo " (Use --force-build to rebuild, --docker-build or --npm-build to specify method)" fi # If SERVICE_PATH is set, create override file for local service build if [ -n "$SERVICE_PATH" ] && [ ! -f "docker-compose.test.local.yml" ]; then OVERRIDE_FILE="docker-compose.test.local.yml" cat > "$OVERRIDE_FILE" </dev/null | grep -q "Up" || \ docker-compose $COMPOSE_FILES_CHECK ps 2>/dev/null | grep -q "Up"; then CONTAINERS_RUNNING=1 fi # If containers are running, automatically update the card (no need for UPDATE_MODE) if [ "$CONTAINERS_RUNNING" = "1" ]; then echo "๐Ÿ”„ Updating card in running test environment (preserving HA state)..." # Copy card file echo "๐Ÿ“‹ Updating card file..." mkdir -p test-ha/config/www # Copy file using Docker (containers run as root, so we use root to write) docker run --rm \ -v "$(pwd)/dist/bom-local-radar-card.js:/source/bom-local-radar-card.js:ro" \ -v "$(pwd)/test-ha/config/www:/dest:rw" \ -u root \ alpine:latest \ sh -c "cp /source/bom-local-radar-card.js /dest/bom-local-radar-card.js && chmod 644 /dest/bom-local-radar-card.js" 2>/dev/null || { echo "โŒ Error: Failed to copy card file" exit 1 } echo " โœ… Card file copied successfully" # Verify copy succeeded if [ ! -f "test-ha/config/www/bom-local-radar-card.js" ]; then echo "โŒ Error: Failed to update card file" exit 1 fi # Verify file sizes match SOURCE_SIZE=$(stat -f%z "dist/bom-local-radar-card.js" 2>/dev/null || stat -c%s "dist/bom-local-radar-card.js" 2>/dev/null) DEST_SIZE=$(stat -f%z "test-ha/config/www/bom-local-radar-card.js" 2>/dev/null || stat -c%s "test-ha/config/www/bom-local-radar-card.js" 2>/dev/null) if [ "$SOURCE_SIZE" != "$DEST_SIZE" ]; then echo "โŒ Error: File sizes don't match after copy" exit 1 fi # Verify file contains expected content (basic sanity check) if ! grep -q "resolveImageUrl\|BOM-LOCAL-RADAR-CARD" test-ha/config/www/bom-local-radar-card.js 2>/dev/null; then echo " โš ๏ธ Warning: Card file may not be valid (missing expected content)" fi echo "โœ… Card file updated (${SOURCE_SIZE} bytes)" # Restart HA to pick up changes if docker compose version &> /dev/null; then DOCKER_COMPOSE_CMD="docker compose" elif command -v docker-compose &> /dev/null; then DOCKER_COMPOSE_CMD="docker-compose" else echo "โŒ Error: docker compose not found" exit 1 fi # Determine compose files to use (check if override exists) COMPOSE_FILES="-f docker-compose.test.yml" if [ -f "docker-compose.test.local.yml" ]; then COMPOSE_FILES="$COMPOSE_FILES -f docker-compose.test.local.yml" fi # If service path is specified, rebuild and restart the service if [ -n "$SERVICE_PATH" ]; then echo "๐Ÿ”จ Rebuilding service from local source: $SERVICE_PATH" # Ensure override file exists for local service build OVERRIDE_FILE="docker-compose.test.local.yml" if [ ! -f "$OVERRIDE_FILE" ]; then cat > "$OVERRIDE_FILE" </dev/null | grep -c "Up" || echo "0") if [ "$SERVICE_STATUS" = "0" ]; then echo "๐Ÿ”„ Restarting BOM service (was not running)..." $DOCKER_COMPOSE_CMD $COMPOSE_FILES restart bom-local-service || \ $DOCKER_COMPOSE_CMD $COMPOSE_FILES up -d bom-local-service # Wait a moment and check if service started sleep 3 if ! curl -s -f "http://localhost:${SERVICE_HOST_PORT}/api/radar/Brisbane/QLD" > /dev/null 2>&1; then echo " โš ๏ธ Warning: BOM service may not be responding correctly" echo " Check logs: docker logs bom-card-test-service" else echo " โœ… Service is responding" fi fi echo "โณ Waiting for Home Assistant to restart..." sleep 5 MAX_WAIT=60 WAIT_COUNT=0 while [ $WAIT_COUNT -lt $MAX_WAIT ]; do if curl -s -f "http://localhost:${HA_HOST_PORT}" > /dev/null 2>&1; then echo "โœ… Home Assistant restarted with updated card!" break fi sleep 2 WAIT_COUNT=$((WAIT_COUNT + 2)) done echo "" echo "โœ… Card updated! Refresh your browser (Ctrl+F5) to see changes." exit 0 fi # Normal mode - full setup (containers not running) echo "๐Ÿš€ Setting up fresh test environment..." echo "๐Ÿณ Stopping any existing containers..." # Clean up any existing override files rm -f docker-compose.test.local.yml # Determine compose files to use COMPOSE_FILES_CLEAN="-f docker-compose.test.yml" if [ -f "docker-compose.test.local.yml" ]; then COMPOSE_FILES_CLEAN="$COMPOSE_FILES_CLEAN -f docker-compose.test.local.yml" fi # Stop and remove containers with volumes docker compose $COMPOSE_FILES_CLEAN down -v 2>/dev/null || docker-compose $COMPOSE_FILES_CLEAN down -v 2>/dev/null || true # Clean up .storage directory if it exists (using Docker if needed) if [ -d "test-ha/config/.storage" ]; then echo "๐Ÿงน Cleaning up existing .storage directory..." if ! rm -rf test-ha/config/.storage 2>/dev/null; then echo " Removing with Docker (files may be owned by root)..." docker run --rm \ -v "$(pwd)/test-ha/config:/config:rw" \ -u root \ alpine:latest \ sh -c "rm -rf /config/.storage" 2>/dev/null || true fi fi echo "๐Ÿ“ฆ Ensuring test directories exist..." mkdir -p test-ha/config/.storage mkdir -p test-ha/config/www # Ensure directories exist (Docker will create files as current user due to user: setting in compose) # Copy built card file to www directory (for /local/ access in HA) echo "๐Ÿ“‹ Copying card file to www directory..." mkdir -p test-ha/config/www # Check if update is needed by comparing file sizes and content NEEDS_UPDATE=1 if [ -f "test-ha/config/www/bom-local-radar-card.js" ]; then SOURCE_SIZE=$(stat -f%z "dist/bom-local-radar-card.js" 2>/dev/null || stat -c%s "dist/bom-local-radar-card.js" 2>/dev/null) DEST_SIZE=$(stat -f%z "test-ha/config/www/bom-local-radar-card.js" 2>/dev/null || stat -c%s "test-ha/config/www/bom-local-radar-card.js" 2>/dev/null) # Check if sizes match and if the file contains expected content (resolveImageUrl function) if [ "$SOURCE_SIZE" = "$DEST_SIZE" ]; then # Check if file contains key function to verify it's the latest version if grep -q "resolveImageUrl" test-ha/config/www/bom-local-radar-card.js 2>/dev/null || \ grep -q "Resolve relative image URLs" test-ha/config/www/bom-local-radar-card.js 2>/dev/null; then # File exists, sizes match, and contains expected content - may not need update # But we'll update anyway to be safe, or check modification time SOURCE_MTIME=$(stat -f%m "dist/bom-local-radar-card.js" 2>/dev/null || stat -c%Y "dist/bom-local-radar-card.js" 2>/dev/null) DEST_MTIME=$(stat -f%m "test-ha/config/www/bom-local-radar-card.js" 2>/dev/null || stat -c%Y "test-ha/config/www/bom-local-radar-card.js" 2>/dev/null) if [ "$SOURCE_MTIME" -le "$DEST_MTIME" ]; then NEEDS_UPDATE=0 fi fi fi fi if [ "$NEEDS_UPDATE" = "1" ]; then # Copy file using Docker (containers run as root, so we use root to write) echo " Copying file using Docker..." docker run --rm \ -v "$(pwd)/dist/bom-local-radar-card.js:/source/bom-local-radar-card.js:ro" \ -v "$(pwd)/test-ha/config/www:/dest:rw" \ -u root \ alpine:latest \ sh -c "cp /source/bom-local-radar-card.js /dest/bom-local-radar-card.js && chmod 644 /dest/bom-local-radar-card.js" 2>/dev/null || { echo "โŒ Error: Failed to copy card file" exit 1 } echo " โœ… Card file copied successfully" # Verify copy succeeded if [ ! -f "test-ha/config/www/bom-local-radar-card.js" ]; then echo "โŒ Error: Failed to copy card file to www directory" exit 1 fi # Verify file sizes match SOURCE_SIZE=$(stat -f%z "dist/bom-local-radar-card.js" 2>/dev/null || stat -c%s "dist/bom-local-radar-card.js" 2>/dev/null) DEST_SIZE=$(stat -f%z "test-ha/config/www/bom-local-radar-card.js" 2>/dev/null || stat -c%s "test-ha/config/www/bom-local-radar-card.js" 2>/dev/null) if [ "$SOURCE_SIZE" != "$DEST_SIZE" ]; then echo "โŒ Error: File sizes don't match after copy" exit 1 fi # Verify file contains expected content (basic sanity check) if ! grep -q "resolveImageUrl\|BOM-LOCAL-RADAR-CARD" test-ha/config/www/bom-local-radar-card.js 2>/dev/null; then echo " โš ๏ธ Warning: Card file may not be valid (missing expected content)" fi echo "โœ… Card file copied successfully" else echo "โœ… Card file is up to date (skipping copy)" fi # Copy configuration files to HA config directory echo "๐Ÿ“‹ Copying HA configuration files..." for config_file in configuration.yaml ui-lovelace.yaml; do if [ ! -f "test-ha/$config_file" ]; then echo " โš ๏ธ Warning: test-ha/$config_file not found, skipping..." continue fi cp "test-ha/$config_file" "test-ha/config/$config_file" done # Create onboarding bypass - skip everything EXCEPT user creation # This way HA will prompt for user creation but skip other setup steps echo "๐Ÿ“ Creating onboarding bypass..." mkdir -p test-ha/config/.storage cat > test-ha/config/.storage/onboarding < /dev/null; then DOCKER_COMPOSE_CMD="docker compose" elif command -v docker-compose &> /dev/null; then DOCKER_COMPOSE_CMD="docker-compose" else echo "โŒ Error: docker compose or docker-compose not found" exit 1 fi # Handle service build - either from local source or pre-built image DOCKER_COMPOSE_FILES="-f docker-compose.test.yml" if [ -n "$SERVICE_PATH" ]; then echo "๐Ÿ”จ Building service from local source: $SERVICE_PATH" # Create docker-compose override file for local service build OVERRIDE_FILE="docker-compose.test.local.yml" cat > "$OVERRIDE_FILE" < /dev/null 2>&1; then echo "โœ… Home Assistant is ready!" break fi sleep 2 WAIT_COUNT=$((WAIT_COUNT + 2)) if [ $((WAIT_COUNT % 10)) -eq 0 ]; then echo " Still waiting... (${WAIT_COUNT}s / ${MAX_WAIT}s)" fi done if [ $WAIT_COUNT -ge $MAX_WAIT ]; then echo " โš ๏ธ Warning: Home Assistant may not be fully ready yet" echo " Check logs: $DOCKER_COMPOSE_CMD $DOCKER_COMPOSE_FILES logs homeassistant" fi echo "" echo "โœ… Test environment started!" echo "" echo "๐Ÿ“‹ Next steps:" echo " 1. Open http://localhost:${HA_HOST_PORT} in your browser" echo " 2. Create a user account (first time only)" echo " 3. The test dashboard should be available with your custom card" echo " 4. Check browser console (F12) for any errors" echo "" echo "๐Ÿ”ง Useful commands:" echo " View logs: $DOCKER_COMPOSE_CMD $DOCKER_COMPOSE_FILES logs -f" echo " Stop: $DOCKER_COMPOSE_CMD $DOCKER_COMPOSE_FILES down" echo " Clean restart: ./run.sh clean && ./scripts/test.sh" echo " Force rebuild: ./scripts/test.sh --force-build" echo " Docker build: ./scripts/test.sh --docker-build" echo " NPM build: ./scripts/test.sh --npm-build" if [ -n "$SERVICE_PATH" ]; then echo " Local service: ./scripts/test.sh --service-path $SERVICE_PATH" fi echo " Help: ./scripts/test.sh --help" echo ""