CLAUDE: Add native development workflow for instant startup and hot-reload
- Add dev-native.sh script for fast local development (10s vs 3-5min Docker builds) - Fix cookie security flags to respect APP_ENV (dev uses Secure=false, prod uses Secure=true) - Pin backend to Python 3.13 (3.14 not yet supported by pydantic-core) - Add comprehensive NATIVE_DEV_SETUP.md documentation - Update CLAUDE.md to recommend native dev workflow - Add .pids/ and .logs/ to .gitignore for native dev artifacts Benefits: - Instant startup (5-10 seconds) - Hot-reload enabled (backend + frontend) - Native debugging support - No Docker rebuilds needed - Discord OAuth works on localhost with proper cookie settings
This commit is contained in:
parent
701098881a
commit
50bd998ecf
4
.gitignore
vendored
4
.gitignore
vendored
@ -201,3 +201,7 @@ redis_data/
|
|||||||
.env.local
|
.env.local
|
||||||
.env.*.local
|
.env.*.local
|
||||||
.env.production
|
.env.production
|
||||||
|
|
||||||
|
# Native dev mode artifacts
|
||||||
|
.pids/
|
||||||
|
.logs/
|
||||||
|
|||||||
25
CLAUDE.md
25
CLAUDE.md
@ -88,14 +88,31 @@ strat-gameplay-webapp/
|
|||||||
└── scripts/env-switch.sh # Environment profile switcher
|
└── scripts/env-switch.sh # Environment profile switcher
|
||||||
```
|
```
|
||||||
|
|
||||||
## Quick Start (All-Docker Workflow)
|
## Quick Start
|
||||||
|
|
||||||
The entire stack runs in Docker with a single command. No local Python or Node.js required.
|
### 🚀 Recommended: Native Development (Fast)
|
||||||
|
|
||||||
> **⚠️ ALWAYS USE PROD MODE**: Discord OAuth does not work in dev mode due to cookie/CORS configuration. Since this system isn't live yet, always build and run with `prod` mode for testing.
|
For day-to-day development, use native mode for instant startup and hot-reload:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Production (optimized build) - USE THIS
|
# Start everything natively (5-10 seconds, hot-reload enabled)
|
||||||
|
./dev-native.sh start
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
./dev-native.sh logs
|
||||||
|
|
||||||
|
# Stop when done
|
||||||
|
./dev-native.sh stop
|
||||||
|
```
|
||||||
|
|
||||||
|
**See [NATIVE_DEV_SETUP.md](NATIVE_DEV_SETUP.md) for full setup instructions.**
|
||||||
|
|
||||||
|
### 🐳 Alternative: Docker Workflow
|
||||||
|
|
||||||
|
For testing production builds or Docker-specific scenarios:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Production (optimized build)
|
||||||
./start.sh prod
|
./start.sh prod
|
||||||
|
|
||||||
# Development - DO NOT USE (auth broken)
|
# Development - DO NOT USE (auth broken)
|
||||||
|
|||||||
255
NATIVE_DEV_SETUP.md
Normal file
255
NATIVE_DEV_SETUP.md
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
# Native Development Setup
|
||||||
|
|
||||||
|
Fast development workflow with instant startup, hot-reload, and no Docker rebuilds.
|
||||||
|
|
||||||
|
## Why Native Development?
|
||||||
|
|
||||||
|
**Problem**: Docker production builds take 3-5 minutes to rebuild on every change.
|
||||||
|
|
||||||
|
**Solution**: Run backend and frontend natively on your machine, use Docker only for Redis.
|
||||||
|
|
||||||
|
**Benefits**:
|
||||||
|
- ⚡ **Instant startup** (seconds, not minutes)
|
||||||
|
- 🔥 **Hot-reload** on file changes (backend + frontend)
|
||||||
|
- 🐛 **Native debugging** (attach debugger directly)
|
||||||
|
- 💾 **No rebuild overhead** (just save and refresh)
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
### 1. Install Dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# UV (Python package manager)
|
||||||
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
|
|
||||||
|
# Node.js 22+ (if not already installed)
|
||||||
|
# https://nodejs.org/
|
||||||
|
|
||||||
|
# Docker (for Redis only)
|
||||||
|
# Already installed if you've been using ./start.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Configure Discord OAuth (One-Time Setup)
|
||||||
|
|
||||||
|
You need to add `http://localhost:8000` as an allowed redirect URI in your Discord application.
|
||||||
|
|
||||||
|
**Steps**:
|
||||||
|
|
||||||
|
1. Go to [Discord Developer Portal](https://discord.com/developers/applications)
|
||||||
|
2. Select your application (ID: `1441192438055178420`)
|
||||||
|
3. Navigate to **OAuth2** → **General**
|
||||||
|
4. Under **Redirects**, click **Add Redirect**
|
||||||
|
5. Add: `http://localhost:8000/api/auth/discord/callback/server`
|
||||||
|
6. Click **Save Changes**
|
||||||
|
|
||||||
|
**Your redirect URIs should now include**:
|
||||||
|
- ✅ `http://localhost:8000/api/auth/discord/callback/server` (dev)
|
||||||
|
- ✅ `https://gameplay-demo.manticorum.com/api/auth/discord/callback/server` (prod)
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Option 1: New Native Dev Script (Recommended)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start everything (instant, hot-reload)
|
||||||
|
./dev-native.sh start
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
./dev-native.sh logs
|
||||||
|
|
||||||
|
# Stop everything
|
||||||
|
./dev-native.sh stop
|
||||||
|
|
||||||
|
# Restart everything
|
||||||
|
./dev-native.sh restart
|
||||||
|
```
|
||||||
|
|
||||||
|
**What it does**:
|
||||||
|
1. Starts Redis in Docker (seconds)
|
||||||
|
2. Starts backend natively with `uv run uvicorn --reload` (instant)
|
||||||
|
3. Starts frontend natively with `npm run dev` (instant)
|
||||||
|
|
||||||
|
**No rebuilds. Ever.**
|
||||||
|
|
||||||
|
### Option 2: Manual Setup (Full Control)
|
||||||
|
|
||||||
|
If you prefer to run each service in separate terminals:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Terminal 1: Redis (Docker)
|
||||||
|
docker compose up redis
|
||||||
|
|
||||||
|
# Terminal 2: Backend (native)
|
||||||
|
cd backend
|
||||||
|
uv run uvicorn app.main:socket_app --host 0.0.0.0 --port 8000 --reload
|
||||||
|
|
||||||
|
# Terminal 3: Frontend (native)
|
||||||
|
cd frontend-sba
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## What Changed?
|
||||||
|
|
||||||
|
### 1. Cookie Security Flags Now Respect `APP_ENV`
|
||||||
|
|
||||||
|
**Before**:
|
||||||
|
- Cookies always used `Secure=true` + `SameSite=none`
|
||||||
|
- Required HTTPS (prod URLs only)
|
||||||
|
- Broke on localhost
|
||||||
|
|
||||||
|
**After** (in `backend/app/utils/cookies.py`):
|
||||||
|
- `APP_ENV=development` → `Secure=false` + `SameSite=lax` (works on localhost)
|
||||||
|
- `APP_ENV=production` → `Secure=true` + `SameSite=none` (works cross-domain)
|
||||||
|
|
||||||
|
### 2. Environment Files Already Configured
|
||||||
|
|
||||||
|
The `.env.dev` files already have localhost URLs:
|
||||||
|
|
||||||
|
**Backend** (`backend/.env.dev`):
|
||||||
|
```bash
|
||||||
|
DISCORD_SERVER_REDIRECT_URI=http://localhost:8000/api/auth/discord/callback/server
|
||||||
|
FRONTEND_URL=http://localhost:3000
|
||||||
|
CORS_ORIGINS=["http://localhost:3000","http://localhost:3001"]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Frontend** (`frontend-sba/.env.dev`):
|
||||||
|
```bash
|
||||||
|
NUXT_PUBLIC_API_URL=http://localhost:8000
|
||||||
|
NUXT_PUBLIC_WS_URL=http://localhost:8000
|
||||||
|
NUXT_PUBLIC_DISCORD_REDIRECT_URI=http://localhost:3000/auth/callback
|
||||||
|
```
|
||||||
|
|
||||||
|
The `dev-native.sh` script automatically copies these to `.env` when you run it.
|
||||||
|
|
||||||
|
## Usage Patterns
|
||||||
|
|
||||||
|
### Typical Development Flow
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start once
|
||||||
|
./dev-native.sh start
|
||||||
|
|
||||||
|
# Make changes to backend/frontend code
|
||||||
|
# Changes auto-reload instantly!
|
||||||
|
|
||||||
|
# View logs if needed
|
||||||
|
./dev-native.sh logs
|
||||||
|
|
||||||
|
# Stop when done
|
||||||
|
./dev-native.sh stop
|
||||||
|
```
|
||||||
|
|
||||||
|
### Debugging
|
||||||
|
|
||||||
|
**Backend** (Python):
|
||||||
|
```bash
|
||||||
|
# Stop the background process
|
||||||
|
./dev-native.sh stop
|
||||||
|
|
||||||
|
# Run backend in foreground with debugger
|
||||||
|
cd backend
|
||||||
|
uv run python -m debugpy --listen 5678 -m uvicorn app.main:socket_app --host 0.0.0.0 --port 8000 --reload
|
||||||
|
|
||||||
|
# Attach your IDE's debugger to localhost:5678
|
||||||
|
```
|
||||||
|
|
||||||
|
**Frontend** (Vue):
|
||||||
|
```bash
|
||||||
|
# Browser DevTools work automatically with HMR
|
||||||
|
# Or use Vue DevTools browser extension
|
||||||
|
```
|
||||||
|
|
||||||
|
### Switching Between Native and Docker
|
||||||
|
|
||||||
|
**To Native**:
|
||||||
|
```bash
|
||||||
|
# Stop Docker services
|
||||||
|
./start.sh stop
|
||||||
|
|
||||||
|
# Start native
|
||||||
|
./dev-native.sh start
|
||||||
|
```
|
||||||
|
|
||||||
|
**To Docker** (for testing prod builds):
|
||||||
|
```bash
|
||||||
|
# Stop native
|
||||||
|
./dev-native.sh stop
|
||||||
|
|
||||||
|
# Start Docker prod
|
||||||
|
./start.sh prod
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Discord OAuth Still Fails
|
||||||
|
|
||||||
|
**Symptoms**: Redirect to Discord works, but callback fails with "Invalid redirect_uri"
|
||||||
|
|
||||||
|
**Fixes**:
|
||||||
|
1. Verify you added `http://localhost:8000/api/auth/discord/callback/server` to Discord app
|
||||||
|
2. Check `backend/.env` has `APP_ENV=development`
|
||||||
|
3. Check `backend/.env` has `DISCORD_SERVER_REDIRECT_URI=http://localhost:8000/api/auth/discord/callback/server`
|
||||||
|
4. Restart backend: `./dev-native.sh restart`
|
||||||
|
|
||||||
|
### Backend Won't Start
|
||||||
|
|
||||||
|
**Check logs**:
|
||||||
|
```bash
|
||||||
|
tail -f .logs/backend.log
|
||||||
|
```
|
||||||
|
|
||||||
|
**Common issues**:
|
||||||
|
- Port 8000 already in use: `lsof -i :8000` (kill the process)
|
||||||
|
- Missing Python dependencies: `cd backend && uv sync`
|
||||||
|
- Database connection error: Check PostgreSQL is running
|
||||||
|
|
||||||
|
### Frontend Won't Start
|
||||||
|
|
||||||
|
**Check logs**:
|
||||||
|
```bash
|
||||||
|
tail -f .logs/frontend.log
|
||||||
|
```
|
||||||
|
|
||||||
|
**Common issues**:
|
||||||
|
- Port 3000 already in use: `lsof -i :3000` (kill the process)
|
||||||
|
- Missing node_modules: `cd frontend-sba && npm ci`
|
||||||
|
- Build error: Delete `.nuxt` and `.output` folders, restart
|
||||||
|
|
||||||
|
### Redis Connection Error
|
||||||
|
|
||||||
|
**Symptoms**: Backend logs show "Redis connection refused"
|
||||||
|
|
||||||
|
**Fix**:
|
||||||
|
```bash
|
||||||
|
# Check Redis status
|
||||||
|
docker compose ps redis
|
||||||
|
|
||||||
|
# Restart Redis if needed
|
||||||
|
docker compose restart redis
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Comparison
|
||||||
|
|
||||||
|
| Method | Initial Start | After Code Change | Rebuild Time |
|
||||||
|
|--------|--------------|-------------------|--------------|
|
||||||
|
| **Native Dev** | 5-10 seconds | Instant (HMR) | Never |
|
||||||
|
| Docker Dev | 30-60 seconds | Instant (volume mount) | 2-3 minutes |
|
||||||
|
| Docker Prod | **3-5 minutes** | **3-5 minutes** | **3-5 minutes** |
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- **Redis still uses Docker** (lightweight, starts in seconds)
|
||||||
|
- **PostgreSQL** is external (on `10.10.0.42`), not affected
|
||||||
|
- `.pids/` and `.logs/` directories are gitignored
|
||||||
|
- Backend uses `uvicorn --reload` (watches Python files)
|
||||||
|
- Frontend uses `nuxt dev` (HMR for Vue files)
|
||||||
|
|
||||||
|
## When to Use Docker Prod Mode
|
||||||
|
|
||||||
|
Use `./start.sh prod` when:
|
||||||
|
- Testing production builds before deployment
|
||||||
|
- Debugging Docker-specific issues
|
||||||
|
- Testing HTTPS/cross-origin scenarios
|
||||||
|
- Running integration tests
|
||||||
|
|
||||||
|
For day-to-day development, use `./dev-native.sh start` for maximum speed.
|
||||||
1
backend/.python-version
Normal file
1
backend/.python-version
Normal file
@ -0,0 +1 @@
|
|||||||
|
3.13
|
||||||
@ -9,6 +9,7 @@ Date: 2025-11-27
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from fastapi import Response
|
from fastapi import Response
|
||||||
|
|
||||||
from app.config import get_settings
|
from app.config import get_settings
|
||||||
|
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
@ -45,27 +46,29 @@ def set_auth_cookies(
|
|||||||
|
|
||||||
Security settings:
|
Security settings:
|
||||||
- HttpOnly: Prevents XSS access to tokens
|
- HttpOnly: Prevents XSS access to tokens
|
||||||
- Secure: True (required for SameSite=None)
|
- Secure: Respects APP_ENV (false in dev, true in prod)
|
||||||
- SameSite=None: Required for Safari to send cookies with fetch/XHR requests
|
- SameSite: "lax" in dev (localhost), "none" in prod (cross-origin)
|
||||||
- Path: Limits cookie scope
|
- Path: Limits cookie scope
|
||||||
|
|
||||||
Note: Safari's ITP treats SameSite=Lax cookies as "not sent" for XHR/fetch
|
Note: In production with SameSite=None, Secure=true is required.
|
||||||
requests even on same-origin. SameSite=None with Secure=true fixes this.
|
In development (localhost), we use SameSite=Lax which doesn't require Secure.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
response: FastAPI Response object
|
response: FastAPI Response object
|
||||||
access_token: JWT access token
|
access_token: JWT access token
|
||||||
refresh_token: JWT refresh token
|
refresh_token: JWT refresh token
|
||||||
"""
|
"""
|
||||||
|
secure = is_secure_context()
|
||||||
|
samesite = "none" if secure else "lax"
|
||||||
|
|
||||||
# Access token - short-lived, sent to all requests (needed for SSR cookie forwarding)
|
# Access token - short-lived, sent to all requests (needed for SSR cookie forwarding)
|
||||||
# Using SameSite=None for Safari compatibility (requires Secure=true)
|
|
||||||
response.set_cookie(
|
response.set_cookie(
|
||||||
key=ACCESS_TOKEN_COOKIE,
|
key=ACCESS_TOKEN_COOKIE,
|
||||||
value=access_token,
|
value=access_token,
|
||||||
max_age=ACCESS_TOKEN_MAX_AGE,
|
max_age=ACCESS_TOKEN_MAX_AGE,
|
||||||
httponly=True,
|
httponly=True,
|
||||||
secure=True, # Required for SameSite=None
|
secure=secure,
|
||||||
samesite="none", # Safari requires this for fetch() to include cookies
|
samesite=samesite,
|
||||||
path="/",
|
path="/",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -75,8 +78,8 @@ def set_auth_cookies(
|
|||||||
value=refresh_token,
|
value=refresh_token,
|
||||||
max_age=REFRESH_TOKEN_MAX_AGE,
|
max_age=REFRESH_TOKEN_MAX_AGE,
|
||||||
httponly=True,
|
httponly=True,
|
||||||
secure=True, # Required for SameSite=None
|
secure=secure,
|
||||||
samesite="none", # Safari requires this for fetch() to include cookies
|
samesite=samesite,
|
||||||
path="/api/auth",
|
path="/api/auth",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
338
dev-native.sh
Executable file
338
dev-native.sh
Executable file
@ -0,0 +1,338 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Paper Dynasty Game Engine - Native Development Mode
|
||||||
|
#
|
||||||
|
# Fast development workflow - no Docker rebuilds, instant restarts, hot-reload
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./dev-native.sh start Start all services natively
|
||||||
|
# ./dev-native.sh stop Stop all services
|
||||||
|
# ./dev-native.sh logs Show logs from all services
|
||||||
|
# ./dev-native.sh restart Restart all services
|
||||||
|
#
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
print_status() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||||
|
print_success() { echo -e "${GREEN}[OK]${NC} $1"; }
|
||||||
|
print_warning() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||||
|
print_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||||
|
|
||||||
|
PID_DIR="$SCRIPT_DIR/.pids"
|
||||||
|
LOG_DIR="$SCRIPT_DIR/.logs"
|
||||||
|
|
||||||
|
# Create directories
|
||||||
|
mkdir -p "$PID_DIR" "$LOG_DIR"
|
||||||
|
|
||||||
|
# Check dependencies
|
||||||
|
check_dependencies() {
|
||||||
|
local missing=0
|
||||||
|
|
||||||
|
if ! command -v uv &> /dev/null; then
|
||||||
|
print_error "uv not found - install from https://docs.astral.sh/uv/"
|
||||||
|
missing=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v node &> /dev/null; then
|
||||||
|
print_error "node not found - install Node.js 22+"
|
||||||
|
missing=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v docker &> /dev/null; then
|
||||||
|
print_error "docker not found - needed for Redis"
|
||||||
|
missing=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $missing -eq 1 ]]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_success "All dependencies found"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Setup environment files
|
||||||
|
setup_env() {
|
||||||
|
print_status "Setting up development environment files..."
|
||||||
|
|
||||||
|
# Backend
|
||||||
|
if [[ ! -f "backend/.env" ]] || ! grep -q "localhost:8000" "backend/.env" 2>/dev/null; then
|
||||||
|
print_status "Copying backend/.env.dev -> backend/.env"
|
||||||
|
cp backend/.env.dev backend/.env
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Frontend
|
||||||
|
if [[ ! -f "frontend-sba/.env" ]] || ! grep -q "localhost:8000" "frontend-sba/.env" 2>/dev/null; then
|
||||||
|
print_status "Copying frontend-sba/.env.dev -> frontend-sba/.env"
|
||||||
|
cp frontend-sba/.env.dev frontend-sba/.env
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_success "Environment files ready"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Start Redis (Docker)
|
||||||
|
start_redis() {
|
||||||
|
print_status "Starting Redis (Docker)..."
|
||||||
|
|
||||||
|
if docker ps | grep -q "strat-gameplay-webapp-redis"; then
|
||||||
|
print_warning "Redis already running"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker compose up -d redis
|
||||||
|
|
||||||
|
# Wait for Redis to be healthy
|
||||||
|
local max_wait=30
|
||||||
|
local waited=0
|
||||||
|
|
||||||
|
while [[ $waited -lt $max_wait ]]; do
|
||||||
|
if docker compose ps redis 2>/dev/null | grep -q "healthy"; then
|
||||||
|
print_success "Redis is healthy"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
waited=$((waited + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
print_error "Redis failed to start"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Start Backend (native Python with uv)
|
||||||
|
start_backend() {
|
||||||
|
print_status "Starting backend (native uvicorn)..."
|
||||||
|
|
||||||
|
cd backend
|
||||||
|
|
||||||
|
# Check if backend is already running
|
||||||
|
if [[ -f "$PID_DIR/backend.pid" ]]; then
|
||||||
|
local pid=$(cat "$PID_DIR/backend.pid")
|
||||||
|
if ps -p "$pid" > /dev/null 2>&1; then
|
||||||
|
print_warning "Backend already running (PID: $pid)"
|
||||||
|
cd ..
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Start backend in background
|
||||||
|
nohup uv run python -m uvicorn app.main:socket_app \
|
||||||
|
--host 0.0.0.0 \
|
||||||
|
--port 8000 \
|
||||||
|
--reload \
|
||||||
|
> "$LOG_DIR/backend.log" 2>&1 &
|
||||||
|
|
||||||
|
local pid=$!
|
||||||
|
echo $pid > "$PID_DIR/backend.pid"
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
# Wait for backend to respond
|
||||||
|
local max_wait=10
|
||||||
|
local waited=0
|
||||||
|
|
||||||
|
while [[ $waited -lt $max_wait ]]; do
|
||||||
|
if curl -sf http://localhost:8000/api/health > /dev/null 2>&1; then
|
||||||
|
print_success "Backend started (PID: $pid)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
waited=$((waited + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
print_error "Backend failed to start - check logs: tail -f $LOG_DIR/backend.log"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Start Frontend (native npm)
|
||||||
|
start_frontend() {
|
||||||
|
print_status "Starting frontend (native nuxt dev)..."
|
||||||
|
|
||||||
|
cd frontend-sba
|
||||||
|
|
||||||
|
# Check if frontend is already running
|
||||||
|
if [[ -f "$PID_DIR/frontend.pid" ]]; then
|
||||||
|
local pid=$(cat "$PID_DIR/frontend.pid")
|
||||||
|
if ps -p "$pid" > /dev/null 2>&1; then
|
||||||
|
print_warning "Frontend already running (PID: $pid)"
|
||||||
|
cd ..
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure node_modules exists
|
||||||
|
if [[ ! -d "node_modules" ]]; then
|
||||||
|
print_status "Installing frontend dependencies..."
|
||||||
|
npm ci
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Start frontend in background
|
||||||
|
nohup npm run dev > "$LOG_DIR/frontend.log" 2>&1 &
|
||||||
|
|
||||||
|
local pid=$!
|
||||||
|
echo $pid > "$PID_DIR/frontend.pid"
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
# Wait for frontend to respond
|
||||||
|
local max_wait=30
|
||||||
|
local waited=0
|
||||||
|
|
||||||
|
while [[ $waited -lt $max_wait ]]; do
|
||||||
|
if curl -sf http://localhost:3000 > /dev/null 2>&1; then
|
||||||
|
print_success "Frontend started (PID: $pid)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
waited=$((waited + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
print_error "Frontend failed to start - check logs: tail -f $LOG_DIR/frontend.log"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Stop all services
|
||||||
|
stop_services() {
|
||||||
|
print_status "Stopping all services..."
|
||||||
|
|
||||||
|
# Stop backend
|
||||||
|
if [[ -f "$PID_DIR/backend.pid" ]]; then
|
||||||
|
local pid=$(cat "$PID_DIR/backend.pid")
|
||||||
|
if ps -p "$pid" > /dev/null 2>&1; then
|
||||||
|
print_status "Stopping backend (PID: $pid)..."
|
||||||
|
kill $pid
|
||||||
|
rm "$PID_DIR/backend.pid"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Stop frontend
|
||||||
|
if [[ -f "$PID_DIR/frontend.pid" ]]; then
|
||||||
|
local pid=$(cat "$PID_DIR/frontend.pid")
|
||||||
|
if ps -p "$pid" > /dev/null 2>&1; then
|
||||||
|
print_status "Stopping frontend (PID: $pid)..."
|
||||||
|
kill $pid
|
||||||
|
rm "$PID_DIR/frontend.pid"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Stop Redis
|
||||||
|
print_status "Stopping Redis..."
|
||||||
|
docker compose stop redis
|
||||||
|
|
||||||
|
print_success "All services stopped"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Show logs
|
||||||
|
show_logs() {
|
||||||
|
print_status "Showing logs (Ctrl+C to exit)..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Use multitail if available, otherwise tail
|
||||||
|
if command -v multitail &> /dev/null; then
|
||||||
|
multitail -s 2 \
|
||||||
|
-l "tail -f $LOG_DIR/backend.log" \
|
||||||
|
-l "tail -f $LOG_DIR/frontend.log"
|
||||||
|
else
|
||||||
|
print_warning "Install 'multitail' for better log viewing"
|
||||||
|
tail -f "$LOG_DIR/backend.log" "$LOG_DIR/frontend.log"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Start all services
|
||||||
|
start_all() {
|
||||||
|
print_status "Starting native development environment..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
check_dependencies
|
||||||
|
setup_env
|
||||||
|
|
||||||
|
start_redis || exit 1
|
||||||
|
start_backend || exit 1
|
||||||
|
start_frontend || exit 1
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
print_success "Development environment ready!"
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}========================================${NC}"
|
||||||
|
echo -e "${GREEN} Native Development Mode${NC}"
|
||||||
|
echo -e "${GREEN}========================================${NC}"
|
||||||
|
echo ""
|
||||||
|
echo " Backend API: http://localhost:8000"
|
||||||
|
echo " API Docs: http://localhost:8000/docs"
|
||||||
|
echo " Frontend: http://localhost:3000"
|
||||||
|
echo ""
|
||||||
|
echo " Features:"
|
||||||
|
echo " ✓ Hot-reload enabled (backend + frontend)"
|
||||||
|
echo " ✓ No Docker rebuilds"
|
||||||
|
echo " ✓ Instant restarts"
|
||||||
|
echo ""
|
||||||
|
echo " Logs:"
|
||||||
|
echo " Backend: tail -f $LOG_DIR/backend.log"
|
||||||
|
echo " Frontend: tail -f $LOG_DIR/frontend.log"
|
||||||
|
echo " All: ./dev-native.sh logs"
|
||||||
|
echo ""
|
||||||
|
echo " Commands:"
|
||||||
|
echo " ./dev-native.sh logs View logs"
|
||||||
|
echo " ./dev-native.sh stop Stop services"
|
||||||
|
echo " ./dev-native.sh restart Restart services"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Restart all services
|
||||||
|
restart_all() {
|
||||||
|
stop_services
|
||||||
|
sleep 2
|
||||||
|
start_all
|
||||||
|
}
|
||||||
|
|
||||||
|
# Show usage
|
||||||
|
show_usage() {
|
||||||
|
echo "Paper Dynasty - Native Development Mode"
|
||||||
|
echo ""
|
||||||
|
echo "Usage: ./dev-native.sh <command>"
|
||||||
|
echo ""
|
||||||
|
echo "Commands:"
|
||||||
|
echo " start Start all services natively (instant, hot-reload)"
|
||||||
|
echo " stop Stop all services"
|
||||||
|
echo " logs Tail logs from all services"
|
||||||
|
echo " restart Restart all services"
|
||||||
|
echo ""
|
||||||
|
echo "Benefits:"
|
||||||
|
echo " • No Docker rebuilds (saves minutes)"
|
||||||
|
echo " • Instant startup"
|
||||||
|
echo " • Hot-reload on file changes (backend + frontend)"
|
||||||
|
echo " • Native debugging support"
|
||||||
|
echo ""
|
||||||
|
echo "Requirements:"
|
||||||
|
echo " • uv (Python package manager)"
|
||||||
|
echo " • Node.js 22+"
|
||||||
|
echo " • Docker (for Redis only)"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main
|
||||||
|
case "${1:-}" in
|
||||||
|
start)
|
||||||
|
start_all
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
stop_services
|
||||||
|
;;
|
||||||
|
logs)
|
||||||
|
show_logs
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
restart_all
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
show_usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
Loading…
Reference in New Issue
Block a user