- Updated SHA256 checksums for the latest portal backend and frontend tar.gz files to reflect new builds. - Introduced a new development script (`dev-watch.sh`) for the BFF application to streamline TypeScript building and aliasing during development. - Refactored the `package.json` scripts in the BFF application to improve development workflow and added new watch commands. - Enhanced the Salesforce connection service to support private key handling via environment variables, improving security and flexibility in configuration.
453 lines
14 KiB
Bash
Executable File
453 lines
14 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# 🔧 Development Environment Manager
|
|
# Clean, portable helper for local dev services & apps
|
|
|
|
set -Eeuo pipefail
|
|
IFS=$'\n\t'
|
|
|
|
########################################
|
|
# Config (override via env if you like)
|
|
########################################
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_ROOT="${PROJECT_ROOT:-"$(cd "$SCRIPT_DIR/../.." && pwd)"}"
|
|
|
|
# Compose file selection
|
|
# Priority: explicit COMPOSE_FILE > DEV_STACK (full|services|auto) > default (full if present)
|
|
DEFAULT_COMPOSE_SERVICES="$PROJECT_ROOT/docker/dev/docker-compose.yml"
|
|
DEFAULT_COMPOSE_FULL="$PROJECT_ROOT/docker/dev/docker-compose.full.yml"
|
|
|
|
if [ -z "${COMPOSE_FILE:-}" ]; then
|
|
case "${DEV_STACK:-services}" in
|
|
full)
|
|
COMPOSE_FILE="$DEFAULT_COMPOSE_FULL"
|
|
;;
|
|
services)
|
|
COMPOSE_FILE="$DEFAULT_COMPOSE_SERVICES"
|
|
;;
|
|
auto)
|
|
if [ -f "$DEFAULT_COMPOSE_FULL" ]; then
|
|
COMPOSE_FILE="$DEFAULT_COMPOSE_FULL"
|
|
else
|
|
COMPOSE_FILE="$DEFAULT_COMPOSE_SERVICES"
|
|
fi
|
|
;;
|
|
*)
|
|
COMPOSE_FILE="$DEFAULT_COMPOSE_FULL"
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
ENV_FILE="${ENV_FILE:-"$PROJECT_ROOT/.env"}"
|
|
ENV_EXAMPLE_FILE="${ENV_EXAMPLE_FILE:-"$PROJECT_ROOT/.env.example"}"
|
|
PROJECT_NAME="${PROJECT_NAME:-portal-dev}"
|
|
|
|
DB_USER_DEFAULT="dev"
|
|
DB_NAME_DEFAULT="portal_dev"
|
|
DB_WAIT_SECS="${DB_WAIT_SECS:-30}"
|
|
|
|
NEXT_PORT_DEFAULT=3000
|
|
BFF_PORT_DEFAULT=4000
|
|
|
|
########################################
|
|
# Colors (fallback if tput missing)
|
|
########################################
|
|
if command -v tput >/dev/null 2>&1 && [ -t 1 ]; then
|
|
GREEN="$(tput setaf 2)"
|
|
YELLOW="$(tput setaf 3)"
|
|
RED="$(tput setaf 1)"
|
|
NC="$(tput sgr0)"
|
|
else
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
RED='\033[0;31m'
|
|
NC='\033[0m'
|
|
fi
|
|
|
|
log() { echo -e "${GREEN}[DEV] $*${NC}"; }
|
|
warn() { echo -e "${YELLOW}[DEV] $*${NC}"; }
|
|
fail() { echo -e "${RED}[DEV] ERROR: $*${NC}"; exit 1; }
|
|
|
|
trap 'fail "Command failed (exit $?) at line $LINENO. See logs above."' ERR
|
|
|
|
########################################
|
|
# Docker Compose wrapper (v2 & v1)
|
|
########################################
|
|
detect_compose() {
|
|
if docker compose version >/dev/null 2>&1; then
|
|
echo "docker compose"
|
|
elif command -v docker-compose >/dev/null 2>&1; then
|
|
echo "docker-compose"
|
|
else
|
|
fail "Docker Compose not found. Install Docker Desktop or docker-compose."
|
|
fi
|
|
}
|
|
COMPOSE_BIN="$(detect_compose)"
|
|
|
|
compose() {
|
|
# shellcheck disable=SC2086
|
|
eval $COMPOSE_BIN -f "$COMPOSE_FILE" -p "$PROJECT_NAME" "$@"
|
|
}
|
|
|
|
########################################
|
|
# Preflight checks
|
|
########################################
|
|
preflight() {
|
|
command -v docker >/dev/null 2>&1 || fail "Docker is required."
|
|
[ -f "$COMPOSE_FILE" ] || fail "Compose file not found: $COMPOSE_FILE (set DEV_STACK=services or COMPOSE_FILE to override)"
|
|
|
|
# Suggest Docker running if ps fails
|
|
if ! docker info >/dev/null 2>&1; then
|
|
fail "Docker daemon not reachable. Is Docker running?"
|
|
fi
|
|
|
|
# pnpm required for app tasks
|
|
if [[ "${1:-}" == "apps" || "${1:-}" == "migrate" ]]; then
|
|
command -v pnpm >/dev/null 2>&1 || fail "pnpm is required for app commands."
|
|
fi
|
|
}
|
|
|
|
########################################
|
|
# Env handling
|
|
########################################
|
|
ensure_env() {
|
|
if [ ! -f "$ENV_FILE" ]; then
|
|
warn "Environment file not found at $ENV_FILE"
|
|
if [ -f "$ENV_EXAMPLE_FILE" ]; then
|
|
log "Creating .env from example..."
|
|
cp "$ENV_EXAMPLE_FILE" "$ENV_FILE"
|
|
warn "Please edit $ENV_FILE with your actual values."
|
|
else
|
|
warn "No .env.example found at $ENV_EXAMPLE_FILE. Creating empty .env..."
|
|
: > "$ENV_FILE"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
load_env_exported() {
|
|
# Export so child processes see env (compose, pnpm etc.)
|
|
set +u
|
|
set -a
|
|
[ -f "$ENV_FILE" ] && . "$ENV_FILE" || true
|
|
set +a
|
|
set -u
|
|
}
|
|
|
|
########################################
|
|
# Helpers
|
|
########################################
|
|
services_running() {
|
|
compose ps | grep -q "Up"
|
|
}
|
|
|
|
wait_for_postgres() {
|
|
local user="${POSTGRES_USER:-$DB_USER_DEFAULT}"
|
|
local db="${POSTGRES_DB:-$DB_NAME_DEFAULT}"
|
|
local timeout="$DB_WAIT_SECS"
|
|
|
|
log "⏳ Waiting for database ($db) to be ready (timeout: ${timeout}s)..."
|
|
local elapsed=0
|
|
local step=2
|
|
until compose exec -T postgres pg_isready -U "$user" -d "$db" >/dev/null 2>&1; do
|
|
sleep "$step"
|
|
elapsed=$((elapsed + step))
|
|
if (( elapsed >= timeout )); then
|
|
fail "Database failed to become ready within ${timeout}s"
|
|
fi
|
|
done
|
|
log "✅ Database is ready!"
|
|
}
|
|
|
|
kill_by_port() {
|
|
local port="$1"
|
|
# Prefer lsof on macOS; fall back to fuser on Linux
|
|
if command -v lsof >/dev/null 2>&1; then
|
|
if lsof -tiTCP:"$port" -sTCP:LISTEN >/dev/null 2>&1; then
|
|
log " Killing process on port $port..."
|
|
lsof -tiTCP:"$port" -sTCP:LISTEN | xargs -r kill -9 2>/dev/null || true
|
|
fi
|
|
elif command -v fuser >/dev/null 2>&1; then
|
|
if fuser -n tcp "$port" >/dev/null 2>&1; then
|
|
log " Killing process on port $port..."
|
|
fuser -k -n tcp "$port" 2>/dev/null || true
|
|
fi
|
|
else
|
|
warn "Neither lsof nor fuser found; skipping port cleanup for $port."
|
|
fi
|
|
}
|
|
|
|
# Check if a port is free using Node (portable)
|
|
is_port_free() {
|
|
local port="$1"
|
|
if ! command -v node >/dev/null 2>&1; then
|
|
return 0 # assume free if node unavailable
|
|
fi
|
|
node -e "const net=require('net');const p=parseInt(process.argv[1],10);const s=net.createServer();s.once('error',()=>process.exit(1));s.once('listening',()=>s.close(()=>process.exit(0)));s.listen({port:p,host:'127.0.0.1'});" "$port"
|
|
}
|
|
|
|
# Find a free port starting from base, up to +50
|
|
find_free_port() {
|
|
local base="$1"
|
|
local limit=$((base+50))
|
|
local p="$base"
|
|
while [ "$p" -le "$limit" ]; do
|
|
if is_port_free "$p"; then
|
|
echo "$p"
|
|
return 0
|
|
fi
|
|
p=$((p+1))
|
|
done
|
|
echo "$base"
|
|
}
|
|
|
|
########################################
|
|
# Commands
|
|
########################################
|
|
start_services() {
|
|
preflight "start"
|
|
cd "$PROJECT_ROOT"
|
|
ensure_env
|
|
load_env_exported
|
|
|
|
log "🚀 Starting development services..."
|
|
log "🧩 Using compose: $COMPOSE_FILE"
|
|
compose up -d postgres redis
|
|
wait_for_postgres
|
|
|
|
local next="${NEXT_PORT:-$NEXT_PORT_DEFAULT}"
|
|
local bff="${BFF_PORT:-$BFF_PORT_DEFAULT}"
|
|
# Ensure desired ports are free; kill any listeners
|
|
kill_by_port "$next"
|
|
kill_by_port "$bff"
|
|
# If still busy, either auto-shift (if allowed) or fail
|
|
if ! is_port_free "$next"; then
|
|
if [ "${ALLOW_PORT_SHIFT:-0}" = "1" ]; then
|
|
local next_free
|
|
next_free="$(find_free_port "$next")"
|
|
warn "Port $next in use; assigning NEXT_PORT=$next_free"
|
|
export NEXT_PORT="$next_free"
|
|
next="$next_free"
|
|
else
|
|
fail "Port $next is in use. Stop the process or run '$0 cleanup'. Set ALLOW_PORT_SHIFT=1 to auto-assign another port."
|
|
fi
|
|
fi
|
|
if ! is_port_free "$bff"; then
|
|
if [ "${ALLOW_PORT_SHIFT:-0}" = "1" ]; then
|
|
local bff_free
|
|
bff_free="$(find_free_port "$bff")"
|
|
warn "Port $bff in use; assigning BFF_PORT=$bff_free"
|
|
export BFF_PORT="$bff_free"
|
|
bff="$bff_free"
|
|
else
|
|
fail "Port $bff is in use. Stop the process or run '$0 cleanup'. Set ALLOW_PORT_SHIFT=1 to auto-assign another port."
|
|
fi
|
|
fi
|
|
log "✅ Development services are running!"
|
|
log "🔗 Database: postgresql://${POSTGRES_USER:-$DB_USER_DEFAULT}:${POSTGRES_PASSWORD:-${POSTGRES_PASSWORD:-dev}}@localhost:5432/${POSTGRES_DB:-$DB_NAME_DEFAULT}"
|
|
log "🔗 Redis: redis://localhost:6379"
|
|
log "🔗 BFF API (expected): http://localhost:${bff}/api"
|
|
log "🔗 Frontend (expected): http://localhost:${next}"
|
|
}
|
|
|
|
start_with_tools() {
|
|
preflight "tools"
|
|
cd "$PROJECT_ROOT"
|
|
ensure_env
|
|
load_env_exported
|
|
|
|
log "🛠️ Starting development services with admin tools..."
|
|
log "🧩 Using compose: $COMPOSE_FILE"
|
|
# Explicitly start only services + admin tools to avoid building app containers in the full compose
|
|
compose --profile tools up -d postgres redis adminer redis-commander mailhog
|
|
wait_for_postgres
|
|
log "🔗 Database Admin: http://localhost:8080"
|
|
log "🔗 Redis Commander: http://localhost:8081"
|
|
}
|
|
|
|
stop_services() {
|
|
preflight "stop"
|
|
cd "$PROJECT_ROOT"
|
|
log "⏹️ Stopping development services..."
|
|
compose down --remove-orphans
|
|
log "✅ Services stopped"
|
|
}
|
|
|
|
show_status() {
|
|
preflight "status"
|
|
cd "$PROJECT_ROOT"
|
|
log "📊 Development Services Status:"
|
|
log "🧩 Using compose: $COMPOSE_FILE"
|
|
compose ps
|
|
}
|
|
|
|
show_logs() {
|
|
preflight "logs"
|
|
cd "$PROJECT_ROOT"
|
|
# Pass-through any service names after "logs"
|
|
# e.g. ./dev.sh logs postgres redis
|
|
log "🧩 Using compose: $COMPOSE_FILE"
|
|
compose logs -f --tail=100 "${@:2}"
|
|
}
|
|
|
|
cleanup_dev() {
|
|
log "🧹 Cleaning up all development processes and ports..."
|
|
|
|
# Pull ports from env if present; include common defaults
|
|
local ports=()
|
|
ports+=("${NEXT_PORT:-$NEXT_PORT_DEFAULT}")
|
|
ports+=("${BFF_PORT:-$BFF_PORT_DEFAULT}")
|
|
ports+=(5555) # Prisma Studio default
|
|
|
|
for p in "${ports[@]}"; do
|
|
kill_by_port "$p"
|
|
done
|
|
|
|
# Kill common dev processes by name
|
|
pkill -f "next dev" 2>/dev/null && log " Stopped Next.js dev server" || true
|
|
pkill -f "nest start --watch" 2>/dev/null && log " Stopped NestJS watch server" || true
|
|
pkill -f "next-server" 2>/dev/null && log " Stopped Next.js server process" || true
|
|
pkill -f "pnpm.*--parallel.*dev" 2>/dev/null && log " Stopped parallel dev processes" || true
|
|
pkill -f "prisma studio" 2>/dev/null && log " Stopped Prisma Studio" || true
|
|
pkill -f "apps/bff/scripts/dev-watch.sh" 2>/dev/null && log " Stopped BFF dev-watch script" || true
|
|
pkill -f "tsc -p tsconfig.build.json --watch" 2>/dev/null && log " Stopped BFF TypeScript watcher" || true
|
|
pkill -f "tsc-alias.*tsconfig.build.json.*-w" 2>/dev/null && log " Stopped BFF tsc-alias watcher" || true
|
|
pkill -f "node --watch dist/main.js" 2>/dev/null && log " Stopped BFF Node watcher" || true
|
|
|
|
sleep 1
|
|
log "✅ Development cleanup completed"
|
|
}
|
|
|
|
start_apps() {
|
|
preflight "apps"
|
|
cd "$PROJECT_ROOT"
|
|
|
|
cleanup_dev
|
|
|
|
if ! services_running; then
|
|
start_services
|
|
fi
|
|
|
|
load_env_exported
|
|
|
|
# Build shared package first
|
|
log "🔨 Building shared package..."
|
|
pnpm --filter @customer-portal/domain build
|
|
pnpm --filter @customer-portal/validation build
|
|
|
|
# Build BFF before watch (ensures dist exists). Use Nest build for correct emit.
|
|
log "🔨 Building BFF for initial setup (ts emit)..."
|
|
(
|
|
cd "$PROJECT_ROOT/apps/bff" \
|
|
&& pnpm clean \
|
|
&& rm -f tsconfig.build.tsbuildinfo \
|
|
&& pnpm build || pnpm exec tsc -b --force tsconfig.build.json
|
|
)
|
|
if [ ! -d "$PROJECT_ROOT/apps/bff/dist" ]; then
|
|
warn "BFF dist not found after build; forcing TypeScript emit..."
|
|
(cd "$PROJECT_ROOT/apps/bff" && pnpm exec tsc -b --force tsconfig.build.json)
|
|
fi
|
|
if [ ! -f "$PROJECT_ROOT/apps/bff/dist/main.js" ] && [ ! -f "$PROJECT_ROOT/apps/bff/dist/main.cjs" ]; then
|
|
warn "BFF main output not found; will rely on watch to produce it."
|
|
fi
|
|
|
|
local next="${NEXT_PORT:-$NEXT_PORT_DEFAULT}"
|
|
local bff="${BFF_PORT:-$BFF_PORT_DEFAULT}"
|
|
log "🎯 Starting development applications..."
|
|
log "🔗 BFF API: http://localhost:${bff}/api"
|
|
log "🔗 Frontend: http://localhost:${next}"
|
|
log "🔗 Database: postgresql://${POSTGRES_USER:-$DB_USER_DEFAULT}:${POSTGRES_PASSWORD:-${POSTGRES_PASSWORD:-dev}}@localhost:5432/${POSTGRES_DB:-$DB_NAME_DEFAULT}"
|
|
log "🔗 Redis: redis://localhost:6379"
|
|
log "📚 API Docs: http://localhost:${bff}/api/docs"
|
|
log "Starting apps with hot-reload..."
|
|
# Prisma Studio can be started manually with: pnpm db:studio
|
|
|
|
# Run portal + bff in parallel with hot reload
|
|
pnpm --parallel \
|
|
--filter @customer-portal/domain \
|
|
--filter @customer-portal/validation \
|
|
--filter @customer-portal/portal \
|
|
--filter @customer-portal/bff run dev
|
|
}
|
|
|
|
reset_env() {
|
|
preflight "reset"
|
|
cd "$PROJECT_ROOT"
|
|
log "🔄 Resetting development environment..."
|
|
compose down -v --remove-orphans
|
|
docker system prune -f
|
|
log "✅ Development environment reset"
|
|
}
|
|
|
|
migrate_db() {
|
|
preflight "migrate"
|
|
cd "$PROJECT_ROOT"
|
|
|
|
if ! compose ps postgres | grep -q "Up"; then
|
|
fail "Database service not running. Run '$0 start' or '$0 apps' first."
|
|
fi
|
|
|
|
load_env_exported
|
|
log "🗄️ Running database migrations..."
|
|
pnpm db:migrate
|
|
log "✅ Database migrations completed"
|
|
}
|
|
|
|
studio_db() {
|
|
preflight "studio"
|
|
cd "$PROJECT_ROOT"
|
|
|
|
if ! compose ps postgres | grep -q "Up"; then
|
|
fail "Database service not running. Run '$0 start' or '$0 apps' first."
|
|
fi
|
|
|
|
load_env_exported
|
|
log "🔍 Starting Prisma Studio..."
|
|
pnpm --filter @customer-portal/bff run db:studio
|
|
}
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
🔧 Development Environment Manager
|
|
|
|
Usage: $0 {command}
|
|
|
|
Commands:
|
|
start - Start dev services (PostgreSQL + Redis)
|
|
stop - Stop all dev services
|
|
restart - Restart dev services
|
|
status - Show service status
|
|
logs - Tail logs (optionally: specify services, e.g. '$0 logs postgres redis')
|
|
tools - Start services with admin tools
|
|
apps - Start services + run dev apps (auto-cleanup)
|
|
cleanup - Clean up local dev processes and ports
|
|
migrate - Run database migrations
|
|
studio - Open Prisma Studio database browser
|
|
reset - Reset development environment
|
|
help - Show this help
|
|
|
|
Environment overrides:
|
|
PROJECT_ROOT, COMPOSE_FILE, ENV_FILE, ENV_EXAMPLE_FILE, PROJECT_NAME
|
|
DEV_STACK (full|services|auto) # default: full
|
|
POSTGRES_USER, POSTGRES_DB, POSTGRES_PASSWORD, DB_WAIT_SECS
|
|
NEXT_PORT, BFF_PORT
|
|
EOF
|
|
}
|
|
|
|
########################################
|
|
# Main
|
|
########################################
|
|
cmd="${1:-help}"
|
|
case "$cmd" in
|
|
start) start_services ;;
|
|
stop) stop_services ;;
|
|
restart) stop_services; start_services ;;
|
|
status) show_status ;;
|
|
logs) show_logs "$@" ;;
|
|
tools) start_with_tools ;;
|
|
apps) start_apps ;;
|
|
cleanup) cleanup_dev ;;
|
|
migrate) migrate_db ;;
|
|
studio) studio_db ;;
|
|
reset) reset_env ;;
|
|
help|*) usage; exit 0 ;;
|
|
esac
|