- Updated typography for headings and paragraphs in AboutUsView. - Added animation effects for header and sections to improve user experience. - Refactored section headers to use new display styles. feat: Implement bilingual address handling in AddressConfirmation - Integrated JapanAddressForm for ZIP code lookup and bilingual address input. - Updated state management to handle bilingual addresses and validation. - Enhanced save functionality to support dual-write to WHMCS and Salesforce. fix: Adjust Japan Post address mapping to handle nullish values - Updated address mapping to use nullish coalescing for optional fields. - Ensured compatibility with API responses that may return null for certain fields. feat: Add ServiceCard component for displaying services - Created a flexible ServiceCard component with multiple variants (default, featured, minimal, bento). - Implemented accent color options and responsive design for better UI. - Added detailed props documentation and usage examples. chore: Clean up development scripts - Removed unnecessary build steps for validation package in manage.sh.
465 lines
14 KiB
Bash
Executable File
465 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
|
|
########################################
|
|
|
|
# Check that per-app env files exist
|
|
check_app_env() {
|
|
local missing=0
|
|
|
|
if [ ! -f "$PROJECT_ROOT/apps/bff/.env" ] && [ ! -f "$PROJECT_ROOT/apps/bff/.env.development" ]; then
|
|
warn "BFF env file not found. Create one with:"
|
|
warn " cp apps/bff/.env.example apps/bff/.env"
|
|
missing=1
|
|
fi
|
|
|
|
# Portal env is optional - Next.js has sensible defaults for dev
|
|
if [ ! -f "$PROJECT_ROOT/apps/portal/.env.local" ] && [ ! -f "$PROJECT_ROOT/apps/portal/.env.development" ]; then
|
|
log "💡 Portal env file not found (optional). Create one with:"
|
|
log " cp apps/portal/.env.example apps/portal/.env.local"
|
|
fi
|
|
|
|
if [ "$missing" -eq 1 ]; then
|
|
fail "Required env files missing. See messages above."
|
|
fi
|
|
}
|
|
|
|
# Load root .env for Docker Compose services (postgres, redis config)
|
|
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"
|
|
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"
|
|
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"
|
|
|
|
# Verify app env files exist before proceeding
|
|
check_app_env
|
|
|
|
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
|
|
|
|
# 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/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
|
|
|
|
Setup (first time):
|
|
cp apps/bff/.env.example apps/bff/.env
|
|
cp apps/portal/.env.example apps/portal/.env.local # optional
|
|
|
|
Environment overrides:
|
|
PROJECT_ROOT, COMPOSE_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
|