#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" BACKEND_DIR="$ROOT_DIR/backend" FRONTEND_DIR="$ROOT_DIR/frontend" BACKEND_VENV="${BACKEND_VENV:-}" BACKEND_PORT="${BACKEND_PORT:-8000}" FRONTEND_PORT="${FRONTEND_PORT:-5173}" PYTHON_BIN="${PYTHON_BIN:-}" BACKEND_PID="" is_supported_python_version() { local version="$1" case "$version" in 3.10|3.11|3.12|3.13) return 0 ;; *) return 1 ;; esac } cleanup() { if [[ -n "$BACKEND_PID" ]] && kill -0 "$BACKEND_PID" >/dev/null 2>&1; then echo "" echo "Encerrando backend..." kill "$BACKEND_PID" >/dev/null 2>&1 || true wait "$BACKEND_PID" 2>/dev/null || true fi } trap cleanup EXIT INT TERM require_command() { local command_name="$1" if ! command -v "$command_name" >/dev/null 2>&1; then echo "Comando obrigatório não encontrado: $command_name" >&2 exit 1 fi } python_bin_works() { local command_name="$1" [[ -n "$command_name" ]] && command -v "$command_name" >/dev/null 2>&1 } python_version_of_bin() { local command_name="$1" if python_bin_works "$command_name"; then "$command_name" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")' fi } venv_python_works() { local venv_dir="$1" [[ -x "$venv_dir/bin/python" ]] && "$venv_dir/bin/python" -c 'import sys' >/dev/null 2>&1 } resolve_python_bin() { local candidates=() if [[ -n "$PYTHON_BIN" ]]; then candidates+=("$PYTHON_BIN") fi candidates+=("python3.13" "python3.12" "python3.11" "python3.10" "python3" "python") local candidate for candidate in "${candidates[@]}"; do if python_bin_works "$candidate"; then local version version="$(python_version_of_bin "$candidate")" if is_supported_python_version "$version"; then echo "$candidate" return 0 fi fi done return 1 } resolve_backend_venv() { if [[ -n "$BACKEND_VENV" ]]; then echo "$BACKEND_VENV" return 0 fi local candidates=( "$BACKEND_DIR/.venv313" "$BACKEND_DIR/.venv312" "$BACKEND_DIR/.venv" ) local candidate for candidate in "${candidates[@]}"; do if venv_python_works "$candidate"; then local version version="$(python_version_of_venv "$candidate")" if is_supported_python_version "$version"; then echo "$candidate" return 0 fi fi done if python_bin_works "python3.13"; then echo "$BACKEND_DIR/.venv313" return 0 fi if python_bin_works "python3.12"; then echo "$BACKEND_DIR/.venv312" return 0 fi echo "$BACKEND_DIR/.venv" } port_in_use() { local port="$1" lsof -nP -iTCP:"$port" -sTCP:LISTEN >/dev/null 2>&1 } pick_available_port() { local port="$1" while port_in_use "$port"; do port=$((port + 1)) done echo "$port" } python_version_of_venv() { local venv_dir="$1" if [[ -x "$venv_dir/bin/python" ]]; then "$venv_dir/bin/python" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")' fi } ensure_backend_env() { BACKEND_VENV="$(resolve_backend_venv)" PYTHON_BIN="$(resolve_python_bin || true)" if [[ -z "$PYTHON_BIN" ]]; then echo "Comando obrigatório não encontrado: python3.13, python3.12, python3.11, python3.10, python3 ou python" >&2 exit 1 fi local desired_version desired_version="$(python_version_of_bin "$PYTHON_BIN")" if [[ -d "$BACKEND_VENV" ]] && venv_python_works "$BACKEND_VENV" && [[ "$(python_version_of_venv "$BACKEND_VENV")" == "$desired_version" ]]; then echo "Usando ambiente virtual existente em $BACKEND_VENV." else if [[ -d "$BACKEND_VENV" ]]; then local current_version current_version="$(python_version_of_venv "$BACKEND_VENV")" echo "A venv em $BACKEND_VENV não está adequada para o ambiente atual." >&2 if [[ -n "$current_version" ]]; then echo "Ela usa Python $current_version, mas o script vai usar Python $desired_version." >&2 fi echo "Vou recriar a venv com $PYTHON_BIN (Python $desired_version)." >&2 rm -rf "$BACKEND_VENV" else echo "Criando ambiente virtual do backend com $PYTHON_BIN..." fi "$PYTHON_BIN" -m venv "$BACKEND_VENV" fi if [[ -d "$BACKEND_VENV" ]] && ! venv_python_works "$BACKEND_VENV"; then local current_version current_version="$(python_version_of_venv "$BACKEND_VENV")" echo "A venv em $BACKEND_VENV continua indisponível após a preparação." >&2 echo "Versão detectada: ${current_version:-desconhecida}" >&2 exit 1 fi echo "Instalando/verificando dependências do backend..." # shellcheck disable=SC1091 source "$BACKEND_VENV/bin/activate" pip install -r "$BACKEND_DIR/requirements.txt" >/dev/null } ensure_frontend_env() { require_command npm if [[ ! -d "$FRONTEND_DIR/node_modules" ]] || [[ ! -x "$FRONTEND_DIR/node_modules/.bin/vite" ]]; then echo "Instalando dependências do frontend..." (cd "$FRONTEND_DIR" && npm install) fi } prepare_ports() { if port_in_use "$BACKEND_PORT"; then local requested_port="$BACKEND_PORT" BACKEND_PORT="$(pick_available_port "$BACKEND_PORT")" echo "Porta $requested_port já está em uso no backend. Usando $BACKEND_PORT." fi if port_in_use "$FRONTEND_PORT"; then local requested_port="$FRONTEND_PORT" FRONTEND_PORT="$(pick_available_port "$FRONTEND_PORT")" echo "Porta $requested_port já está em uso no frontend. Usando $FRONTEND_PORT." fi } start_backend() { echo "Subindo backend em http://127.0.0.1:$BACKEND_PORT ..." ( cd "$BACKEND_DIR" # shellcheck disable=SC1091 source "$BACKEND_VENV/bin/activate" uvicorn app.main:app --reload --host 127.0.0.1 --port "$BACKEND_PORT" ) & BACKEND_PID=$! } start_frontend() { echo "Subindo frontend em http://127.0.0.1:$FRONTEND_PORT ..." cd "$FRONTEND_DIR" VITE_API_BASE_URL="http://127.0.0.1:$BACKEND_PORT" npm run dev -- --host 127.0.0.1 --port "$FRONTEND_PORT" } echo "Preparando ambiente de desenvolvimento..." ensure_backend_env ensure_frontend_env prepare_ports start_backend start_frontend