#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" TMP_DIR="$ROOT_DIR/.tmp/ui-e2e" LOG_DIR="$TMP_DIR/logs" POSTGRES_CONTAINER="crank-e2e-postgres" POSTGRES_PORT="${CRANK_E2E_POSTGRES_PORT:-55433}" ADMIN_PORT="${CRANK_E2E_ADMIN_PORT:-3301}" MCP_PORT="${CRANK_E2E_MCP_PORT:-3302}" UI_PORT="${CRANK_E2E_UI_PORT:-3300}" STREAM_FIXTURE_PORT="${CRANK_E2E_STREAM_FIXTURE_PORT:-3310}" GRPC_FIXTURE_BIND="${CRANK_E2E_GRPC_FIXTURE_BIND:-127.0.0.1:3311}" POSTGRES_DB="${CRANK_E2E_POSTGRES_DB:-crank}" POSTGRES_USER="${CRANK_E2E_POSTGRES_USER:-crank}" POSTGRES_PASSWORD="${CRANK_E2E_POSTGRES_PASSWORD:-crank}" POSTGRES_HOST="${CRANK_E2E_POSTGRES_HOST:-127.0.0.1}" USE_EXTERNAL_POSTGRES="${CRANK_E2E_USE_EXTERNAL_POSTGRES:-0}" ADMIN_EMAIL="${CRANK_E2E_ADMIN_EMAIL:-owner@crank.local}" ADMIN_PASSWORD="${CRANK_E2E_ADMIN_PASSWORD:-change-me-admin-password}" if [[ -f "$HOME/.cargo/env" ]]; then . "$HOME/.cargo/env" fi PYTHON_BIN="${PYTHON_BIN:-}" if [[ -z "$PYTHON_BIN" ]]; then if command -v python3 >/dev/null 2>&1; then PYTHON_BIN="python3" else PYTHON_BIN="python" fi fi mkdir -p "$LOG_DIR" ( cd "$ROOT_DIR/apps/ui" npm run build >"$LOG_DIR/ui-build.log" 2>&1 ) kill_port_processes() { local port="$1" if command -v lsof >/dev/null 2>&1; then lsof -ti "tcp:$port" | xargs -r kill >/dev/null 2>&1 || true fi } cleanup() { set +e if [[ -f "$TMP_DIR/admin-api.pid" ]]; then kill "$(cat "$TMP_DIR/admin-api.pid")" >/dev/null 2>&1 || true rm -f "$TMP_DIR/admin-api.pid" fi if [[ -f "$TMP_DIR/mcp-server.pid" ]]; then kill "$(cat "$TMP_DIR/mcp-server.pid")" >/dev/null 2>&1 || true rm -f "$TMP_DIR/mcp-server.pid" fi if [[ -f "$TMP_DIR/ui-server.pid" ]]; then kill "$(cat "$TMP_DIR/ui-server.pid")" >/dev/null 2>&1 || true rm -f "$TMP_DIR/ui-server.pid" fi if [[ "$USE_EXTERNAL_POSTGRES" != "1" ]]; then docker rm -f "$POSTGRES_CONTAINER" >/dev/null 2>&1 || true fi kill_port_processes "$UI_PORT" kill_port_processes "$ADMIN_PORT" kill_port_processes "$MCP_PORT" } trap cleanup EXIT INT TERM cleanup wait_for_port() { local host="$1" local port="$2" until "$PYTHON_BIN" - "$host" "$port" <<'PY' import socket, sys sock = socket.socket() sock.settimeout(0.5) try: sock.connect((sys.argv[1], int(sys.argv[2]))) except OSError: sys.exit(1) finally: sock.close() PY do sleep 1 done } if [[ "$USE_EXTERNAL_POSTGRES" = "1" ]]; then wait_for_port "$POSTGRES_HOST" "$POSTGRES_PORT" else docker run -d --rm \ --name "$POSTGRES_CONTAINER" \ -e POSTGRES_DB="$POSTGRES_DB" \ -e POSTGRES_USER="$POSTGRES_USER" \ -e POSTGRES_PASSWORD="$POSTGRES_PASSWORD" \ -p "$POSTGRES_PORT:5432" \ postgres:16-alpine >/dev/null until docker exec "$POSTGRES_CONTAINER" pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" >/dev/null 2>&1; do sleep 1 done fi export POSTGRES_HOST export POSTGRES_PORT="$POSTGRES_PORT" export POSTGRES_DB="$POSTGRES_DB" export POSTGRES_USER="$POSTGRES_USER" export POSTGRES_PASSWORD="$POSTGRES_PASSWORD" export DATABASE_URL="postgres://$POSTGRES_USER:$POSTGRES_PASSWORD@$POSTGRES_HOST:$POSTGRES_PORT/$POSTGRES_DB" export SQLX_OFFLINE="true" export CRANK_STORAGE_ROOT="$TMP_DIR/storage" export CRANK_ADMIN_BIND="0.0.0.0:$ADMIN_PORT" export CRANK_MCP_BIND="0.0.0.0:$MCP_PORT" export CRANK_MCP_REFRESH_MS="1000" export CRANK_LOG_LEVEL="info" export CRANK_SESSION_SECRET="e2e-session-secret" export CRANK_PASSWORD_PEPPER="e2e-password-pepper" export CRANK_MASTER_KEY="0000000000000000000000000000000000000000000000000000000000000000" export CRANK_SESSION_TTL_HOURS="24" export CRANK_BOOTSTRAP_ADMIN_EMAIL="$ADMIN_EMAIL" export CRANK_BOOTSTRAP_ADMIN_PASSWORD="$ADMIN_PASSWORD" export CRANK_BOOTSTRAP_ADMIN_DISPLAY_NAME="Crank E2E" export CRANK_DEMO_SEED="true" export CRANK_BASE_URL="http://127.0.0.1:$UI_PORT" mkdir -p "$CRANK_STORAGE_ROOT" ( cd "$ROOT_DIR" cargo run -p admin-api >"$LOG_DIR/admin-api.log" 2>&1 ) & echo $! > "$TMP_DIR/admin-api.pid" until curl -fsS "http://127.0.0.1:$ADMIN_PORT/health" >/dev/null 2>&1; do sleep 1 done ( cd "$ROOT_DIR" cargo run -p mcp-server >"$LOG_DIR/mcp-server.log" 2>&1 ) & echo $! > "$TMP_DIR/mcp-server.pid" until curl -fsS "http://127.0.0.1:$MCP_PORT/health" >/dev/null 2>&1; do sleep 1 done ( cd "$ROOT_DIR/apps/ui" node scripts/playwright-ui-server.js >"$LOG_DIR/ui-server.log" 2>&1 ) & echo $! > "$TMP_DIR/ui-server.pid" until curl -fsS "http://127.0.0.1:$UI_PORT/login" >/dev/null 2>&1; do sleep 1 done echo "Crank UI e2e stack is ready on http://127.0.0.1:$UI_PORT" while true; do sleep 1 done