mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 19:41:09 +02:00
chore: remove staging/Hetzner infra, add Watchtower auto-deploy
- Remove old Hetzner deployment workflows (cd-staging, cd-production) - Remove staging docker-compose files - Remove outdated staging/Hetzner documentation - Add Watchtower to docker-compose.macmini.yml for auto-updates - Update CLAUDE.md with Mac Mini server access - Simplify docs/DEPLOYMENT.md for new architecture Production now runs on Mac Mini with automatic deployments via Watchtower. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
f47bf8edd9
commit
ac663a6c91
27 changed files with 104 additions and 15582 deletions
389
.github/workflows/cd-production.yml
vendored
389
.github/workflows/cd-production.yml
vendored
|
|
@ -1,389 +0,0 @@
|
|||
# Production Deployment
|
||||
#
|
||||
# Triggered by:
|
||||
# - Manual only (workflow_dispatch with confirmation)
|
||||
#
|
||||
# Flow: dev (staging) → main (production)
|
||||
# Requires typing "deploy" to confirm
|
||||
name: CD - Production Deployment
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
service:
|
||||
description: 'Service to deploy'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- all
|
||||
- mana-core-auth
|
||||
- maerchenzauber-backend
|
||||
- chat-backend
|
||||
- manadeck-backend
|
||||
- nutriphi-backend
|
||||
- news-api
|
||||
environment:
|
||||
description: 'Deployment environment'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- production
|
||||
confirm:
|
||||
description: 'Type "deploy" to confirm production deployment'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
env:
|
||||
NODE_VERSION: '20'
|
||||
PNPM_VERSION: '9.15.0'
|
||||
|
||||
jobs:
|
||||
validate-deployment:
|
||||
name: Validate Deployment Request
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Validate confirmation
|
||||
run: |
|
||||
if [ "${{ github.event.inputs.confirm }}" != "deploy" ]; then
|
||||
echo "❌ Deployment not confirmed. Please type 'deploy' to confirm."
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Deployment confirmed"
|
||||
|
||||
- name: Validate branch
|
||||
run: |
|
||||
if [ "${{ github.ref }}" != "refs/heads/main" ]; then
|
||||
echo "❌ Production deployments must be from main branch"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Deploying from main branch"
|
||||
|
||||
- name: Check recent commits
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 10
|
||||
|
||||
- name: Verify recent CI passes
|
||||
run: |
|
||||
echo "Checking recent CI status..."
|
||||
# This would check recent CI runs, simplified for now
|
||||
echo "✅ Recent CI checks verified"
|
||||
|
||||
# Request manual approval for production
|
||||
request-approval:
|
||||
name: Request Production Approval
|
||||
runs-on: ubuntu-latest
|
||||
needs: validate-deployment
|
||||
environment:
|
||||
name: production-approval
|
||||
steps:
|
||||
- name: Approval granted
|
||||
run: |
|
||||
echo "## Production Deployment Approved" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Approved by**: ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Service**: ${{ github.event.inputs.service }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Timestamp**: $(date -u +'%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Create deployment backup
|
||||
create-backup:
|
||||
name: Create Production Backup
|
||||
runs-on: ubuntu-latest
|
||||
needs: request-approval
|
||||
environment:
|
||||
name: production
|
||||
steps:
|
||||
- name: Setup SSH
|
||||
uses: webfactory/ssh-agent@v0.9.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.PRODUCTION_SSH_KEY }}
|
||||
|
||||
- name: Add production server to known hosts
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
ssh-keyscan -H ${{ secrets.PRODUCTION_HOST }} >> ~/.ssh/known_hosts
|
||||
|
||||
- name: Create database backup
|
||||
run: |
|
||||
ssh ${{ secrets.PRODUCTION_USER }}@${{ secrets.PRODUCTION_HOST }} << 'EOF'
|
||||
cd ~/manacore-production
|
||||
|
||||
# Backup timestamp
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_DIR="backups/$TIMESTAMP"
|
||||
mkdir -p $BACKUP_DIR
|
||||
|
||||
# Backup PostgreSQL
|
||||
docker compose exec -T postgres pg_dumpall -U $POSTGRES_USER > $BACKUP_DIR/postgres_backup.sql
|
||||
|
||||
# Backup Redis (if applicable)
|
||||
docker compose exec -T redis redis-cli SAVE || echo "Redis backup skipped"
|
||||
|
||||
# Backup docker-compose and env files
|
||||
cp docker-compose.yml $BACKUP_DIR/
|
||||
cp .env $BACKUP_DIR/.env.backup
|
||||
|
||||
echo "Backup created at: $BACKUP_DIR"
|
||||
ls -lh $BACKUP_DIR/
|
||||
EOF
|
||||
|
||||
- name: Tag current deployment
|
||||
run: |
|
||||
ssh ${{ secrets.PRODUCTION_USER }}@${{ secrets.PRODUCTION_HOST }} << 'EOF'
|
||||
cd ~/manacore-production
|
||||
docker compose images > deployment_images.txt
|
||||
echo "Current deployment tagged: $(date -u +'%Y-%m-%d %H:%M:%S UTC')"
|
||||
EOF
|
||||
|
||||
# Deploy to production
|
||||
deploy-production:
|
||||
name: Deploy to Production
|
||||
runs-on: ubuntu-latest
|
||||
needs: create-backup
|
||||
environment:
|
||||
name: production
|
||||
url: https://api.manacore.app
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup SSH
|
||||
uses: webfactory/ssh-agent@v0.9.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.PRODUCTION_SSH_KEY }}
|
||||
|
||||
- name: Add production server to known hosts
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
ssh-keyscan -H ${{ secrets.PRODUCTION_HOST }} >> ~/.ssh/known_hosts
|
||||
|
||||
- name: Copy deployment files
|
||||
run: |
|
||||
scp docker-compose.production.yml ${{ secrets.PRODUCTION_USER }}@${{ secrets.PRODUCTION_HOST }}:~/manacore-production/docker-compose.yml
|
||||
|
||||
- name: Update environment variables
|
||||
run: |
|
||||
# Create production env file from secrets
|
||||
cat > .env.production << EOF
|
||||
# Database
|
||||
POSTGRES_HOST=${{ secrets.PRODUCTION_POSTGRES_HOST }}
|
||||
POSTGRES_PORT=${{ secrets.PRODUCTION_POSTGRES_PORT }}
|
||||
POSTGRES_DB=${{ secrets.PRODUCTION_POSTGRES_DB }}
|
||||
POSTGRES_USER=${{ secrets.PRODUCTION_POSTGRES_USER }}
|
||||
POSTGRES_PASSWORD=${{ secrets.PRODUCTION_POSTGRES_PASSWORD }}
|
||||
|
||||
# Redis
|
||||
REDIS_HOST=${{ secrets.PRODUCTION_REDIS_HOST }}
|
||||
REDIS_PORT=${{ secrets.PRODUCTION_REDIS_PORT }}
|
||||
REDIS_PASSWORD=${{ secrets.PRODUCTION_REDIS_PASSWORD }}
|
||||
|
||||
# Mana Core Auth
|
||||
MANA_SERVICE_URL=${{ secrets.PRODUCTION_MANA_SERVICE_URL }}
|
||||
JWT_SECRET=${{ secrets.PRODUCTION_JWT_SECRET }}
|
||||
JWT_PUBLIC_KEY=${{ secrets.PRODUCTION_JWT_PUBLIC_KEY }}
|
||||
JWT_PRIVATE_KEY=${{ secrets.PRODUCTION_JWT_PRIVATE_KEY }}
|
||||
|
||||
# Supabase
|
||||
SUPABASE_URL=${{ secrets.PRODUCTION_SUPABASE_URL }}
|
||||
SUPABASE_ANON_KEY=${{ secrets.PRODUCTION_SUPABASE_ANON_KEY }}
|
||||
SUPABASE_SERVICE_ROLE_KEY=${{ secrets.PRODUCTION_SUPABASE_SERVICE_ROLE_KEY }}
|
||||
|
||||
# Azure OpenAI
|
||||
AZURE_OPENAI_ENDPOINT=${{ secrets.PRODUCTION_AZURE_OPENAI_ENDPOINT }}
|
||||
AZURE_OPENAI_API_KEY=${{ secrets.PRODUCTION_AZURE_OPENAI_API_KEY }}
|
||||
AZURE_OPENAI_API_VERSION=2024-12-01-preview
|
||||
|
||||
# Environment
|
||||
NODE_ENV=production
|
||||
EOF
|
||||
|
||||
scp .env.production ${{ secrets.PRODUCTION_USER }}@${{ secrets.PRODUCTION_HOST }}:~/manacore-production/.env
|
||||
rm .env.production
|
||||
|
||||
- name: Pull latest images
|
||||
run: |
|
||||
ssh ${{ secrets.PRODUCTION_USER }}@${{ secrets.PRODUCTION_HOST }} << 'EOF'
|
||||
cd ~/manacore-production
|
||||
docker compose pull
|
||||
EOF
|
||||
|
||||
- name: Run database migrations
|
||||
run: |
|
||||
ssh ${{ secrets.PRODUCTION_USER }}@${{ secrets.PRODUCTION_HOST }} << 'EOF'
|
||||
cd ~/manacore-production
|
||||
|
||||
echo "=== Running Database Migrations ==="
|
||||
echo ""
|
||||
|
||||
# Migration function with retry logic
|
||||
run_migration() {
|
||||
local service=$1
|
||||
local max_attempts=3
|
||||
local timeout=300 # 5 minutes
|
||||
local attempt=1
|
||||
|
||||
while [ $attempt -le $max_attempts ]; do
|
||||
echo "[$service] Migration attempt $attempt/$max_attempts..."
|
||||
|
||||
# Run migration with timeout using a temporary container
|
||||
if timeout $timeout docker compose run --rm $service pnpm run db:migrate 2>&1; then
|
||||
echo "✅ [$service] Migration succeeded"
|
||||
return 0
|
||||
else
|
||||
exit_code=$?
|
||||
if [ $exit_code -eq 124 ]; then
|
||||
echo "⚠️ [$service] Migration timeout after ${timeout}s"
|
||||
else
|
||||
echo "⚠️ [$service] Migration failed with exit code $exit_code"
|
||||
fi
|
||||
|
||||
attempt=$((attempt + 1))
|
||||
if [ $attempt -le $max_attempts ]; then
|
||||
wait_time=$((10 * attempt)) # Backoff: 10s, 20s, 30s
|
||||
echo " Waiting ${wait_time}s before retry..."
|
||||
sleep $wait_time
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo "❌ [$service] Migration failed after $max_attempts attempts"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Run migrations for mana-core-auth (central auth service)
|
||||
run_migration mana-core-auth || {
|
||||
echo "❌ mana-core-auth migration failed"
|
||||
echo "⚠️ Continuing with deployment - manual migration may be required"
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "✅ Migration step completed"
|
||||
EOF
|
||||
|
||||
- name: Deploy with zero-downtime
|
||||
run: |
|
||||
SERVICE="${{ github.event.inputs.service }}"
|
||||
|
||||
ssh ${{ secrets.PRODUCTION_USER }}@${{ secrets.PRODUCTION_HOST }} << EOF
|
||||
cd ~/manacore-production
|
||||
|
||||
if [ "$SERVICE" == "all" ]; then
|
||||
# Rolling update for all services
|
||||
for service in mana-core-auth maerchenzauber-backend chat-backend manadeck-backend nutriphi-backend news-api; do
|
||||
echo "Deploying \$service..."
|
||||
docker compose up -d --no-deps --scale \$service=2 \$service
|
||||
sleep 10
|
||||
docker compose up -d --no-deps --scale \$service=1 \$service
|
||||
done
|
||||
else
|
||||
# Single service deployment
|
||||
echo "Deploying $SERVICE..."
|
||||
docker compose up -d --no-deps $SERVICE
|
||||
fi
|
||||
|
||||
# Cleanup old images
|
||||
docker image prune -f
|
||||
EOF
|
||||
|
||||
- name: Verify deployment
|
||||
run: |
|
||||
# Wait for services to stabilize
|
||||
sleep 30
|
||||
|
||||
SERVICES=(
|
||||
"mana-core-auth:3001:/api/v1/health"
|
||||
"maerchenzauber-backend:3002:/health"
|
||||
"chat-backend:3002:/api/health"
|
||||
)
|
||||
|
||||
for SERVICE_CONFIG in "${SERVICES[@]}"; do
|
||||
IFS=':' read -r SERVICE PORT PATH <<< "$SERVICE_CONFIG"
|
||||
|
||||
echo "Verifying $SERVICE..."
|
||||
ssh ${{ secrets.PRODUCTION_USER }}@${{ secrets.PRODUCTION_HOST }} << EOF
|
||||
HEALTH=\$(docker compose -f ~/manacore-production/docker-compose.yml exec -T $SERVICE wget -q -O - http://localhost:$PORT$PATH || echo "FAILED")
|
||||
|
||||
if [[ "\$HEALTH" == *"FAILED"* ]]; then
|
||||
echo "❌ Health check failed for $SERVICE"
|
||||
docker compose -f ~/manacore-production/docker-compose.yml logs --tail=100 $SERVICE
|
||||
exit 1
|
||||
else
|
||||
echo "✅ Health check passed for $SERVICE"
|
||||
fi
|
||||
EOF
|
||||
done
|
||||
|
||||
- name: Monitor for 5 minutes
|
||||
run: |
|
||||
echo "Monitoring services for 5 minutes..."
|
||||
for i in {1..5}; do
|
||||
echo "Check $i/5..."
|
||||
sleep 60
|
||||
ssh ${{ secrets.PRODUCTION_USER }}@${{ secrets.PRODUCTION_HOST }} << 'EOF'
|
||||
cd ~/manacore-production
|
||||
docker compose ps
|
||||
EOF
|
||||
done
|
||||
echo "✅ Monitoring complete - services stable"
|
||||
|
||||
# Post-deployment verification
|
||||
post-deployment-checks:
|
||||
name: Post-Deployment Checks
|
||||
runs-on: ubuntu-latest
|
||||
needs: deploy-production
|
||||
steps:
|
||||
- name: Run smoke tests
|
||||
run: |
|
||||
# Test key endpoints
|
||||
ENDPOINTS=(
|
||||
"${{ secrets.PRODUCTION_API_URL }}/api/v1/health"
|
||||
"${{ secrets.PRODUCTION_API_URL }}/health"
|
||||
)
|
||||
|
||||
for ENDPOINT in "${ENDPOINTS[@]}"; do
|
||||
echo "Testing: $ENDPOINT"
|
||||
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" $ENDPOINT)
|
||||
|
||||
if [ "$RESPONSE" -eq 200 ]; then
|
||||
echo "✅ $ENDPOINT is healthy"
|
||||
else
|
||||
echo "❌ $ENDPOINT returned $RESPONSE"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Deployment summary
|
||||
run: |
|
||||
echo "## Production Deployment Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Environment**: Production" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Deployed by**: ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Service**: ${{ github.event.inputs.service }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Commit**: ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Timestamp**: $(date -u +'%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Deployment Status" >> $GITHUB_STEP_SUMMARY
|
||||
echo "✅ All services deployed and verified successfully" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Backup Information" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Pre-deployment backup created and stored" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Notify team
|
||||
notify-deployment:
|
||||
name: Notify Team
|
||||
runs-on: ubuntu-latest
|
||||
needs: post-deployment-checks
|
||||
if: always()
|
||||
steps:
|
||||
- name: Deployment notification
|
||||
run: |
|
||||
STATUS="${{ needs.post-deployment-checks.result }}"
|
||||
|
||||
if [ "$STATUS" == "success" ]; then
|
||||
echo "✅ Production deployment completed successfully"
|
||||
echo "Service: ${{ github.event.inputs.service }}"
|
||||
else
|
||||
echo "❌ Production deployment failed"
|
||||
echo "Please check logs and consider rollback"
|
||||
exit 1
|
||||
fi
|
||||
555
.github/workflows/cd-staging-tagged.yml
vendored
555
.github/workflows/cd-staging-tagged.yml
vendored
|
|
@ -1,555 +0,0 @@
|
|||
name: CD - Staging (Tagged Releases)
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
# Pattern: {project}-staging-v{version} or {project}-v{version}-staging
|
||||
# Examples: chat-staging-v1.0.0, picture-v2.1.0-staging, mana-core-auth-staging-v1.0.0
|
||||
# For multi-app: chat-all-staging-v1.0.0 (deploys backend + web + landing)
|
||||
- '*-staging-v*'
|
||||
- '*-v*-staging'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
project:
|
||||
description: 'Project to deploy'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- chat
|
||||
- picture
|
||||
- manadeck
|
||||
- zitare
|
||||
- presi
|
||||
- mana-core-auth
|
||||
- todo
|
||||
apps:
|
||||
description: 'Apps to deploy (comma-separated: backend,web,landing or "all")'
|
||||
required: true
|
||||
type: string
|
||||
default: 'backend'
|
||||
version:
|
||||
description: 'Version tag (e.g., v1.0.0)'
|
||||
required: false
|
||||
type: string
|
||||
default: 'latest'
|
||||
|
||||
env:
|
||||
NODE_VERSION: '20'
|
||||
PNPM_VERSION: '9.15.0'
|
||||
REGISTRY: ghcr.io
|
||||
# Note: repository_owner is lowercased for Docker compatibility
|
||||
IMAGE_PREFIX: ghcr.io/memo-2023
|
||||
|
||||
jobs:
|
||||
# Parse tag or inputs to determine what to deploy
|
||||
parse-deployment:
|
||||
name: Parse Deployment Target
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
project: ${{ steps.parse.outputs.project }}
|
||||
version: ${{ steps.parse.outputs.version }}
|
||||
matrix: ${{ steps.matrix.outputs.matrix }}
|
||||
steps:
|
||||
- name: Parse tag or inputs
|
||||
id: parse
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" == "push" ]; then
|
||||
# Parse from tag: {project}-staging-v{version} or {project}-v{version}-staging
|
||||
# Also supports: {project}-all-staging-v{version} for multi-app deploy
|
||||
TAG="${GITHUB_REF#refs/tags/}"
|
||||
echo "Parsing tag: $TAG"
|
||||
|
||||
# Extract project, app hint, and version from tag
|
||||
if [[ "$TAG" =~ ^(.+)-all-staging-v(.+)$ ]]; then
|
||||
PROJECT="${BASH_REMATCH[1]}"
|
||||
VERSION="v${BASH_REMATCH[2]}"
|
||||
APPS="all"
|
||||
elif [[ "$TAG" =~ ^(.+)-staging-v(.+)$ ]]; then
|
||||
PROJECT="${BASH_REMATCH[1]}"
|
||||
VERSION="v${BASH_REMATCH[2]}"
|
||||
APPS="backend"
|
||||
elif [[ "$TAG" =~ ^(.+)-v(.+)-staging$ ]]; then
|
||||
PROJECT="${BASH_REMATCH[1]}"
|
||||
VERSION="v${BASH_REMATCH[2]}"
|
||||
APPS="backend"
|
||||
else
|
||||
echo "Invalid tag format: $TAG"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
# Use workflow dispatch inputs
|
||||
PROJECT="${{ github.event.inputs.project }}"
|
||||
APPS="${{ github.event.inputs.apps }}"
|
||||
VERSION="${{ github.event.inputs.version }}"
|
||||
fi
|
||||
|
||||
echo "Project: $PROJECT"
|
||||
echo "Apps: $APPS"
|
||||
echo "Version: $VERSION"
|
||||
|
||||
echo "project=$PROJECT" >> $GITHUB_OUTPUT
|
||||
echo "apps=$APPS" >> $GITHUB_OUTPUT
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Generate build matrix
|
||||
id: matrix
|
||||
run: |
|
||||
PROJECT="${{ steps.parse.outputs.project }}"
|
||||
APPS="${{ steps.parse.outputs.apps }}"
|
||||
VERSION="${{ steps.parse.outputs.version }}"
|
||||
|
||||
# Define available apps per project
|
||||
declare -A PROJECT_APPS
|
||||
PROJECT_APPS[chat]="backend,web,landing"
|
||||
PROJECT_APPS[picture]="backend,web,landing"
|
||||
PROJECT_APPS[manadeck]="backend,web"
|
||||
PROJECT_APPS[zitare]="backend,web"
|
||||
PROJECT_APPS[presi]="backend,web"
|
||||
PROJECT_APPS[mana-core-auth]="service"
|
||||
PROJECT_APPS[todo]="backend,web"
|
||||
|
||||
# Expand "all" to available apps
|
||||
if [ "$APPS" == "all" ]; then
|
||||
APPS="${PROJECT_APPS[$PROJECT]}"
|
||||
fi
|
||||
|
||||
# Build JSON matrix
|
||||
MATRIX='{"include":['
|
||||
FIRST=true
|
||||
|
||||
IFS=',' read -ra APP_ARRAY <<< "$APPS"
|
||||
for APP in "${APP_ARRAY[@]}"; do
|
||||
APP=$(echo "$APP" | xargs) # Trim whitespace
|
||||
|
||||
# Determine paths based on project and app
|
||||
case "$PROJECT" in
|
||||
mana-core-auth)
|
||||
DOCKERFILE_PATH="services/mana-core-auth/Dockerfile"
|
||||
CONTEXT_PATH="."
|
||||
IMAGE_NAME="mana-core-auth"
|
||||
PORT="3001"
|
||||
HEALTH_PATH="/api/v1/health"
|
||||
;;
|
||||
*)
|
||||
case "$APP" in
|
||||
backend|service)
|
||||
DOCKERFILE_PATH="apps/$PROJECT/apps/backend/Dockerfile"
|
||||
CONTEXT_PATH="."
|
||||
IMAGE_NAME="${PROJECT}-backend"
|
||||
;;
|
||||
web)
|
||||
# Apps with their own Dockerfiles (need monorepo root for shared packages)
|
||||
case "$PROJECT" in
|
||||
manacore|todo|calendar|clock)
|
||||
DOCKERFILE_PATH="apps/$PROJECT/apps/web/Dockerfile"
|
||||
CONTEXT_PATH="."
|
||||
;;
|
||||
*)
|
||||
DOCKERFILE_PATH="docker/templates/Dockerfile.sveltekit"
|
||||
CONTEXT_PATH="apps/$PROJECT/apps/web"
|
||||
;;
|
||||
esac
|
||||
IMAGE_NAME="${PROJECT}-web"
|
||||
;;
|
||||
landing)
|
||||
DOCKERFILE_PATH="docker/templates/Dockerfile.astro"
|
||||
CONTEXT_PATH="apps/$PROJECT/apps/landing"
|
||||
IMAGE_NAME="${PROJECT}-landing"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Set backend ports per project (must match docker-compose.staging.yml)
|
||||
case "$PROJECT" in
|
||||
chat) PORT="3002" ;;
|
||||
picture) PORT="3006" ;;
|
||||
manadeck) PORT="3009" ;;
|
||||
zitare) PORT="3007" ;;
|
||||
presi) PORT="3008" ;;
|
||||
todo) PORT="3018" ;;
|
||||
esac
|
||||
|
||||
# Override ports for web apps (SvelteKit uses different ports)
|
||||
if [ "$APP" == "web" ]; then
|
||||
case "$PROJECT" in
|
||||
manacore) PORT="5173" ;;
|
||||
todo) PORT="5188" ;;
|
||||
calendar) PORT="5186" ;;
|
||||
clock) PORT="5187" ;;
|
||||
*) PORT="5173" ;; # default SvelteKit port
|
||||
esac
|
||||
fi
|
||||
HEALTH_PATH="/api/v1/health"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$FIRST" = true ]; then
|
||||
FIRST=false
|
||||
else
|
||||
MATRIX+=','
|
||||
fi
|
||||
|
||||
MATRIX+="{\"app\":\"$APP\",\"image_name\":\"$IMAGE_NAME\",\"dockerfile_path\":\"$DOCKERFILE_PATH\",\"context_path\":\"$CONTEXT_PATH\",\"port\":\"$PORT\",\"health_path\":\"$HEALTH_PATH\"}"
|
||||
done
|
||||
|
||||
MATRIX+=']}'
|
||||
|
||||
echo "Generated matrix: $MATRIX"
|
||||
echo "matrix=$MATRIX" >> $GITHUB_OUTPUT
|
||||
|
||||
# Build and push Docker images (parallel for multi-app)
|
||||
build:
|
||||
name: Build ${{ matrix.image_name }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: parse-deployment
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJSON(needs.parse-deployment.outputs.matrix) }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check Dockerfile exists
|
||||
id: check
|
||||
run: |
|
||||
if [ -f "${{ matrix.dockerfile_path }}" ]; then
|
||||
echo "exists=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "Dockerfile not found: ${{ matrix.dockerfile_path }}"
|
||||
echo "exists=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: steps.check.outputs.exists == 'true'
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: steps.check.outputs.exists == 'true'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata
|
||||
if: steps.check.outputs.exists == 'true'
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.IMAGE_PREFIX }}/${{ matrix.image_name }}
|
||||
tags: |
|
||||
type=raw,value=${{ needs.parse-deployment.outputs.version }}
|
||||
type=raw,value=staging-latest
|
||||
type=sha,prefix=staging-
|
||||
|
||||
- name: Build and push
|
||||
if: steps.check.outputs.exists == 'true'
|
||||
id: build
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ${{ matrix.context_path }}
|
||||
file: ${{ matrix.dockerfile_path }}
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
build-args: |
|
||||
NODE_ENV=staging
|
||||
|
||||
- name: Build summary
|
||||
run: |
|
||||
echo "## Build: ${{ matrix.image_name }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Project**: ${{ needs.parse-deployment.outputs.project }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **App**: ${{ matrix.app }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Version**: ${{ needs.parse-deployment.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Image**: ${{ env.IMAGE_PREFIX }}/${{ matrix.image_name }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Tags**: ${{ steps.meta.outputs.tags }}" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Deploy to staging (parallel for multi-app)
|
||||
deploy:
|
||||
name: Deploy ${{ matrix.image_name }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: [parse-deployment, build]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJSON(needs.parse-deployment.outputs.matrix) }}
|
||||
environment:
|
||||
name: staging
|
||||
url: https://staging.manacore.app
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup SSH
|
||||
uses: webfactory/ssh-agent@v0.9.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
|
||||
|
||||
- name: Add staging server to known hosts
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
ssh-keyscan -H ${{ secrets.STAGING_HOST }} >> ~/.ssh/known_hosts
|
||||
|
||||
- name: Sync docker-compose to staging
|
||||
run: |
|
||||
# Ensure staging directory exists
|
||||
ssh ${{ secrets.STAGING_USER }}@${{ secrets.STAGING_HOST }} "mkdir -p ~/manacore-staging"
|
||||
# Copy the docker-compose file
|
||||
scp docker-compose.staging.yml ${{ secrets.STAGING_USER }}@${{ secrets.STAGING_HOST }}:~/manacore-staging/docker-compose.yml
|
||||
|
||||
- name: Login to GHCR on staging server
|
||||
run: |
|
||||
ssh ${{ secrets.STAGING_USER }}@${{ secrets.STAGING_HOST }} << EOF
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
||||
EOF
|
||||
|
||||
- name: Deploy service
|
||||
env:
|
||||
VERSION: ${{ needs.parse-deployment.outputs.version }}
|
||||
IMAGE_NAME: ${{ matrix.image_name }}
|
||||
APP_TYPE: ${{ matrix.app }}
|
||||
PROJECT: ${{ needs.parse-deployment.outputs.project }}
|
||||
run: |
|
||||
# Compute the version variable name locally (before SSH)
|
||||
# Map: todo-web -> TODO_WEB_VERSION, chat-backend -> CHAT_VERSION
|
||||
case "$IMAGE_NAME" in
|
||||
*-web)
|
||||
PROJECT_UPPER=$(echo "$PROJECT" | tr '[:lower:]-' '[:upper:]_')
|
||||
VERSION_VAR="${PROJECT_UPPER}_WEB_VERSION"
|
||||
;;
|
||||
*-backend)
|
||||
PROJECT_UPPER=$(echo "$PROJECT" | tr '[:lower:]-' '[:upper:]_')
|
||||
VERSION_VAR="${PROJECT_UPPER}_VERSION"
|
||||
;;
|
||||
mana-core-auth)
|
||||
VERSION_VAR="AUTH_VERSION"
|
||||
;;
|
||||
*)
|
||||
VERSION_VAR=$(echo "$IMAGE_NAME" | tr '[:lower:]-' '[:upper:]_')_VERSION
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "Will set $VERSION_VAR=$VERSION for docker-compose"
|
||||
|
||||
ssh ${{ secrets.STAGING_USER }}@${{ secrets.STAGING_HOST }} << EOF
|
||||
cd ~/manacore-staging
|
||||
|
||||
echo "Deploying $IMAGE_NAME:$VERSION to staging..."
|
||||
|
||||
# Pull the new image with specific version tag
|
||||
docker pull ${{ env.IMAGE_PREFIX }}/$IMAGE_NAME:$VERSION
|
||||
|
||||
# Update .env file with the version for this service
|
||||
# This ensures docker-compose uses the correct image tag
|
||||
if grep -q "^$VERSION_VAR=" .env 2>/dev/null; then
|
||||
sed -i "s/^$VERSION_VAR=.*/$VERSION_VAR=$VERSION/" .env
|
||||
else
|
||||
echo "Service \$SERVICE_NAME not found in compose, starting..."
|
||||
docker compose up -d --force-recreate \$SERVICE_NAME
|
||||
fi
|
||||
|
||||
echo "Updated .env: $VERSION_VAR=$VERSION"
|
||||
grep "$VERSION_VAR" .env || true
|
||||
|
||||
# Service name matches docker-compose service name (with hyphens)
|
||||
SERVICE_NAME="$IMAGE_NAME"
|
||||
CONTAINER_NAME="${IMAGE_NAME}-staging"
|
||||
|
||||
# Remove any stale container with the same name (prevents "name already in use" error)
|
||||
if docker ps -a --format '{{.Names}}' | grep -q "^\$CONTAINER_NAME\$"; then
|
||||
echo "Removing stale container: \$CONTAINER_NAME"
|
||||
docker rm -f \$CONTAINER_NAME 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Always use --force-recreate to ensure the new image is used
|
||||
echo "Deploying service: \$SERVICE_NAME"
|
||||
docker compose up -d --no-deps --force-recreate \$SERVICE_NAME
|
||||
|
||||
# Wait for startup
|
||||
sleep 10
|
||||
docker compose ps \$SERVICE_NAME
|
||||
|
||||
# Verify correct image is running
|
||||
echo "Running image:"
|
||||
docker inspect --format='{{.Config.Image}}' ${IMAGE_NAME}-staging 2>/dev/null || true
|
||||
|
||||
# Cleanup old images
|
||||
docker image prune -f
|
||||
EOF
|
||||
|
||||
- name: Health check
|
||||
if: matrix.app == 'backend' || matrix.app == 'service'
|
||||
run: |
|
||||
PORT="${{ matrix.port }}"
|
||||
HEALTH_PATH="${{ matrix.health_path }}"
|
||||
|
||||
echo "Running health check on port $PORT$HEALTH_PATH..."
|
||||
|
||||
ssh ${{ secrets.STAGING_USER }}@${{ secrets.STAGING_HOST }} << EOF
|
||||
for i in {1..5}; do
|
||||
RESPONSE=\$(curl -s -o /dev/null -w "%{http_code}" http://localhost:$PORT$HEALTH_PATH || echo "000")
|
||||
if [ "\$RESPONSE" == "200" ]; then
|
||||
echo "Health check passed (attempt \$i)"
|
||||
exit 0
|
||||
fi
|
||||
echo "Health check failed (attempt \$i), response: \$RESPONSE"
|
||||
sleep 5
|
||||
done
|
||||
echo "Health check failed after 5 attempts"
|
||||
exit 1
|
||||
EOF
|
||||
|
||||
- name: Deployment summary
|
||||
run: |
|
||||
echo "## Deploy: ${{ matrix.image_name }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Environment**: Staging" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Project**: ${{ needs.parse-deployment.outputs.project }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **App**: ${{ matrix.app }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Version**: ${{ needs.parse-deployment.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Image**: ${{ env.IMAGE_PREFIX }}/${{ matrix.image_name }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Deployed by**: ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Timestamp**: $(date -u +'%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Run database migrations after deploy
|
||||
migrations:
|
||||
name: Database Migrations
|
||||
runs-on: ubuntu-latest
|
||||
needs: [parse-deployment, deploy]
|
||||
# Only run for projects with backends (not manacore which is web-only)
|
||||
if: needs.parse-deployment.outputs.project != 'manacore'
|
||||
steps:
|
||||
- name: Setup SSH
|
||||
uses: webfactory/ssh-agent@v0.9.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
|
||||
|
||||
- name: Add staging server to known hosts
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
ssh-keyscan -H ${{ secrets.STAGING_HOST }} >> ~/.ssh/known_hosts
|
||||
|
||||
- name: Run database migrations
|
||||
env:
|
||||
PROJECT: ${{ needs.parse-deployment.outputs.project }}
|
||||
run: |
|
||||
# Determine service name based on project
|
||||
case "$PROJECT" in
|
||||
mana-core-auth)
|
||||
SERVICE_NAME="mana-core-auth"
|
||||
;;
|
||||
*)
|
||||
SERVICE_NAME="${PROJECT}-backend"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "Running database migrations for $SERVICE_NAME..."
|
||||
|
||||
ssh ${{ secrets.STAGING_USER }}@${{ secrets.STAGING_HOST }} << EOF
|
||||
cd ~/manacore-staging
|
||||
|
||||
echo "=== Database Migration for $SERVICE_NAME ==="
|
||||
|
||||
# Check if service is running
|
||||
if ! docker compose ps $SERVICE_NAME --format '{{.State}}' 2>/dev/null | grep -q "running"; then
|
||||
echo "⚠️ Service $SERVICE_NAME is not running, skipping migrations"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Migration function with retry logic
|
||||
run_db_push() {
|
||||
local service=\$1
|
||||
local max_attempts=3
|
||||
local timeout=120 # 2 minutes
|
||||
local attempt=1
|
||||
|
||||
while [ \$attempt -le \$max_attempts ]; do
|
||||
echo "[\$service] db:push attempt \$attempt/\$max_attempts..."
|
||||
|
||||
# Try db:push with timeout (staging uses push, not migrate)
|
||||
if timeout \$timeout docker compose exec -T \$service pnpm run db:push 2>&1; then
|
||||
echo "✅ [\$service] Database schema pushed successfully"
|
||||
return 0
|
||||
else
|
||||
exit_code=\$?
|
||||
if [ \$exit_code -eq 124 ]; then
|
||||
echo "⚠️ [\$service] db:push timeout after \${timeout}s"
|
||||
else
|
||||
echo "⚠️ [\$service] db:push failed with exit code \$exit_code"
|
||||
fi
|
||||
|
||||
attempt=\$((attempt + 1))
|
||||
if [ \$attempt -le \$max_attempts ]; then
|
||||
wait_time=\$((5 * attempt)) # Backoff: 5s, 10s, 15s
|
||||
echo " Waiting \${wait_time}s before retry..."
|
||||
sleep \$wait_time
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo "❌ [\$service] db:push failed after \$max_attempts attempts"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Run db:push for the service
|
||||
run_db_push $SERVICE_NAME || {
|
||||
echo "❌ Database migration failed for $SERVICE_NAME"
|
||||
echo "⚠️ You may need to run migrations manually:"
|
||||
echo " ssh deploy@\${{ secrets.STAGING_HOST }} 'cd ~/manacore-staging && docker compose exec -T $SERVICE_NAME pnpm run db:push'"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "✅ Database migrations completed for $SERVICE_NAME"
|
||||
EOF
|
||||
|
||||
- name: Migration summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## Database Migrations" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Project**: ${{ needs.parse-deployment.outputs.project }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Status**: ${{ job.status }}" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Notify on completion
|
||||
notify:
|
||||
name: Deployment Complete
|
||||
runs-on: ubuntu-latest
|
||||
needs: [parse-deployment, build, deploy, migrations]
|
||||
if: always()
|
||||
steps:
|
||||
- name: Deployment notification
|
||||
run: |
|
||||
BUILD_STATUS="${{ needs.build.result }}"
|
||||
DEPLOY_STATUS="${{ needs.deploy.result }}"
|
||||
MIGRATION_STATUS="${{ needs.migrations.result }}"
|
||||
PROJECT="${{ needs.parse-deployment.outputs.project }}"
|
||||
VERSION="${{ needs.parse-deployment.outputs.version }}"
|
||||
|
||||
echo "## Staging Deployment Complete" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Stage | Status |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Build | $BUILD_STATUS |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Deploy | $DEPLOY_STATUS |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Migrations | $MIGRATION_STATUS |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Project**: $PROJECT" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Version**: $VERSION" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Check all stages (migrations can be skipped for web-only projects)
|
||||
if [ "$BUILD_STATUS" == "success" ] && [ "$DEPLOY_STATUS" == "success" ]; then
|
||||
if [ "$MIGRATION_STATUS" == "success" ] || [ "$MIGRATION_STATUS" == "skipped" ]; then
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "All stages completed successfully" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "⚠️ Migrations failed - database may need manual update" >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Some deployments failed - check individual job logs" >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
371
.github/workflows/cd-staging.yml
vendored
371
.github/workflows/cd-staging.yml
vendored
|
|
@ -1,371 +0,0 @@
|
|||
# Staging Deployment
|
||||
#
|
||||
# Triggered by:
|
||||
# - Automatic: Push to dev branch (via ci.yml)
|
||||
# - Manual: workflow_dispatch
|
||||
#
|
||||
# Full config archived at: .github/workflows/cd-staging.full.yml
|
||||
#
|
||||
# To add a service:
|
||||
# 1. Add service to workflow_dispatch options
|
||||
# 2. Add health check in "Run health checks" step
|
||||
# 3. Add service to docker-compose.staging.yml
|
||||
name: CD - Staging Deployment
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
service:
|
||||
description: 'Service to deploy (leave empty for all)'
|
||||
required: false
|
||||
type: choice
|
||||
options:
|
||||
- all
|
||||
- mana-core-auth
|
||||
- chat-backend
|
||||
- chat-web
|
||||
- manacore-web
|
||||
- todo-backend
|
||||
- todo-web
|
||||
- calendar-backend
|
||||
- calendar-web
|
||||
- clock-backend
|
||||
- clock-web
|
||||
- telegram-stats-bot
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
|
||||
env:
|
||||
NODE_VERSION: '20'
|
||||
PNPM_VERSION: '9.15.0'
|
||||
|
||||
jobs:
|
||||
deploy-staging:
|
||||
name: Deploy to Staging
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: staging
|
||||
url: https://staging.manacore.app
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup SSH for deployment
|
||||
uses: webfactory/ssh-agent@v0.9.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
|
||||
|
||||
- name: Add staging server to known hosts
|
||||
env:
|
||||
STAGING_HOST: 46.224.108.214
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
ssh-keyscan -H $STAGING_HOST >> ~/.ssh/known_hosts
|
||||
|
||||
- name: Prepare deployment directory
|
||||
env:
|
||||
STAGING_USER: deploy
|
||||
STAGING_HOST: 46.224.108.214
|
||||
run: |
|
||||
ssh $STAGING_USER@$STAGING_HOST << 'EOF'
|
||||
mkdir -p ~/manacore-staging
|
||||
cd ~/manacore-staging
|
||||
|
||||
# Create required directories
|
||||
mkdir -p logs
|
||||
mkdir -p data/postgres
|
||||
mkdir -p data/redis
|
||||
EOF
|
||||
|
||||
- name: Copy docker-compose file
|
||||
env:
|
||||
STAGING_USER: deploy
|
||||
STAGING_HOST: 46.224.108.214
|
||||
run: |
|
||||
scp docker-compose.staging.yml $STAGING_USER@$STAGING_HOST:~/manacore-staging/docker-compose.yml
|
||||
|
||||
- name: Copy environment file
|
||||
env:
|
||||
STAGING_USER: deploy
|
||||
STAGING_HOST: 46.224.108.214
|
||||
run: |
|
||||
# Create staging env file (mix of hardcoded config and secrets)
|
||||
cat > .env.staging << EOF
|
||||
# Database - Configuration
|
||||
POSTGRES_HOST=postgres
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_DB=manacore
|
||||
POSTGRES_USER=postgres
|
||||
POSTGRES_PASSWORD=${{ secrets.STAGING_POSTGRES_PASSWORD }}
|
||||
|
||||
# Redis - Configuration
|
||||
REDIS_HOST=redis
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=${{ secrets.STAGING_REDIS_PASSWORD }}
|
||||
|
||||
# Mana Core Auth - Configuration
|
||||
MANA_SERVICE_URL=http://mana-core-auth:3001
|
||||
JWT_SECRET=${{ secrets.STAGING_JWT_SECRET }}
|
||||
JWT_PUBLIC_KEY=${{ secrets.STAGING_JWT_PUBLIC_KEY }}
|
||||
JWT_PRIVATE_KEY=${{ secrets.STAGING_JWT_PRIVATE_KEY }}
|
||||
|
||||
# Supabase
|
||||
SUPABASE_URL=${{ secrets.STAGING_SUPABASE_URL }}
|
||||
SUPABASE_ANON_KEY=${{ secrets.STAGING_SUPABASE_ANON_KEY }}
|
||||
SUPABASE_SERVICE_ROLE_KEY=${{ secrets.STAGING_SUPABASE_SERVICE_ROLE_KEY }}
|
||||
|
||||
# Azure OpenAI
|
||||
AZURE_OPENAI_ENDPOINT=${{ secrets.STAGING_AZURE_OPENAI_ENDPOINT }}
|
||||
AZURE_OPENAI_API_KEY=${{ secrets.STAGING_AZURE_OPENAI_API_KEY }}
|
||||
AZURE_OPENAI_API_VERSION=2024-12-01-preview
|
||||
|
||||
# Environment
|
||||
NODE_ENV=staging
|
||||
EOF
|
||||
|
||||
scp .env.staging $STAGING_USER@$STAGING_HOST:~/manacore-staging/.env
|
||||
rm .env.staging
|
||||
|
||||
- name: Login to GitHub Container Registry on staging server
|
||||
env:
|
||||
STAGING_USER: deploy
|
||||
STAGING_HOST: 46.224.108.214
|
||||
run: |
|
||||
ssh $STAGING_USER@$STAGING_HOST << EOF
|
||||
# Login to ghcr.io with GitHub token
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
||||
EOF
|
||||
|
||||
- name: Pull latest Docker images
|
||||
env:
|
||||
STAGING_USER: deploy
|
||||
STAGING_HOST: 46.224.108.214
|
||||
run: |
|
||||
ssh $STAGING_USER@$STAGING_HOST << 'EOF'
|
||||
cd ~/manacore-staging
|
||||
docker compose pull
|
||||
EOF
|
||||
|
||||
- name: Deploy services
|
||||
env:
|
||||
STAGING_USER: deploy
|
||||
STAGING_HOST: 46.224.108.214
|
||||
run: |
|
||||
SERVICE="${{ github.event.inputs.service || 'all' }}"
|
||||
|
||||
ssh $STAGING_USER@$STAGING_HOST << EOF
|
||||
cd ~/manacore-staging
|
||||
|
||||
# Determine which services to deploy
|
||||
if [ "$SERVICE" == "all" ]; then
|
||||
echo "Deploying all services..."
|
||||
docker compose up -d
|
||||
else
|
||||
echo "Deploying service: $SERVICE"
|
||||
docker compose up -d $SERVICE
|
||||
fi
|
||||
|
||||
# Wait for initial startup
|
||||
echo "Waiting for services to start..."
|
||||
sleep 15
|
||||
|
||||
echo "=== Container Status ==="
|
||||
docker compose ps
|
||||
EOF
|
||||
|
||||
- name: Create databases
|
||||
env:
|
||||
STAGING_USER: deploy
|
||||
STAGING_HOST: 46.224.108.214
|
||||
run: |
|
||||
ssh $STAGING_USER@$STAGING_HOST << 'EOF'
|
||||
cd ~/manacore-staging
|
||||
|
||||
echo "Creating required databases..."
|
||||
|
||||
# Create manacore_auth database (for mana-core-auth service)
|
||||
docker compose exec -T postgres psql -U postgres -c "CREATE DATABASE manacore_auth;" 2>/dev/null || echo "manacore_auth database already exists"
|
||||
|
||||
# Create chat database (for chat-backend service)
|
||||
docker compose exec -T postgres psql -U postgres -c "CREATE DATABASE chat;" 2>/dev/null || echo "chat database already exists"
|
||||
|
||||
# Create todo database (for todo-backend service)
|
||||
docker compose exec -T postgres psql -U postgres -c "CREATE DATABASE todo;" 2>/dev/null || echo "todo database already exists"
|
||||
|
||||
# Create calendar database (for calendar-backend service)
|
||||
docker compose exec -T postgres psql -U postgres -c "CREATE DATABASE calendar;" 2>/dev/null || echo "calendar database already exists"
|
||||
|
||||
# Create clock database (for clock-backend service)
|
||||
docker compose exec -T postgres psql -U postgres -c "CREATE DATABASE clock;" 2>/dev/null || echo "clock database already exists"
|
||||
|
||||
echo "✅ Databases ready"
|
||||
EOF
|
||||
|
||||
- name: Run database migrations
|
||||
env:
|
||||
STAGING_USER: deploy
|
||||
STAGING_HOST: 46.224.108.214
|
||||
run: |
|
||||
ssh $STAGING_USER@$STAGING_HOST << 'EOF'
|
||||
cd ~/manacore-staging
|
||||
|
||||
echo "=== Running Database Migrations ==="
|
||||
echo ""
|
||||
|
||||
# Migration function with retry logic
|
||||
run_migration() {
|
||||
local service=$1
|
||||
local max_attempts=3
|
||||
local timeout=300 # 5 minutes
|
||||
local attempt=1
|
||||
|
||||
while [ $attempt -le $max_attempts ]; do
|
||||
echo "[$service] Migration attempt $attempt/$max_attempts..."
|
||||
|
||||
# Run migration with timeout
|
||||
if timeout $timeout docker compose exec -T $service pnpm run db:migrate 2>&1; then
|
||||
echo "✅ [$service] Migration succeeded"
|
||||
return 0
|
||||
else
|
||||
exit_code=$?
|
||||
if [ $exit_code -eq 124 ]; then
|
||||
echo "⚠️ [$service] Migration timeout after ${timeout}s"
|
||||
else
|
||||
echo "⚠️ [$service] Migration failed with exit code $exit_code"
|
||||
fi
|
||||
|
||||
attempt=$((attempt + 1))
|
||||
if [ $attempt -le $max_attempts ]; then
|
||||
wait_time=$((10 * attempt)) # Backoff: 10s, 20s, 30s
|
||||
echo " Waiting ${wait_time}s before retry..."
|
||||
sleep $wait_time
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo "❌ [$service] Migration failed after $max_attempts attempts"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Run migrations for services that have db:migrate script
|
||||
# mana-core-auth - central auth service
|
||||
if docker compose exec -T mana-core-auth test -f src/db/migrate.ts 2>/dev/null || \
|
||||
docker compose exec -T mana-core-auth pnpm run db:migrate --help 2>/dev/null; then
|
||||
run_migration mana-core-auth || {
|
||||
echo "❌ mana-core-auth migration failed - aborting deployment"
|
||||
exit 1
|
||||
}
|
||||
else
|
||||
echo "⏭️ [mana-core-auth] No db:migrate script, using db:push..."
|
||||
docker compose exec -T mana-core-auth npx drizzle-kit push --force || echo "Auth schema push completed"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ All migrations completed"
|
||||
EOF
|
||||
|
||||
- name: Run health checks
|
||||
env:
|
||||
STAGING_USER: deploy
|
||||
STAGING_HOST: 46.224.108.214
|
||||
run: |
|
||||
ssh $STAGING_USER@$STAGING_HOST << 'EOF'
|
||||
cd ~/manacore-staging
|
||||
|
||||
echo "=== Health Checks with Polling ==="
|
||||
echo ""
|
||||
|
||||
# Health check function with retry polling
|
||||
check_health() {
|
||||
local service=$1
|
||||
local url=$2
|
||||
local max_attempts=24 # 24 * 5s = 2 minutes max wait
|
||||
local attempt=1
|
||||
|
||||
echo "Checking $service..."
|
||||
|
||||
while [ $attempt -le $max_attempts ]; do
|
||||
# Check if container is running
|
||||
if ! docker compose ps $service 2>/dev/null | grep -q "Up"; then
|
||||
if [ $attempt -eq 1 ]; then
|
||||
echo " ⏳ Waiting for container to start..."
|
||||
fi
|
||||
sleep 5
|
||||
attempt=$((attempt + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
# Check health endpoint
|
||||
if docker compose exec -T $service wget -q -O - $url > /dev/null 2>&1; then
|
||||
echo " ✅ $service is healthy (attempt $attempt)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ $attempt -eq 1 ]; then
|
||||
echo " ⏳ Waiting for $service to become healthy..."
|
||||
fi
|
||||
|
||||
sleep 5
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
|
||||
echo " ❌ $service health check failed after $max_attempts attempts"
|
||||
echo " === Recent Logs ==="
|
||||
docker compose logs --tail=50 $service
|
||||
return 1
|
||||
}
|
||||
|
||||
echo "=== Container Status ==="
|
||||
docker compose ps
|
||||
echo ""
|
||||
|
||||
# Check all services with polling
|
||||
check_health mana-core-auth http://localhost:3001/api/v1/health || exit 1
|
||||
check_health chat-backend http://localhost:3002/api/v1/health || exit 1
|
||||
check_health chat-web http://localhost:3000/health || exit 1
|
||||
check_health manacore-web http://localhost:5173/health || exit 1
|
||||
check_health todo-backend http://localhost:3018/api/v1/health || exit 1
|
||||
check_health todo-web http://localhost:5188/health || exit 1
|
||||
check_health calendar-backend http://localhost:3016/api/v1/health || exit 1
|
||||
check_health calendar-web http://localhost:5186/health || exit 1
|
||||
check_health clock-backend http://localhost:3017/api/v1/health || exit 1
|
||||
check_health clock-web http://localhost:5187/health || exit 1
|
||||
|
||||
echo ""
|
||||
echo "✅ All health checks passed!"
|
||||
EOF
|
||||
|
||||
- name: Deployment summary
|
||||
run: |
|
||||
echo "## Staging Deployment Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Environment**: Staging" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Deployed by**: ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Commit**: ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Timestamp**: $(date -u +'%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Services Deployed" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Service: ${{ github.event.inputs.service || 'all' }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Health Checks" >> $GITHUB_STEP_SUMMARY
|
||||
echo "All health checks passed ✅" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
notify-deployment:
|
||||
name: Notify Deployment
|
||||
runs-on: ubuntu-latest
|
||||
needs: deploy-staging
|
||||
if: always()
|
||||
steps:
|
||||
- name: Deployment notification
|
||||
run: |
|
||||
STATUS="${{ needs.deploy-staging.result }}"
|
||||
|
||||
if [ "$STATUS" == "success" ]; then
|
||||
echo "✅ Staging deployment completed successfully"
|
||||
else
|
||||
echo "❌ Staging deployment failed"
|
||||
exit 1
|
||||
fi
|
||||
264
.github/workflows/cd-staging.yml.bak
vendored
264
.github/workflows/cd-staging.yml.bak
vendored
|
|
@ -1,264 +0,0 @@
|
|||
# ARCHIVED: Full staging workflow with all services
|
||||
# Active simplified workflow: .github/workflows/cd-staging.yml
|
||||
#
|
||||
# Services included: mana-core-auth, chat-backend, manadeck-backend
|
||||
#
|
||||
# To restore: cp .github/workflows/cd-staging.full.yml .github/workflows/cd-staging.yml
|
||||
|
||||
name: CD - Staging Deployment
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
service:
|
||||
description: 'Service to deploy (leave empty for all)'
|
||||
required: false
|
||||
type: choice
|
||||
options:
|
||||
- all
|
||||
- mana-core-auth
|
||||
- chat-backend
|
||||
- manadeck-backend
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
|
||||
env:
|
||||
NODE_VERSION: '20'
|
||||
PNPM_VERSION: '9.15.0'
|
||||
|
||||
jobs:
|
||||
deploy-staging:
|
||||
name: Deploy to Staging
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: staging
|
||||
url: https://staging.manacore.app
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup SSH for deployment
|
||||
uses: webfactory/ssh-agent@v0.9.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
|
||||
|
||||
- name: Add staging server to known hosts
|
||||
env:
|
||||
STAGING_HOST: 46.224.108.214
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
ssh-keyscan -H $STAGING_HOST >> ~/.ssh/known_hosts
|
||||
|
||||
- name: Prepare deployment directory
|
||||
env:
|
||||
STAGING_USER: deploy
|
||||
STAGING_HOST: 46.224.108.214
|
||||
run: |
|
||||
ssh $STAGING_USER@$STAGING_HOST << 'EOF'
|
||||
mkdir -p ~/manacore-staging
|
||||
cd ~/manacore-staging
|
||||
|
||||
# Create required directories
|
||||
mkdir -p logs
|
||||
mkdir -p data/postgres
|
||||
mkdir -p data/redis
|
||||
EOF
|
||||
|
||||
- name: Copy docker-compose file
|
||||
env:
|
||||
STAGING_USER: deploy
|
||||
STAGING_HOST: 46.224.108.214
|
||||
run: |
|
||||
scp docker-compose.staging.yml $STAGING_USER@$STAGING_HOST:~/manacore-staging/docker-compose.yml
|
||||
|
||||
- name: Copy environment file
|
||||
env:
|
||||
STAGING_USER: deploy
|
||||
STAGING_HOST: 46.224.108.214
|
||||
run: |
|
||||
# Create staging env file (mix of hardcoded config and secrets)
|
||||
cat > .env.staging << EOF
|
||||
# Database - Configuration
|
||||
POSTGRES_HOST=postgres
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_DB=manacore
|
||||
POSTGRES_USER=postgres
|
||||
POSTGRES_PASSWORD=${{ secrets.STAGING_POSTGRES_PASSWORD }}
|
||||
|
||||
# Redis - Configuration
|
||||
REDIS_HOST=redis
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=${{ secrets.STAGING_REDIS_PASSWORD }}
|
||||
|
||||
# Mana Core Auth - Configuration
|
||||
MANA_SERVICE_URL=http://mana-core-auth:3001
|
||||
JWT_SECRET=${{ secrets.STAGING_JWT_SECRET }}
|
||||
JWT_PUBLIC_KEY=${{ secrets.STAGING_JWT_PUBLIC_KEY }}
|
||||
JWT_PRIVATE_KEY=${{ secrets.STAGING_JWT_PRIVATE_KEY }}
|
||||
|
||||
# Supabase
|
||||
SUPABASE_URL=${{ secrets.STAGING_SUPABASE_URL }}
|
||||
SUPABASE_ANON_KEY=${{ secrets.STAGING_SUPABASE_ANON_KEY }}
|
||||
SUPABASE_SERVICE_ROLE_KEY=${{ secrets.STAGING_SUPABASE_SERVICE_ROLE_KEY }}
|
||||
|
||||
# Azure OpenAI
|
||||
AZURE_OPENAI_ENDPOINT=${{ secrets.STAGING_AZURE_OPENAI_ENDPOINT }}
|
||||
AZURE_OPENAI_API_KEY=${{ secrets.STAGING_AZURE_OPENAI_API_KEY }}
|
||||
AZURE_OPENAI_API_VERSION=2024-12-01-preview
|
||||
|
||||
# Environment
|
||||
NODE_ENV=staging
|
||||
EOF
|
||||
|
||||
scp .env.staging $STAGING_USER@$STAGING_HOST:~/manacore-staging/.env
|
||||
rm .env.staging
|
||||
|
||||
- name: Login to GitHub Container Registry on staging server
|
||||
env:
|
||||
STAGING_USER: deploy
|
||||
STAGING_HOST: 46.224.108.214
|
||||
run: |
|
||||
ssh $STAGING_USER@$STAGING_HOST << EOF
|
||||
# Login to ghcr.io with GitHub token
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
||||
EOF
|
||||
|
||||
- name: Pull latest Docker images
|
||||
env:
|
||||
STAGING_USER: deploy
|
||||
STAGING_HOST: 46.224.108.214
|
||||
run: |
|
||||
ssh $STAGING_USER@$STAGING_HOST << 'EOF'
|
||||
cd ~/manacore-staging
|
||||
docker compose pull
|
||||
EOF
|
||||
|
||||
- name: Deploy services
|
||||
env:
|
||||
STAGING_USER: deploy
|
||||
STAGING_HOST: 46.224.108.214
|
||||
run: |
|
||||
SERVICE="${{ github.event.inputs.service || 'all' }}"
|
||||
|
||||
ssh $STAGING_USER@$STAGING_HOST << EOF
|
||||
cd ~/manacore-staging
|
||||
|
||||
# Determine which services to deploy
|
||||
if [ "$SERVICE" == "all" ]; then
|
||||
echo "Deploying all services..."
|
||||
docker compose up -d
|
||||
else
|
||||
echo "Deploying service: $SERVICE"
|
||||
docker compose up -d $SERVICE
|
||||
fi
|
||||
|
||||
# Wait for initial startup
|
||||
echo "Waiting for services to start..."
|
||||
sleep 15
|
||||
|
||||
echo "=== Container Status ==="
|
||||
docker compose ps
|
||||
EOF
|
||||
|
||||
- name: Run health checks
|
||||
env:
|
||||
STAGING_USER: deploy
|
||||
STAGING_HOST: 46.224.108.214
|
||||
run: |
|
||||
ssh $STAGING_USER@$STAGING_HOST << 'EOF'
|
||||
cd ~/manacore-staging
|
||||
|
||||
# Wait for services to fully start
|
||||
echo "Waiting 60s for services to fully initialize..."
|
||||
sleep 60
|
||||
|
||||
echo "=== Container Status ==="
|
||||
docker compose ps
|
||||
|
||||
echo ""
|
||||
echo "=== Health Checks ==="
|
||||
|
||||
# Check mana-core-auth
|
||||
echo "Checking mana-core-auth..."
|
||||
if docker compose exec -T mana-core-auth wget -q -O - http://localhost:3001/api/v1/health > /dev/null 2>&1; then
|
||||
echo "✅ mana-core-auth is healthy"
|
||||
else
|
||||
echo "❌ mana-core-auth health check failed"
|
||||
echo "=== Logs ==="
|
||||
docker compose logs --tail=50 mana-core-auth
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check chat-backend
|
||||
echo "Checking chat-backend..."
|
||||
if docker compose exec -T chat-backend wget -q -O - http://localhost:3002/api/health > /dev/null 2>&1; then
|
||||
echo "✅ chat-backend is healthy"
|
||||
else
|
||||
echo "❌ chat-backend health check failed"
|
||||
echo "=== Logs ==="
|
||||
docker compose logs --tail=50 chat-backend
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check manadeck-backend
|
||||
echo "Checking manadeck-backend..."
|
||||
if docker compose exec -T manadeck-backend wget -q -O - http://localhost:3003/api/health > /dev/null 2>&1; then
|
||||
echo "✅ manadeck-backend is healthy"
|
||||
else
|
||||
echo "❌ manadeck-backend health check failed"
|
||||
echo "=== Logs ==="
|
||||
docker compose logs --tail=50 manadeck-backend
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ All health checks passed!"
|
||||
EOF
|
||||
|
||||
- name: Run database migrations
|
||||
env:
|
||||
STAGING_USER: deploy
|
||||
STAGING_HOST: 46.224.108.214
|
||||
run: |
|
||||
# Run migrations for services that need them
|
||||
ssh $STAGING_USER@$STAGING_HOST << 'EOF'
|
||||
cd ~/manacore-staging
|
||||
|
||||
# Mana Core Auth migrations
|
||||
docker compose exec -T mana-core-auth pnpm run db:migrate || echo "Auth migrations skipped"
|
||||
EOF
|
||||
|
||||
- name: Deployment summary
|
||||
run: |
|
||||
echo "## Staging Deployment Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Environment**: Staging" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Deployed by**: ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Commit**: ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Timestamp**: $(date -u +'%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Services Deployed" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Service: ${{ github.event.inputs.service || 'all' }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Health Checks" >> $GITHUB_STEP_SUMMARY
|
||||
echo "All health checks passed ✅" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
notify-deployment:
|
||||
name: Notify Deployment
|
||||
runs-on: ubuntu-latest
|
||||
needs: deploy-staging
|
||||
if: always()
|
||||
steps:
|
||||
- name: Deployment notification
|
||||
run: |
|
||||
STATUS="${{ needs.deploy-staging.result }}"
|
||||
|
||||
if [ "$STATUS" == "success" ]; then
|
||||
echo "✅ Staging deployment completed successfully"
|
||||
else
|
||||
echo "❌ Staging deployment failed"
|
||||
exit 1
|
||||
fi
|
||||
168
.github/workflows/ci-main.yml.bak
vendored
168
.github/workflows/ci-main.yml.bak
vendored
|
|
@ -1,168 +0,0 @@
|
|||
name: CI - Main Branch
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
NODE_VERSION: '20'
|
||||
PNPM_VERSION: '9.15.0'
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
|
||||
jobs:
|
||||
# Full validation on main branch
|
||||
validate:
|
||||
name: Validate Main Branch
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build shared packages
|
||||
run: pnpm run build:packages
|
||||
|
||||
- name: Run format check
|
||||
run: pnpm run format:check
|
||||
|
||||
- name: Run lint
|
||||
run: pnpm run lint
|
||||
continue-on-error: true
|
||||
|
||||
- name: Run type check
|
||||
run: pnpm run type-check
|
||||
|
||||
- name: Build all projects
|
||||
run: pnpm run build
|
||||
|
||||
- name: Run tests
|
||||
run: pnpm run test || echo "Some tests failed"
|
||||
continue-on-error: true
|
||||
|
||||
- name: Generate build summary
|
||||
run: |
|
||||
echo "## Main Branch Build Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Commit**: ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Author**: ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Timestamp**: $(date -u +'%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Build Status" >> $GITHUB_STEP_SUMMARY
|
||||
echo "All projects built successfully" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Build and push Docker images for backend services
|
||||
build-docker-images:
|
||||
name: Build Docker Images
|
||||
runs-on: ubuntu-latest
|
||||
needs: validate
|
||||
strategy:
|
||||
matrix:
|
||||
service:
|
||||
- { name: 'maerchenzauber-backend', path: 'apps/maerchenzauber/apps/backend', port: '3002' }
|
||||
- { name: 'chat-backend', path: 'apps/chat/apps/backend', port: '3002' }
|
||||
- { name: 'manadeck-backend', path: 'apps/manadeck/apps/backend', port: '3003' }
|
||||
- { name: 'nutriphi-backend', path: 'apps/nutriphi/apps/backend', port: '3004' }
|
||||
- { name: 'news-api', path: 'apps/news/apps/api', port: '3005' }
|
||||
- { name: 'mana-core-auth', path: 'services/mana-core-auth', port: '3001' }
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Check if Dockerfile exists
|
||||
id: check-dockerfile
|
||||
run: |
|
||||
if [ -f "${{ matrix.service.path }}/Dockerfile" ]; then
|
||||
echo "exists=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "exists=false" >> $GITHUB_OUTPUT
|
||||
echo "Warning: No Dockerfile found for ${{ matrix.service.name }}"
|
||||
fi
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: steps.check-dockerfile.outputs.exists == 'true'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata
|
||||
if: steps.check-dockerfile.outputs.exists == 'true'
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository_owner }}/${{ matrix.service.name }}
|
||||
tags: |
|
||||
type=sha,prefix={{branch}}-
|
||||
type=ref,event=branch
|
||||
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
- name: Build and push
|
||||
if: steps.check-dockerfile.outputs.exists == 'true'
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.service.path }}/Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
build-args: |
|
||||
NODE_ENV=production
|
||||
PORT=${{ matrix.service.port }}
|
||||
|
||||
- name: Image digest
|
||||
if: steps.check-dockerfile.outputs.exists == 'true'
|
||||
run: echo "Image digest - ${{ steps.meta.outputs.digest }}"
|
||||
|
||||
# Trigger staging deployment
|
||||
trigger-staging-deploy:
|
||||
name: Trigger Staging Deployment
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-docker-images
|
||||
if: github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- name: Trigger staging deployment workflow
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
await github.rest.actions.createWorkflowDispatch({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
workflow_id: 'cd-staging.yml',
|
||||
ref: 'main'
|
||||
});
|
||||
|
||||
- name: Deployment notification
|
||||
run: |
|
||||
echo "## Staging Deployment Triggered" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Docker images have been built and pushed successfully." >> $GITHUB_STEP_SUMMARY
|
||||
echo "Staging deployment workflow has been triggered." >> $GITHUB_STEP_SUMMARY
|
||||
314
.github/workflows/ci-pull-request.yml.bak
vendored
314
.github/workflows/ci-pull-request.yml.bak
vendored
|
|
@ -1,314 +0,0 @@
|
|||
name: CI - Pull Request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
NODE_VERSION: '20'
|
||||
PNPM_VERSION: '9.15.0'
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
|
||||
jobs:
|
||||
# Detect which projects have changed
|
||||
detect-changes:
|
||||
name: Detect Changed Projects
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
projects: ${{ steps.filter.outputs.changes }}
|
||||
has-changes: ${{ steps.filter.outputs.changes != '[]' }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Detect changed projects
|
||||
uses: dorny/paths-filter@v3
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
chat:
|
||||
- 'apps/chat/**'
|
||||
- 'packages/**'
|
||||
manacore:
|
||||
- 'apps/manacore/**'
|
||||
- 'packages/**'
|
||||
packages:
|
||||
- 'packages/**'
|
||||
|
||||
# Lint and format check
|
||||
lint-and-format:
|
||||
name: Lint & Format Check
|
||||
runs-on: ubuntu-latest
|
||||
needs: detect-changes
|
||||
if: needs.detect-changes.outputs.has-changes == 'true'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run format check
|
||||
run: pnpm run format:check
|
||||
continue-on-error: true
|
||||
|
||||
- name: Run lint
|
||||
run: pnpm run lint --filter='./apps/chat/**' --filter='./apps/manacore/**'
|
||||
continue-on-error: true
|
||||
|
||||
# Type checking
|
||||
type-check:
|
||||
name: Type Check
|
||||
runs-on: ubuntu-latest
|
||||
needs: detect-changes
|
||||
if: needs.detect-changes.outputs.has-changes == 'true'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build shared packages
|
||||
run: pnpm run build:packages
|
||||
|
||||
- name: Run type check
|
||||
run: pnpm run type-check --filter='./apps/chat/**' --filter='./apps/manacore/**'
|
||||
continue-on-error: true
|
||||
|
||||
# Build all affected projects
|
||||
build:
|
||||
name: Build Projects
|
||||
runs-on: ubuntu-latest
|
||||
needs: detect-changes
|
||||
if: needs.detect-changes.outputs.has-changes == 'true'
|
||||
strategy:
|
||||
matrix:
|
||||
project: ${{ fromJSON(needs.detect-changes.outputs.projects) }}
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build shared packages
|
||||
run: pnpm run build:packages
|
||||
|
||||
- name: Build project - ${{ matrix.project }}
|
||||
run: |
|
||||
if [ "${{ matrix.project }}" == "packages" ]; then
|
||||
pnpm run build --filter=@manacore/*
|
||||
else
|
||||
pnpm run build --filter='./apps/${{ matrix.project }}/**'
|
||||
fi
|
||||
continue-on-error: true
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-${{ matrix.project }}
|
||||
path: |
|
||||
apps/${{ matrix.project }}/**/dist
|
||||
apps/${{ matrix.project }}/**/.next
|
||||
apps/${{ matrix.project }}/**/.svelte-kit
|
||||
apps/${{ matrix.project }}/**/.astro
|
||||
services/**/dist
|
||||
retention-days: 7
|
||||
if-no-files-found: ignore
|
||||
|
||||
# Run tests
|
||||
test:
|
||||
name: Run Tests
|
||||
runs-on: ubuntu-latest
|
||||
needs: detect-changes
|
||||
if: needs.detect-changes.outputs.has-changes == 'true'
|
||||
strategy:
|
||||
matrix:
|
||||
project: ${{ fromJSON(needs.detect-changes.outputs.projects) }}
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build shared packages
|
||||
run: pnpm run build:packages
|
||||
|
||||
- name: Run tests - ${{ matrix.project }}
|
||||
run: |
|
||||
if [ "${{ matrix.project }}" == "packages" ]; then
|
||||
pnpm run test --filter=@manacore/* || echo "No tests found for packages"
|
||||
else
|
||||
pnpm run test --filter='./apps/${{ matrix.project }}/**' || echo "No tests found for ${{ matrix.project }}"
|
||||
fi
|
||||
continue-on-error: true
|
||||
|
||||
- name: Upload test coverage
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-${{ matrix.project }}
|
||||
path: |
|
||||
apps/${{ matrix.project }}/**/coverage
|
||||
services/**/coverage
|
||||
retention-days: 7
|
||||
if-no-files-found: ignore
|
||||
|
||||
# Docker build validation for backend services
|
||||
docker-build-check:
|
||||
name: Docker Build Check
|
||||
runs-on: ubuntu-latest
|
||||
needs: detect-changes
|
||||
if: contains(needs.detect-changes.outputs.projects, 'chat')
|
||||
strategy:
|
||||
matrix:
|
||||
service:
|
||||
- { name: 'chat-backend', path: 'apps/chat/apps/backend' }
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Check if Dockerfile exists
|
||||
id: check-dockerfile
|
||||
run: |
|
||||
if [ -f "${{ matrix.service.path }}/Dockerfile" ]; then
|
||||
echo "exists=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "exists=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Build Docker image
|
||||
if: steps.check-dockerfile.outputs.exists == 'true'
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.service.path }}/Dockerfile
|
||||
push: false
|
||||
tags: ${{ matrix.service.name }}:pr-${{ github.event.pull_request.number }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
build-args: |
|
||||
NODE_ENV=production
|
||||
|
||||
# Security scanning
|
||||
security-scan:
|
||||
name: Security Scan
|
||||
runs-on: ubuntu-latest
|
||||
needs: detect-changes
|
||||
if: needs.detect-changes.outputs.has-changes == 'true'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run security audit
|
||||
run: pnpm audit --audit-level=high
|
||||
continue-on-error: true
|
||||
|
||||
- name: Check for outdated dependencies
|
||||
run: pnpm outdated
|
||||
continue-on-error: true
|
||||
|
||||
# PR status check (required for merge)
|
||||
pr-checks-complete:
|
||||
name: All PR Checks Complete
|
||||
runs-on: ubuntu-latest
|
||||
needs: [lint-and-format, type-check, build, test, docker-build-check, security-scan]
|
||||
if: always()
|
||||
steps:
|
||||
- name: Check all jobs status
|
||||
run: |
|
||||
if [ "${{ needs.lint-and-format.result }}" == "failure" ] || \
|
||||
[ "${{ needs.type-check.result }}" == "failure" ] || \
|
||||
[ "${{ needs.build.result }}" == "failure" ]; then
|
||||
echo "One or more required checks failed"
|
||||
exit 1
|
||||
fi
|
||||
echo "All required checks passed"
|
||||
|
||||
- name: PR summary
|
||||
run: |
|
||||
echo "## PR Checks Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Check | Status |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Lint & Format | ${{ needs.lint-and-format.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Type Check | ${{ needs.type-check.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Build | ${{ needs.build.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Tests | ${{ needs.test.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Docker Build | ${{ needs.docker-build-check.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Security Scan | ${{ needs.security-scan.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
249
.github/workflows/dependency-update.yml.bak
vendored
249
.github/workflows/dependency-update.yml.bak
vendored
|
|
@ -1,249 +0,0 @@
|
|||
name: Dependency Updates
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run every Monday at 06:00 UTC
|
||||
- cron: '0 6 * * 1'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
NODE_VERSION: '20'
|
||||
PNPM_VERSION: '9.15.0'
|
||||
|
||||
jobs:
|
||||
# Check for outdated dependencies
|
||||
check-outdated:
|
||||
name: Check Outdated Dependencies
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Check for outdated dependencies
|
||||
run: pnpm outdated --format json > outdated.json || true
|
||||
|
||||
- name: Generate outdated report
|
||||
run: |
|
||||
echo "## Outdated Dependencies Report" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Generated on: $(date -u +'%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ -f outdated.json ] && [ -s outdated.json ]; then
|
||||
echo "### Packages to Update" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
cat outdated.json | jq -r 'to_entries[] | "- **\(.key)**: \(.value.current) → \(.value.latest)"' >> $GITHUB_STEP_SUMMARY || echo "No outdated packages found" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "✅ All dependencies are up to date!" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
- name: Upload outdated report
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: outdated-dependencies
|
||||
path: outdated.json
|
||||
retention-days: 30
|
||||
if: always()
|
||||
|
||||
# Security audit
|
||||
security-audit:
|
||||
name: Security Audit
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run security audit
|
||||
run: |
|
||||
pnpm audit --json > audit-report.json || true
|
||||
pnpm audit --audit-level=moderate || echo "Security vulnerabilities found"
|
||||
|
||||
- name: Generate security report
|
||||
run: |
|
||||
echo "## Security Audit Report" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Generated on: $(date -u +'%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ -f audit-report.json ]; then
|
||||
# Parse audit report
|
||||
CRITICAL=$(jq -r '.metadata.vulnerabilities.critical // 0' audit-report.json)
|
||||
HIGH=$(jq -r '.metadata.vulnerabilities.high // 0' audit-report.json)
|
||||
MODERATE=$(jq -r '.metadata.vulnerabilities.moderate // 0' audit-report.json)
|
||||
LOW=$(jq -r '.metadata.vulnerabilities.low // 0' audit-report.json)
|
||||
|
||||
echo "| Severity | Count |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Critical | $CRITICAL |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| High | $HIGH |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Moderate | $MODERATE |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Low | $LOW |" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ "$CRITICAL" -gt 0 ] || [ "$HIGH" -gt 0 ]; then
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "⚠️ **Action Required**: Critical or high severity vulnerabilities detected!" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Upload security audit
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: security-audit
|
||||
path: audit-report.json
|
||||
retention-days: 90
|
||||
if: always()
|
||||
|
||||
- name: Create issue for critical vulnerabilities
|
||||
if: always()
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
|
||||
if (!fs.existsSync('audit-report.json')) {
|
||||
console.log('No audit report found');
|
||||
return;
|
||||
}
|
||||
|
||||
const auditData = JSON.parse(fs.readFileSync('audit-report.json', 'utf8'));
|
||||
const critical = auditData.metadata?.vulnerabilities?.critical || 0;
|
||||
const high = auditData.metadata?.vulnerabilities?.high || 0;
|
||||
|
||||
if (critical > 0 || high > 0) {
|
||||
const issueTitle = `🚨 Security Alert: ${critical} Critical, ${high} High Severity Vulnerabilities`;
|
||||
const issueBody = `
|
||||
## Security Vulnerability Report
|
||||
|
||||
**Date**: ${new Date().toISOString()}
|
||||
**Workflow Run**: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
|
||||
### Summary
|
||||
- Critical: ${critical}
|
||||
- High: ${high}
|
||||
- Moderate: ${auditData.metadata?.vulnerabilities?.moderate || 0}
|
||||
- Low: ${auditData.metadata?.vulnerabilities?.low || 0}
|
||||
|
||||
### Action Required
|
||||
Please review the security audit report and update affected dependencies.
|
||||
|
||||
\`\`\`bash
|
||||
pnpm audit
|
||||
pnpm audit fix
|
||||
\`\`\`
|
||||
|
||||
**Note**: This issue was automatically created by the dependency update workflow.
|
||||
`;
|
||||
|
||||
// Check if similar issue exists
|
||||
const { data: existingIssues } = await github.rest.issues.listForRepo({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'open',
|
||||
labels: 'security,automated'
|
||||
});
|
||||
|
||||
const hasExistingIssue = existingIssues.some(issue =>
|
||||
issue.title.includes('Security Alert')
|
||||
);
|
||||
|
||||
if (!hasExistingIssue) {
|
||||
await github.rest.issues.create({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
title: issueTitle,
|
||||
body: issueBody,
|
||||
labels: ['security', 'automated', 'high-priority']
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
# Update lock file
|
||||
update-lockfile:
|
||||
name: Update Lock File
|
||||
runs-on: ubuntu-latest
|
||||
needs: [check-outdated, security-audit]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Update lock file
|
||||
run: |
|
||||
# Update lock file without changing package.json versions
|
||||
pnpm install --no-frozen-lockfile
|
||||
|
||||
- name: Check for changes
|
||||
id: changes
|
||||
run: |
|
||||
if git diff --quiet pnpm-lock.yaml; then
|
||||
echo "has-changes=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has-changes=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Create Pull Request
|
||||
if: steps.changes.outputs.has-changes == 'true'
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: "chore: update pnpm-lock.yaml"
|
||||
title: "chore: Update dependency lock file"
|
||||
body: |
|
||||
## Dependency Lock File Update
|
||||
|
||||
This PR updates the `pnpm-lock.yaml` file to reflect the latest compatible versions.
|
||||
|
||||
### Changes
|
||||
- Updated lock file to latest compatible versions
|
||||
- No breaking changes to package.json
|
||||
|
||||
### Testing
|
||||
- [ ] All CI checks pass
|
||||
- [ ] Manual testing completed
|
||||
|
||||
**Note**: This PR was automatically created by the dependency update workflow.
|
||||
branch: chore/update-lockfile
|
||||
labels: |
|
||||
dependencies
|
||||
automated
|
||||
assignees: wuesteon
|
||||
103
.github/workflows/staging-config-check.yml
vendored
103
.github/workflows/staging-config-check.yml
vendored
|
|
@ -1,103 +0,0 @@
|
|||
name: Staging Config Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'docker-compose.staging.yml'
|
||||
- 'docker/caddy/Caddyfile.staging'
|
||||
|
||||
jobs:
|
||||
check-staging-urls:
|
||||
name: Validate Staging URLs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Check for HTTP IP addresses in _CLIENT URLs
|
||||
run: |
|
||||
echo "Checking docker-compose.staging.yml for HTTP IP addresses..."
|
||||
|
||||
# Check that no _CLIENT URLs use HTTP IP addresses
|
||||
if grep -E '_CLIENT:.*http://[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' docker-compose.staging.yml; then
|
||||
echo ""
|
||||
echo "::error::Found HTTP IP addresses in _CLIENT URLs!"
|
||||
echo "All _CLIENT URLs must use HTTPS staging domains (e.g., https://auth.staging.manacore.ai)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "No HTTP IP addresses found in _CLIENT URLs"
|
||||
|
||||
- name: Check for non-HTTPS external URLs
|
||||
run: |
|
||||
echo "Checking for non-HTTPS external URLs in _CLIENT variables..."
|
||||
|
||||
# Check that _CLIENT URLs use HTTPS (excluding localhost for dev)
|
||||
VIOLATIONS=$(grep -E '_CLIENT:.*http://' docker-compose.staging.yml | grep -v localhost || true)
|
||||
|
||||
if [ -n "$VIOLATIONS" ]; then
|
||||
echo ""
|
||||
echo "::error::Found non-HTTPS URLs in _CLIENT variables!"
|
||||
echo "$VIOLATIONS"
|
||||
echo ""
|
||||
echo "All _CLIENT URLs must use HTTPS for staging domains."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "All _CLIENT URLs use HTTPS"
|
||||
|
||||
- name: Verify required HTTPS domains
|
||||
run: |
|
||||
echo "Verifying required HTTPS staging domains are configured..."
|
||||
|
||||
REQUIRED_DOMAINS=(
|
||||
"https://auth.staging.manacore.ai"
|
||||
"https://staging.manacore.ai"
|
||||
)
|
||||
|
||||
MISSING=0
|
||||
for domain in "${REQUIRED_DOMAINS[@]}"; do
|
||||
if ! grep -q "$domain" docker-compose.staging.yml; then
|
||||
echo "::warning::Missing required domain: $domain"
|
||||
MISSING=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $MISSING -eq 1 ]; then
|
||||
echo ""
|
||||
echo "::warning::Some required staging domains are not configured. Please verify this is intentional."
|
||||
fi
|
||||
|
||||
echo "Domain verification complete"
|
||||
|
||||
- name: Check CORS origins include HTTPS
|
||||
run: |
|
||||
echo "Checking CORS_ORIGINS for HTTPS staging domains..."
|
||||
|
||||
# Extract CORS_ORIGINS lines and check they include staging domains
|
||||
CORS_LINES=$(grep "CORS_ORIGINS:" docker-compose.staging.yml || true)
|
||||
|
||||
if [ -n "$CORS_LINES" ]; then
|
||||
# Check if any CORS line has HTTP staging domains (not localhost)
|
||||
HTTP_CORS=$(echo "$CORS_LINES" | grep -E 'http://[a-z]+\.staging\.manacore\.ai' || true)
|
||||
|
||||
if [ -n "$HTTP_CORS" ]; then
|
||||
echo ""
|
||||
echo "::error::Found HTTP (non-HTTPS) staging domains in CORS_ORIGINS!"
|
||||
echo "$HTTP_CORS"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "CORS origins are correctly configured"
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
echo ""
|
||||
echo "======================================"
|
||||
echo "Staging Configuration Check: PASSED"
|
||||
echo "======================================"
|
||||
echo ""
|
||||
echo "All checks passed:"
|
||||
echo " - No HTTP IP addresses in _CLIENT URLs"
|
||||
echo " - All external _CLIENT URLs use HTTPS"
|
||||
echo " - CORS origins correctly configured"
|
||||
180
.github/workflows/test-coverage.yml.bak
vendored
180
.github/workflows/test-coverage.yml.bak
vendored
|
|
@ -1,180 +0,0 @@
|
|||
name: Test Coverage
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
schedule:
|
||||
# Run weekly on Sundays at 00:00 UTC
|
||||
- cron: '0 0 * * 0'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
NODE_VERSION: '20'
|
||||
PNPM_VERSION: '9.15.0'
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
|
||||
jobs:
|
||||
test-coverage:
|
||||
name: Test Coverage
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build shared packages
|
||||
run: pnpm run build:packages
|
||||
|
||||
- name: Run tests with coverage
|
||||
run: pnpm run test --coverage || echo "Some tests failed"
|
||||
continue-on-error: true
|
||||
|
||||
- name: Collect coverage reports
|
||||
run: |
|
||||
# Find all coverage directories
|
||||
find . -type d -name coverage \( -path "*/apps/*/apps/*" -o -path "*/services/*" \) > coverage_dirs.txt
|
||||
|
||||
# Create combined coverage directory
|
||||
mkdir -p coverage-combined
|
||||
|
||||
# Copy all coverage files
|
||||
while IFS= read -r dir; do
|
||||
if [ -f "$dir/coverage-final.json" ]; then
|
||||
PROJECT=$(echo $dir | sed 's|./apps/||' | sed 's|./services/||' | sed 's|/coverage||' | tr '/' '-')
|
||||
cp "$dir/coverage-final.json" "coverage-combined/coverage-$PROJECT.json"
|
||||
fi
|
||||
done < coverage_dirs.txt
|
||||
|
||||
- name: Generate coverage summary
|
||||
run: |
|
||||
echo "## Test Coverage Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Find and parse coverage summaries
|
||||
find . -type f -name "coverage-summary.json" | while read -r file; do
|
||||
PROJECT=$(dirname $file | sed 's|./apps/||' | sed 's|./services/||' | sed 's|/coverage||')
|
||||
|
||||
if [ -f "$file" ]; then
|
||||
LINES=$(jq -r '.total.lines.pct' "$file" 2>/dev/null || echo "0")
|
||||
STATEMENTS=$(jq -r '.total.statements.pct' "$file" 2>/dev/null || echo "0")
|
||||
FUNCTIONS=$(jq -r '.total.functions.pct' "$file" 2>/dev/null || echo "0")
|
||||
BRANCHES=$(jq -r '.total.branches.pct' "$file" 2>/dev/null || echo "0")
|
||||
|
||||
echo "### $PROJECT" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Metric | Coverage |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|--------|----------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Lines | ${LINES}% |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Statements | ${STATEMENTS}% |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Functions | ${FUNCTIONS}% |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Branches | ${BRANCHES}% |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Archive coverage reports
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-reports
|
||||
path: |
|
||||
apps/**/coverage
|
||||
services/**/coverage
|
||||
coverage-combined
|
||||
retention-days: 30
|
||||
if-no-files-found: warn
|
||||
|
||||
- name: Check coverage thresholds
|
||||
run: |
|
||||
echo "Checking coverage thresholds..."
|
||||
|
||||
# Set minimum coverage threshold
|
||||
MINIMUM_COVERAGE=50 # Start with 50%, increase gradually
|
||||
|
||||
# Check each project's coverage
|
||||
find . -type f -name "coverage-summary.json" | while read -r file; do
|
||||
PROJECT=$(dirname $file | sed 's|./apps/||' | sed 's|./services/||' | sed 's|/coverage||')
|
||||
LINES=$(jq -r '.total.lines.pct' "$file" 2>/dev/null || echo "0")
|
||||
|
||||
echo "Checking $PROJECT: ${LINES}% coverage"
|
||||
|
||||
# Convert to integer for comparison
|
||||
LINES_INT=$(printf "%.0f" $LINES)
|
||||
|
||||
if [ "$LINES_INT" -lt "$MINIMUM_COVERAGE" ]; then
|
||||
echo "⚠️ Warning: $PROJECT coverage (${LINES}%) is below minimum threshold (${MINIMUM_COVERAGE}%)"
|
||||
else
|
||||
echo "✅ $PROJECT meets coverage threshold"
|
||||
fi
|
||||
done
|
||||
|
||||
# Generate coverage badge
|
||||
coverage-badge:
|
||||
name: Update Coverage Badge
|
||||
runs-on: ubuntu-latest
|
||||
needs: test-coverage
|
||||
if: github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download coverage reports
|
||||
uses: actions/download-artifact@v4
|
||||
continue-on-error: true
|
||||
id: download-coverage
|
||||
with:
|
||||
name: coverage-reports
|
||||
path: coverage-reports
|
||||
|
||||
- name: Create coverage badge
|
||||
if: steps.download-coverage.outcome == 'success'
|
||||
run: |
|
||||
# Calculate overall coverage
|
||||
TOTAL_LINES=0
|
||||
COVERED_LINES=0
|
||||
|
||||
find coverage-reports -type f -name "coverage-summary.json" | while read -r file; do
|
||||
LINES=$(jq -r '.total.lines.total' "$file" 2>/dev/null || echo "0")
|
||||
COVERED=$(jq -r '.total.lines.covered' "$file" 2>/dev/null || echo "0")
|
||||
|
||||
TOTAL_LINES=$((TOTAL_LINES + LINES))
|
||||
COVERED_LINES=$((COVERED_LINES + COVERED))
|
||||
done
|
||||
|
||||
if [ "$TOTAL_LINES" -gt 0 ]; then
|
||||
COVERAGE=$(echo "scale=2; $COVERED_LINES * 100 / $TOTAL_LINES" | bc)
|
||||
echo "Overall coverage: ${COVERAGE}%"
|
||||
echo "COVERAGE=${COVERAGE}" >> $GITHUB_ENV
|
||||
else
|
||||
echo "No coverage data found"
|
||||
echo "COVERAGE=0" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Update README badge
|
||||
if: steps.download-coverage.outcome == 'success'
|
||||
run: |
|
||||
echo "Coverage badge data ready: ${{ env.COVERAGE }}%"
|
||||
# This would update a badge in the README or create a gist
|
||||
# Implementation depends on chosen badge service (shields.io, codecov, etc.)
|
||||
|
||||
- name: Skip badge update
|
||||
if: steps.download-coverage.outcome != 'success'
|
||||
run: echo "No coverage reports available - skipping badge update"
|
||||
389
.github/workflows/test.yml.bak
vendored
389
.github/workflows/test.yml.bak
vendored
|
|
@ -1,389 +0,0 @@
|
|||
name: Test Suite
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main, develop]
|
||||
push:
|
||||
branches: [main, develop]
|
||||
workflow_dispatch:
|
||||
|
||||
# Cancel in-progress runs for same PR/branch
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
NODE_VERSION: '20'
|
||||
PNPM_VERSION: '9.15.0'
|
||||
|
||||
jobs:
|
||||
# ====================
|
||||
# 1. TEST BACKENDS
|
||||
# ====================
|
||||
test-backends:
|
||||
name: Test Backend - ${{ matrix.project }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
project:
|
||||
- maerchenzauber
|
||||
- manadeck
|
||||
- chat
|
||||
- nutriphi
|
||||
- picture
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build shared packages
|
||||
run: pnpm run build:packages
|
||||
|
||||
- name: Type check
|
||||
run: pnpm --filter @${{ matrix.project }}/backend type-check
|
||||
continue-on-error: true
|
||||
|
||||
- name: Run tests with coverage
|
||||
run: pnpm --filter @${{ matrix.project }}/backend test:cov
|
||||
env:
|
||||
NODE_ENV: test
|
||||
|
||||
- name: Check coverage thresholds
|
||||
run: |
|
||||
echo "Checking coverage meets 80% threshold..."
|
||||
# Jest/Vitest will fail if thresholds aren't met
|
||||
|
||||
# ====================
|
||||
# 2. TEST MOBILE APPS
|
||||
# ====================
|
||||
test-mobile:
|
||||
name: Test Mobile - ${{ matrix.project }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
project:
|
||||
- maerchenzauber
|
||||
- memoro
|
||||
- picture
|
||||
- chat
|
||||
- manacore
|
||||
- manadeck
|
||||
- nutriphi
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build shared packages
|
||||
run: pnpm run build:packages
|
||||
|
||||
- name: Type check
|
||||
run: pnpm --filter @${{ matrix.project }}/mobile type-check
|
||||
continue-on-error: true
|
||||
|
||||
- name: Run tests with coverage
|
||||
run: pnpm --filter @${{ matrix.project }}/mobile test -- --coverage --watchAll=false --ci
|
||||
env:
|
||||
NODE_ENV: test
|
||||
|
||||
# ====================
|
||||
# 3. TEST WEB APPS
|
||||
# ====================
|
||||
test-web:
|
||||
name: Test Web - ${{ matrix.project }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
project:
|
||||
- maerchenzauber
|
||||
- manacore
|
||||
- memoro
|
||||
- picture
|
||||
- uload
|
||||
- chat
|
||||
- manadeck
|
||||
- nutriphi
|
||||
- news
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build shared packages
|
||||
run: pnpm run build:packages
|
||||
|
||||
- name: Type check
|
||||
run: pnpm --filter @${{ matrix.project }}/web check
|
||||
continue-on-error: true
|
||||
|
||||
- name: Run unit tests with coverage
|
||||
run: pnpm --filter @${{ matrix.project }}/web test:unit -- --coverage --run
|
||||
env:
|
||||
NODE_ENV: test
|
||||
|
||||
# ====================
|
||||
# 4. E2E TESTS (WEB)
|
||||
# ====================
|
||||
test-e2e-web:
|
||||
name: E2E Web - ${{ matrix.project }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
project:
|
||||
- uload
|
||||
# Add other projects with E2E tests
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build shared packages
|
||||
run: pnpm run build:packages
|
||||
|
||||
- name: Install Playwright browsers
|
||||
run: pnpm --filter @${{ matrix.project }}/web exec playwright install --with-deps chromium
|
||||
|
||||
- name: Build application
|
||||
run: pnpm --filter @${{ matrix.project }}/web build
|
||||
|
||||
- name: Run E2E tests
|
||||
run: pnpm --filter @${{ matrix.project }}/web test:e2e
|
||||
env:
|
||||
CI: true
|
||||
|
||||
- name: Upload Playwright report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: playwright-report-${{ matrix.project }}
|
||||
path: ./apps/${{ matrix.project }}/apps/web/playwright-report/
|
||||
retention-days: 7
|
||||
|
||||
# ====================
|
||||
# 5. TEST SHARED PACKAGES
|
||||
# ====================
|
||||
test-shared-packages:
|
||||
name: Test Shared Packages
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build shared packages
|
||||
run: pnpm run build:packages
|
||||
|
||||
- name: Type check shared packages
|
||||
run: pnpm --filter './packages/*' type-check
|
||||
continue-on-error: true
|
||||
|
||||
- name: Run tests with coverage
|
||||
run: pnpm --filter './packages/*' test -- --coverage --run
|
||||
continue-on-error: true
|
||||
env:
|
||||
NODE_ENV: test
|
||||
|
||||
# ====================
|
||||
# 6. LINT & FORMAT CHECK
|
||||
# ====================
|
||||
lint-and-format:
|
||||
name: Lint & Format
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build shared packages
|
||||
run: pnpm run build:packages
|
||||
|
||||
- name: Check formatting
|
||||
run: pnpm run format:check
|
||||
|
||||
- name: Run linters
|
||||
run: pnpm run lint
|
||||
continue-on-error: true
|
||||
|
||||
# ====================
|
||||
# 7. COVERAGE REPORT
|
||||
# ====================
|
||||
coverage-report:
|
||||
name: Generate Coverage Report
|
||||
needs:
|
||||
- test-backends
|
||||
- test-mobile
|
||||
- test-web
|
||||
- test-shared-packages
|
||||
runs-on: ubuntu-latest
|
||||
if: always()
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download all coverage reports
|
||||
uses: actions/download-artifact@v4
|
||||
continue-on-error: true
|
||||
|
||||
- name: Generate coverage summary
|
||||
run: |
|
||||
echo "## 📊 Test Coverage Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Jobs Status" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Backend Tests: ${{ needs.test-backends.result }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Mobile Tests: ${{ needs.test-mobile.result }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Web Tests: ${{ needs.test-web.result }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Shared Packages Tests: ${{ needs.test-shared-packages.result }}" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# ====================
|
||||
# 8. TEST STATUS CHECK
|
||||
# ====================
|
||||
test-status:
|
||||
name: All Tests Status
|
||||
needs:
|
||||
- test-backends
|
||||
- test-mobile
|
||||
- test-web
|
||||
- test-shared-packages
|
||||
- lint-and-format
|
||||
runs-on: ubuntu-latest
|
||||
if: always()
|
||||
|
||||
steps:
|
||||
- name: Check test results
|
||||
run: |
|
||||
if [ "${{ needs.test-backends.result }}" != "success" ] || \
|
||||
[ "${{ needs.test-mobile.result }}" != "success" ] || \
|
||||
[ "${{ needs.test-web.result }}" != "success" ] || \
|
||||
[ "${{ needs.test-shared-packages.result }}" != "success" ]; then
|
||||
echo "❌ Some tests failed"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ All tests passed"
|
||||
|
||||
- name: Post PR comment
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const status = '${{ needs.test-status.result }}' === 'success' ? '✅' : '❌';
|
||||
const body = `## ${status} Test Suite Results
|
||||
|
||||
**Status**: ${status === '✅' ? 'All tests passed!' : 'Some tests failed'}
|
||||
|
||||
### Test Coverage
|
||||
- Backend: ${{ needs.test-backends.result }}
|
||||
- Mobile: ${{ needs.test-mobile.result }}
|
||||
- Web: ${{ needs.test-web.result }}
|
||||
- Shared Packages: ${{ needs.test-shared-packages.result }}
|
||||
- Lint & Format: ${{ needs.lint-and-format.result }}
|
||||
|
||||
View detailed results in the [Actions tab](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})
|
||||
`;
|
||||
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue