# CI Pipeline: Validates code on PRs, builds images on push # # Flow: # PR → dev/main : Runs validation (required status check) # Push → dev/main : Builds Docker images (only changed services) # # Selective Builds: # - Only builds services that have changed files # - Detects changes in service path, shared packages, and root config # - Use workflow_dispatch to force rebuild all services # # Deployments are triggered separately: # - Manual: workflow_dispatch on cd-staging.yml / cd-production.yml # - Tag-based: push tag like "chat-staging-v1.0.0" triggers cd-staging-tagged.yml name: CI on: push: branches: - main - dev pull_request: branches: - main - dev workflow_dispatch: inputs: force_build_all: description: 'Force rebuild all services' required: false default: 'false' type: boolean concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: NODE_VERSION: '20' PNPM_VERSION: '9.15.0' jobs: # =========================================== # Detect Changes - determines which services to build # =========================================== detect-changes: name: Detect Changes runs-on: ubuntu-latest if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' outputs: mana-auth: ${{ steps.changes.outputs.mana-auth }} mana-sync: ${{ steps.changes.outputs.mana-sync }} mana-media: ${{ steps.changes.outputs.mana-media }} mana-notify: ${{ steps.changes.outputs.mana-notify }} mana-api-gateway: ${{ steps.changes.outputs.mana-api-gateway }} mana-crawler: ${{ steps.changes.outputs.mana-crawler }} mana-credits: ${{ steps.changes.outputs.mana-credits }} mana-search: ${{ steps.changes.outputs.mana-search }} mana-web: ${{ steps.changes.outputs.mana-web }} chat-backend: ${{ steps.changes.outputs.chat-backend }} chat-web: ${{ steps.changes.outputs.chat-web }} todo-backend: ${{ steps.changes.outputs.todo-backend }} todo-web: ${{ steps.changes.outputs.todo-web }} calendar-backend: ${{ steps.changes.outputs.calendar-backend }} calendar-web: ${{ steps.changes.outputs.calendar-web }} clock-web: ${{ steps.changes.outputs.clock-web }} contacts-backend: ${{ steps.changes.outputs.contacts-backend }} contacts-web: ${{ steps.changes.outputs.contacts-web }} presi-web: ${{ steps.changes.outputs.presi-web }} storage-backend: ${{ steps.changes.outputs.storage-backend }} storage-web: ${{ steps.changes.outputs.storage-web }} telegram-stats-bot: ${{ steps.changes.outputs.telegram-stats-bot }} food-backend: ${{ steps.changes.outputs.food-backend }} food-web: ${{ steps.changes.outputs.food-web }} skilltree-web: ${{ steps.changes.outputs.skilltree-web }} any-changes: ${{ steps.changes.outputs.any-changes }} steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 - name: Check for changes id: changes run: | # Force build all if workflow_dispatch with force_build_all if [ "${{ github.event_name }}" == "workflow_dispatch" ] && [ "${{ inputs.force_build_all }}" == "true" ]; then echo "Force rebuild all services requested" echo "mana-auth=true" >> $GITHUB_OUTPUT echo "mana-sync=true" >> $GITHUB_OUTPUT echo "mana-media=true" >> $GITHUB_OUTPUT echo "mana-notify=true" >> $GITHUB_OUTPUT echo "mana-api-gateway=true" >> $GITHUB_OUTPUT echo "mana-crawler=true" >> $GITHUB_OUTPUT echo "mana-credits=true" >> $GITHUB_OUTPUT echo "mana-search=true" >> $GITHUB_OUTPUT echo "mana-web=true" >> $GITHUB_OUTPUT echo "chat-backend=true" >> $GITHUB_OUTPUT echo "chat-web=true" >> $GITHUB_OUTPUT echo "todo-backend=true" >> $GITHUB_OUTPUT echo "todo-web=true" >> $GITHUB_OUTPUT echo "calendar-backend=true" >> $GITHUB_OUTPUT echo "calendar-web=true" >> $GITHUB_OUTPUT echo "clock-web=true" >> $GITHUB_OUTPUT echo "contacts-backend=true" >> $GITHUB_OUTPUT echo "contacts-web=true" >> $GITHUB_OUTPUT echo "presi-web=true" >> $GITHUB_OUTPUT echo "storage-backend=true" >> $GITHUB_OUTPUT echo "storage-web=true" >> $GITHUB_OUTPUT echo "telegram-stats-bot=true" >> $GITHUB_OUTPUT echo "food-backend=true" >> $GITHUB_OUTPUT echo "food-web=true" >> $GITHUB_OUTPUT echo "skilltree-web=true" >> $GITHUB_OUTPUT echo "any-changes=true" >> $GITHUB_OUTPUT exit 0 fi # Get changed files between current commit and previous if [ "${{ github.event_name }}" == "push" ]; then BASE_SHA="${{ github.event.before }}" HEAD_SHA="${{ github.sha }}" # Handle initial commit case if [ "$BASE_SHA" == "0000000000000000000000000000000000000000" ]; then CHANGED_FILES=$(git ls-files) else CHANGED_FILES=$(git diff --name-only $BASE_SHA $HEAD_SHA 2>/dev/null || git ls-files) fi else # workflow_dispatch without force - build all echo "Workflow dispatch without force_build_all - building all" echo "mana-auth=true" >> $GITHUB_OUTPUT echo "mana-sync=true" >> $GITHUB_OUTPUT echo "mana-media=true" >> $GITHUB_OUTPUT echo "mana-notify=true" >> $GITHUB_OUTPUT echo "mana-api-gateway=true" >> $GITHUB_OUTPUT echo "mana-crawler=true" >> $GITHUB_OUTPUT echo "mana-credits=true" >> $GITHUB_OUTPUT echo "mana-search=true" >> $GITHUB_OUTPUT echo "mana-web=true" >> $GITHUB_OUTPUT echo "chat-backend=true" >> $GITHUB_OUTPUT echo "chat-web=true" >> $GITHUB_OUTPUT echo "todo-backend=true" >> $GITHUB_OUTPUT echo "todo-web=true" >> $GITHUB_OUTPUT echo "calendar-backend=true" >> $GITHUB_OUTPUT echo "calendar-web=true" >> $GITHUB_OUTPUT echo "clock-web=true" >> $GITHUB_OUTPUT echo "contacts-backend=true" >> $GITHUB_OUTPUT echo "contacts-web=true" >> $GITHUB_OUTPUT echo "presi-web=true" >> $GITHUB_OUTPUT echo "storage-backend=true" >> $GITHUB_OUTPUT echo "storage-web=true" >> $GITHUB_OUTPUT echo "telegram-stats-bot=true" >> $GITHUB_OUTPUT echo "food-backend=true" >> $GITHUB_OUTPUT echo "food-web=true" >> $GITHUB_OUTPUT echo "skilltree-web=true" >> $GITHUB_OUTPUT echo "any-changes=true" >> $GITHUB_OUTPUT exit 0 fi echo "Changed files:" echo "$CHANGED_FILES" # Common files that trigger all builds COMMON_PATTERNS="pnpm-lock.yaml|pnpm-workspace.yaml|package.json|.github/workflows/ci.yml" # Shared packages that affect multiple services SHARED_AUTH_PATTERN="packages/shared-auth/|packages/shared-types/" SHARED_UI_PATTERN="packages/shared-ui/|packages/shared-theme/|packages/shared-icons/|packages/shared-tailwind/|packages/shared-branding/" SHARED_WEB_PATTERN="packages/shared-auth-ui/|packages/shared-theme-ui/|packages/shared-feedback-ui/|packages/shared-subscription-ui/|packages/shared-splitscreen/" # Function to check if any pattern matches check_pattern() { echo "$CHANGED_FILES" | grep -qE "$1" && echo "true" || echo "false" } # Check common changes COMMON_CHANGED=$(check_pattern "$COMMON_PATTERNS") SHARED_AUTH_CHANGED=$(check_pattern "$SHARED_AUTH_PATTERN") SHARED_UI_CHANGED=$(check_pattern "$SHARED_UI_PATTERN") SHARED_WEB_CHANGED=$(check_pattern "$SHARED_WEB_PATTERN") echo "Common changed: $COMMON_CHANGED" echo "Shared auth changed: $SHARED_AUTH_CHANGED" echo "Shared UI changed: $SHARED_UI_CHANGED" echo "Shared web changed: $SHARED_WEB_CHANGED" # mana-auth: services/mana-auth AUTH_CHANGED=$(check_pattern "services/mana-auth/") if [ "$COMMON_CHANGED" == "true" ] || [ "$AUTH_CHANGED" == "true" ]; then echo "mana-auth=true" >> $GITHUB_OUTPUT else echo "mana-auth=false" >> $GITHUB_OUTPUT fi # mana-search: services/mana-search SEARCH_CHANGED=$(check_pattern "services/mana-search/") if [ "$COMMON_CHANGED" == "true" ] || [ "$SEARCH_CHANGED" == "true" ]; then echo "mana-search=true" >> $GITHUB_OUTPUT else echo "mana-search=false" >> $GITHUB_OUTPUT fi # Go services (standalone — no COMMON trigger, only own path + shared-go) SHARED_GO_PATTERN="packages/shared-go/" SHARED_GO_CHANGED=$(check_pattern "$SHARED_GO_PATTERN") for GO_SVC in mana-sync mana-notify mana-api-gateway mana-crawler; do SVC_CHANGED=$(check_pattern "services/${GO_SVC}/") if [ "$SVC_CHANGED" == "true" ] || [ "$SHARED_GO_CHANGED" == "true" ]; then echo "${GO_SVC}=true" >> $GITHUB_OUTPUT else echo "${GO_SVC}=false" >> $GITHUB_OUTPUT fi done # Hono/Bun services (standalone — only own path) for HONO_SVC in mana-media mana-credits; do SVC_CHANGED=$(check_pattern "services/${HONO_SVC}/") if [ "$SVC_CHANGED" == "true" ]; then echo "${HONO_SVC}=true" >> $GITHUB_OUTPUT else echo "${HONO_SVC}=false" >> $GITHUB_OUTPUT fi done # mana-web: apps/mana/apps/web + shared packages MANA_WEB_CHANGED=$(check_pattern "apps/mana/apps/web/|apps/mana/packages/") if [ "$COMMON_CHANGED" == "true" ] || [ "$SHARED_AUTH_CHANGED" == "true" ] || [ "$SHARED_UI_CHANGED" == "true" ] || [ "$SHARED_WEB_CHANGED" == "true" ] || [ "$MANA_WEB_CHANGED" == "true" ]; then echo "mana-web=true" >> $GITHUB_OUTPUT else echo "mana-web=false" >> $GITHUB_OUTPUT fi # chat-backend: apps/chat/apps/backend + packages CHAT_BACKEND_CHANGED=$(check_pattern "apps/chat/apps/backend/|apps/chat/packages/") if [ "$COMMON_CHANGED" == "true" ] || [ "$SHARED_AUTH_CHANGED" == "true" ] || [ "$CHAT_BACKEND_CHANGED" == "true" ]; then echo "chat-backend=true" >> $GITHUB_OUTPUT else echo "chat-backend=false" >> $GITHUB_OUTPUT fi # chat-web: apps/chat/apps/web + packages + shared web CHAT_WEB_CHANGED=$(check_pattern "apps/chat/apps/web/|apps/chat/packages/") if [ "$COMMON_CHANGED" == "true" ] || [ "$SHARED_AUTH_CHANGED" == "true" ] || [ "$SHARED_UI_CHANGED" == "true" ] || [ "$SHARED_WEB_CHANGED" == "true" ] || [ "$CHAT_WEB_CHANGED" == "true" ]; then echo "chat-web=true" >> $GITHUB_OUTPUT else echo "chat-web=false" >> $GITHUB_OUTPUT fi # todo-backend TODO_BACKEND_CHANGED=$(check_pattern "apps/todo/apps/backend/|apps/todo/packages/") if [ "$COMMON_CHANGED" == "true" ] || [ "$SHARED_AUTH_CHANGED" == "true" ] || [ "$TODO_BACKEND_CHANGED" == "true" ]; then echo "todo-backend=true" >> $GITHUB_OUTPUT else echo "todo-backend=false" >> $GITHUB_OUTPUT fi # todo-web TODO_WEB_CHANGED=$(check_pattern "apps/todo/apps/web/|apps/todo/packages/") if [ "$COMMON_CHANGED" == "true" ] || [ "$SHARED_AUTH_CHANGED" == "true" ] || [ "$SHARED_UI_CHANGED" == "true" ] || [ "$SHARED_WEB_CHANGED" == "true" ] || [ "$TODO_WEB_CHANGED" == "true" ]; then echo "todo-web=true" >> $GITHUB_OUTPUT else echo "todo-web=false" >> $GITHUB_OUTPUT fi # calendar-backend CALENDAR_BACKEND_CHANGED=$(check_pattern "apps/calendar/apps/backend/|apps/calendar/packages/") if [ "$COMMON_CHANGED" == "true" ] || [ "$SHARED_AUTH_CHANGED" == "true" ] || [ "$CALENDAR_BACKEND_CHANGED" == "true" ]; then echo "calendar-backend=true" >> $GITHUB_OUTPUT else echo "calendar-backend=false" >> $GITHUB_OUTPUT fi # calendar-web CALENDAR_WEB_CHANGED=$(check_pattern "apps/calendar/apps/web/|apps/calendar/packages/") if [ "$COMMON_CHANGED" == "true" ] || [ "$SHARED_AUTH_CHANGED" == "true" ] || [ "$SHARED_UI_CHANGED" == "true" ] || [ "$SHARED_WEB_CHANGED" == "true" ] || [ "$CALENDAR_WEB_CHANGED" == "true" ]; then echo "calendar-web=true" >> $GITHUB_OUTPUT else echo "calendar-web=false" >> $GITHUB_OUTPUT fi # clock-backend: REMOVED — migrated to local-first # clock-web CLOCK_WEB_CHANGED=$(check_pattern "apps/clock/apps/web/|apps/clock/packages/") if [ "$COMMON_CHANGED" == "true" ] || [ "$SHARED_AUTH_CHANGED" == "true" ] || [ "$SHARED_UI_CHANGED" == "true" ] || [ "$SHARED_WEB_CHANGED" == "true" ] || [ "$CLOCK_WEB_CHANGED" == "true" ]; then echo "clock-web=true" >> $GITHUB_OUTPUT else echo "clock-web=false" >> $GITHUB_OUTPUT fi # contacts-backend CONTACTS_BACKEND_CHANGED=$(check_pattern "apps/contacts/apps/backend/") if [ "$COMMON_CHANGED" == "true" ] || [ "$SHARED_AUTH_CHANGED" == "true" ] || [ "$CONTACTS_BACKEND_CHANGED" == "true" ]; then echo "contacts-backend=true" >> $GITHUB_OUTPUT else echo "contacts-backend=false" >> $GITHUB_OUTPUT fi # contacts-web CONTACTS_WEB_CHANGED=$(check_pattern "apps/contacts/apps/web/") if [ "$COMMON_CHANGED" == "true" ] || [ "$SHARED_AUTH_CHANGED" == "true" ] || [ "$SHARED_UI_CHANGED" == "true" ] || [ "$SHARED_WEB_CHANGED" == "true" ] || [ "$CONTACTS_WEB_CHANGED" == "true" ]; then echo "contacts-web=true" >> $GITHUB_OUTPUT else echo "contacts-web=false" >> $GITHUB_OUTPUT fi # presi-backend: REMOVED — replaced by Hono server # presi-web PRESI_WEB_CHANGED=$(check_pattern "apps/presi/apps/web/|apps/presi/packages/") if [ "$COMMON_CHANGED" == "true" ] || [ "$SHARED_AUTH_CHANGED" == "true" ] || [ "$SHARED_UI_CHANGED" == "true" ] || [ "$SHARED_WEB_CHANGED" == "true" ] || [ "$PRESI_WEB_CHANGED" == "true" ]; then echo "presi-web=true" >> $GITHUB_OUTPUT else echo "presi-web=false" >> $GITHUB_OUTPUT fi # storage-backend STORAGE_BACKEND_CHANGED=$(check_pattern "apps/storage/apps/backend/|apps/storage/packages/|packages/shared-storage/") if [ "$COMMON_CHANGED" == "true" ] || [ "$SHARED_AUTH_CHANGED" == "true" ] || [ "$STORAGE_BACKEND_CHANGED" == "true" ]; then echo "storage-backend=true" >> $GITHUB_OUTPUT else echo "storage-backend=false" >> $GITHUB_OUTPUT fi # storage-web STORAGE_WEB_CHANGED=$(check_pattern "apps/storage/apps/web/|apps/storage/packages/") if [ "$COMMON_CHANGED" == "true" ] || [ "$SHARED_AUTH_CHANGED" == "true" ] || [ "$SHARED_UI_CHANGED" == "true" ] || [ "$SHARED_WEB_CHANGED" == "true" ] || [ "$STORAGE_WEB_CHANGED" == "true" ]; then echo "storage-web=true" >> $GITHUB_OUTPUT else echo "storage-web=false" >> $GITHUB_OUTPUT fi # telegram-stats-bot TELEGRAM_STATS_BOT_CHANGED=$(check_pattern "services/telegram-stats-bot/") if [ "$COMMON_CHANGED" == "true" ] || [ "$TELEGRAM_STATS_BOT_CHANGED" == "true" ]; then echo "telegram-stats-bot=true" >> $GITHUB_OUTPUT else echo "telegram-stats-bot=false" >> $GITHUB_OUTPUT fi # food-backend FOOD_BACKEND_CHANGED=$(check_pattern "apps/food/apps/backend/|apps/food/packages/") if [ "$COMMON_CHANGED" == "true" ] || [ "$SHARED_AUTH_CHANGED" == "true" ] || [ "$FOOD_BACKEND_CHANGED" == "true" ]; then echo "food-backend=true" >> $GITHUB_OUTPUT else echo "food-backend=false" >> $GITHUB_OUTPUT fi # food-web FOOD_WEB_CHANGED=$(check_pattern "apps/food/apps/web/|apps/food/packages/") if [ "$COMMON_CHANGED" == "true" ] || [ "$SHARED_AUTH_CHANGED" == "true" ] || [ "$SHARED_UI_CHANGED" == "true" ] || [ "$SHARED_WEB_CHANGED" == "true" ] || [ "$FOOD_WEB_CHANGED" == "true" ]; then echo "food-web=true" >> $GITHUB_OUTPUT else echo "food-web=false" >> $GITHUB_OUTPUT fi # skilltree-backend: REMOVED — migrated to local-first # skilltree-web SKILLTREE_WEB_CHANGED=$(check_pattern "apps/skilltree/apps/web/|apps/skilltree/packages/") if [ "$COMMON_CHANGED" == "true" ] || [ "$SHARED_AUTH_CHANGED" == "true" ] || [ "$SHARED_UI_CHANGED" == "true" ] || [ "$SHARED_WEB_CHANGED" == "true" ] || [ "$SKILLTREE_WEB_CHANGED" == "true" ]; then echo "skilltree-web=true" >> $GITHUB_OUTPUT else echo "skilltree-web=false" >> $GITHUB_OUTPUT fi # quotes-backend: REMOVED — migrated to local-first # Check if any service needs building if grep -q "=true" $GITHUB_OUTPUT; then echo "any-changes=true" >> $GITHUB_OUTPUT else echo "any-changes=false" >> $GITHUB_OUTPUT fi - name: Summary run: | echo "## Build Summary" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| Service | Will Build |" >> $GITHUB_STEP_SUMMARY echo "|---------|------------|" >> $GITHUB_STEP_SUMMARY echo "| mana-auth | ${{ steps.changes.outputs.mana-auth }} |" >> $GITHUB_STEP_SUMMARY echo "| mana-sync | ${{ steps.changes.outputs.mana-sync }} |" >> $GITHUB_STEP_SUMMARY echo "| mana-media | ${{ steps.changes.outputs.mana-media }} |" >> $GITHUB_STEP_SUMMARY echo "| mana-notify | ${{ steps.changes.outputs.mana-notify }} |" >> $GITHUB_STEP_SUMMARY echo "| mana-api-gateway | ${{ steps.changes.outputs.mana-api-gateway }} |" >> $GITHUB_STEP_SUMMARY echo "| mana-crawler | ${{ steps.changes.outputs.mana-crawler }} |" >> $GITHUB_STEP_SUMMARY echo "| mana-credits | ${{ steps.changes.outputs.mana-credits }} |" >> $GITHUB_STEP_SUMMARY echo "| mana-search | ${{ steps.changes.outputs.mana-search }} |" >> $GITHUB_STEP_SUMMARY echo "| mana-web | ${{ steps.changes.outputs.mana-web }} |" >> $GITHUB_STEP_SUMMARY echo "| chat-backend | ${{ steps.changes.outputs.chat-backend }} |" >> $GITHUB_STEP_SUMMARY echo "| chat-web | ${{ steps.changes.outputs.chat-web }} |" >> $GITHUB_STEP_SUMMARY echo "| todo-backend | ${{ steps.changes.outputs.todo-backend }} |" >> $GITHUB_STEP_SUMMARY echo "| todo-web | ${{ steps.changes.outputs.todo-web }} |" >> $GITHUB_STEP_SUMMARY echo "| calendar-backend | ${{ steps.changes.outputs.calendar-backend }} |" >> $GITHUB_STEP_SUMMARY echo "| calendar-web | ${{ steps.changes.outputs.calendar-web }} |" >> $GITHUB_STEP_SUMMARY echo "| clock-backend | removed |" >> $GITHUB_STEP_SUMMARY echo "| clock-web | ${{ steps.changes.outputs.clock-web }} |" >> $GITHUB_STEP_SUMMARY echo "| contacts-backend | ${{ steps.changes.outputs.contacts-backend }} |" >> $GITHUB_STEP_SUMMARY echo "| contacts-web | ${{ steps.changes.outputs.contacts-web }} |" >> $GITHUB_STEP_SUMMARY echo "| presi-backend | removed |" >> $GITHUB_STEP_SUMMARY echo "| presi-web | ${{ steps.changes.outputs.presi-web }} |" >> $GITHUB_STEP_SUMMARY echo "| storage-backend | ${{ steps.changes.outputs.storage-backend }} |" >> $GITHUB_STEP_SUMMARY echo "| storage-web | ${{ steps.changes.outputs.storage-web }} |" >> $GITHUB_STEP_SUMMARY echo "| telegram-stats-bot | ${{ steps.changes.outputs.telegram-stats-bot }} |" >> $GITHUB_STEP_SUMMARY echo "| food-backend | ${{ steps.changes.outputs.food-backend }} |" >> $GITHUB_STEP_SUMMARY echo "| food-web | ${{ steps.changes.outputs.food-web }} |" >> $GITHUB_STEP_SUMMARY echo "| skilltree-backend | removed |" >> $GITHUB_STEP_SUMMARY echo "| skilltree-web | ${{ steps.changes.outputs.skilltree-web }} |" >> $GITHUB_STEP_SUMMARY echo "| quotes-backend | removed |" >> $GITHUB_STEP_SUMMARY # =========================================== # Validation job - runs on PRs # =========================================== validate: name: Validate runs-on: ubuntu-latest if: github.event_name == 'pull_request' 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: Validate monorepo best practices run: pnpm run validate:monorepo - name: Type check run: pnpm run type-check - name: Format check run: pnpm run format:check - name: Lint run: pnpm run lint - name: Test run: pnpm run test || true - name: Security Audit run: | # Run pnpm audit and capture exit code # Exit 0 if only moderate/low vulnerabilities pnpm audit --audit-level=high || { echo "::warning::Security vulnerabilities found. Run 'pnpm audit' locally for details." exit 0 # Don't fail build on audit issues (just warn) } - name: Check for known vulnerable packages run: | # Check for packages with known critical vulnerabilities # This is a basic check - for production, consider Snyk or similar if grep -r "lodash@[0-3]\." pnpm-lock.yaml 2>/dev/null; then echo "::warning::Potentially vulnerable lodash version detected" fi if grep -r "axios@0\.[0-9]\." pnpm-lock.yaml 2>/dev/null; then echo "::warning::Potentially vulnerable axios version detected" fi # =========================================== # Auth flow integration test # =========================================== # Spins up postgres + redis + mailpit + mana-auth + mana-notify via # docker-compose.test.yml and runs tests/integration/auth-flow.test.ts. # Catches register/verify/login/JWT/encryption-vault regressions before # they can be merged. Required check — never bypass. auth-integration: name: Auth flow integration test runs-on: ubuntu-latest if: github.event_name == 'pull_request' timeout-minutes: 15 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: Setup Bun uses: oven-sh/setup-bun@v2 with: bun-version: latest - name: Install dependencies run: pnpm install --frozen-lockfile - name: Run auth flow integration test run: ./scripts/run-integration-tests.sh - name: Dump test stack logs on failure if: failure() run: | echo "::group::mana-auth logs" docker logs mana-test-mana-auth 2>&1 | tail -200 || true echo "::endgroup::" echo "::group::mana-notify logs" docker logs mana-test-mana-notify 2>&1 | tail -200 || true echo "::endgroup::" echo "::group::mailpit messages" curl -s http://localhost:8026/api/v1/messages | head -100 || true echo "::endgroup::" # =========================================== # Build Docker images - only changed services # =========================================== build-mana-auth: name: Build mana-auth runs-on: ubuntu-latest needs: detect-changes if: needs.detect-changes.outputs.mana-auth == 'true' steps: - uses: actions/checkout@v4 - uses: docker/setup-qemu-action@v3 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/metadata-action@v5 id: meta with: images: ghcr.io/${{ github.repository_owner }}/mana-auth tags: type=raw,value=latest - uses: docker/build-push-action@v5 with: context: . file: services/mana-auth/Dockerfile platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max build-mana-search: name: Build mana-search runs-on: ubuntu-latest needs: detect-changes if: needs.detect-changes.outputs.mana-search == 'true' steps: - uses: actions/checkout@v4 - uses: docker/setup-qemu-action@v3 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/metadata-action@v5 id: meta with: images: ghcr.io/${{ github.repository_owner }}/mana-search tags: type=raw,value=latest - uses: docker/build-push-action@v5 with: context: . file: services/mana-search/Dockerfile platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max # Go services build-mana-sync: name: Build mana-sync runs-on: ubuntu-latest needs: detect-changes if: needs.detect-changes.outputs.mana-sync == 'true' steps: - uses: actions/checkout@v4 - uses: docker/setup-qemu-action@v3 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/metadata-action@v5 id: meta with: images: ghcr.io/${{ github.repository_owner }}/mana-sync tags: type=raw,value=latest - uses: docker/build-push-action@v5 with: context: . file: services/mana-sync/Dockerfile platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max build-mana-notify: name: Build mana-notify runs-on: ubuntu-latest needs: detect-changes if: needs.detect-changes.outputs.mana-notify == 'true' steps: - uses: actions/checkout@v4 - uses: docker/setup-qemu-action@v3 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/metadata-action@v5 id: meta with: images: ghcr.io/${{ github.repository_owner }}/mana-notify tags: type=raw,value=latest - uses: docker/build-push-action@v5 with: context: . file: services/mana-notify/Dockerfile platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max build-mana-api-gateway: name: Build mana-api-gateway runs-on: ubuntu-latest needs: detect-changes if: needs.detect-changes.outputs.mana-api-gateway == 'true' steps: - uses: actions/checkout@v4 - uses: docker/setup-qemu-action@v3 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/metadata-action@v5 id: meta with: images: ghcr.io/${{ github.repository_owner }}/mana-api-gateway tags: type=raw,value=latest - uses: docker/build-push-action@v5 with: context: . file: services/mana-api-gateway/Dockerfile platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max build-mana-crawler: name: Build mana-crawler runs-on: ubuntu-latest needs: detect-changes if: needs.detect-changes.outputs.mana-crawler == 'true' steps: - uses: actions/checkout@v4 - uses: docker/setup-qemu-action@v3 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/metadata-action@v5 id: meta with: images: ghcr.io/${{ github.repository_owner }}/mana-crawler tags: type=raw,value=latest - uses: docker/build-push-action@v5 with: context: . file: services/mana-crawler/Dockerfile platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max # Hono/Bun services build-mana-media: name: Build mana-media runs-on: ubuntu-latest needs: detect-changes if: needs.detect-changes.outputs.mana-media == 'true' steps: - uses: actions/checkout@v4 - uses: docker/setup-qemu-action@v3 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/metadata-action@v5 id: meta with: images: ghcr.io/${{ github.repository_owner }}/mana-media tags: type=raw,value=latest - uses: docker/build-push-action@v5 with: context: services/mana-media/apps/api file: services/mana-media/apps/api/Dockerfile platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max build-mana-credits: name: Build mana-credits runs-on: ubuntu-latest needs: detect-changes if: needs.detect-changes.outputs.mana-credits == 'true' steps: - uses: actions/checkout@v4 - uses: docker/setup-qemu-action@v3 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/metadata-action@v5 id: meta with: images: ghcr.io/${{ github.repository_owner }}/mana-credits tags: type=raw,value=latest - uses: docker/build-push-action@v5 with: context: services/mana-credits file: services/mana-credits/Dockerfile platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max build-mana-web: name: Build mana-web runs-on: ubuntu-latest needs: detect-changes if: needs.detect-changes.outputs.mana-web == 'true' steps: - uses: actions/checkout@v4 - uses: docker/setup-qemu-action@v3 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/metadata-action@v5 id: meta with: images: ghcr.io/${{ github.repository_owner }}/mana-web tags: type=raw,value=latest - uses: docker/build-push-action@v5 with: context: . file: apps/mana/apps/web/Dockerfile platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max build-chat-backend: name: Build chat-backend runs-on: ubuntu-latest needs: detect-changes if: needs.detect-changes.outputs.chat-backend == 'true' steps: - uses: actions/checkout@v4 - uses: docker/setup-qemu-action@v3 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/metadata-action@v5 id: meta with: images: ghcr.io/${{ github.repository_owner }}/chat-backend tags: type=raw,value=latest - uses: docker/build-push-action@v5 with: context: . file: apps/chat/apps/backend/Dockerfile platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max build-chat-web: name: Build chat-web runs-on: ubuntu-latest needs: detect-changes if: needs.detect-changes.outputs.chat-web == 'true' steps: - uses: actions/checkout@v4 - uses: docker/setup-qemu-action@v3 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/metadata-action@v5 id: meta with: images: ghcr.io/${{ github.repository_owner }}/chat-web tags: type=raw,value=latest - uses: docker/build-push-action@v5 with: context: . file: apps/chat/apps/web/Dockerfile platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max build-todo-backend: name: Build todo-backend runs-on: ubuntu-latest needs: detect-changes if: needs.detect-changes.outputs.todo-backend == 'true' steps: - uses: actions/checkout@v4 - uses: docker/setup-qemu-action@v3 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/metadata-action@v5 id: meta with: images: ghcr.io/${{ github.repository_owner }}/todo-backend tags: type=raw,value=latest - uses: docker/build-push-action@v5 with: context: . file: apps/todo/apps/backend/Dockerfile platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max build-todo-web: name: Build todo-web runs-on: ubuntu-latest needs: detect-changes if: needs.detect-changes.outputs.todo-web == 'true' steps: - uses: actions/checkout@v4 - uses: docker/setup-qemu-action@v3 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/metadata-action@v5 id: meta with: images: ghcr.io/${{ github.repository_owner }}/todo-web tags: type=raw,value=latest - uses: docker/build-push-action@v5 with: context: . file: apps/todo/apps/web/Dockerfile platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max build-calendar-backend: name: Build calendar-backend runs-on: ubuntu-latest needs: detect-changes if: needs.detect-changes.outputs.calendar-backend == 'true' steps: - uses: actions/checkout@v4 - uses: docker/setup-qemu-action@v3 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/metadata-action@v5 id: meta with: images: ghcr.io/${{ github.repository_owner }}/calendar-backend tags: type=raw,value=latest - uses: docker/build-push-action@v5 with: context: . file: apps/calendar/apps/backend/Dockerfile platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max build-calendar-web: name: Build calendar-web runs-on: ubuntu-latest needs: detect-changes if: needs.detect-changes.outputs.calendar-web == 'true' steps: - uses: actions/checkout@v4 - uses: docker/setup-qemu-action@v3 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/metadata-action@v5 id: meta with: images: ghcr.io/${{ github.repository_owner }}/calendar-web tags: type=raw,value=latest - uses: docker/build-push-action@v5 with: context: . file: apps/calendar/apps/web/Dockerfile platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max # build-clock-backend: REMOVED — migrated to local-first build-clock-web: name: Build clock-web runs-on: ubuntu-latest needs: detect-changes if: needs.detect-changes.outputs.clock-web == 'true' steps: - uses: actions/checkout@v4 - uses: docker/setup-qemu-action@v3 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/metadata-action@v5 id: meta with: images: ghcr.io/${{ github.repository_owner }}/clock-web tags: type=raw,value=latest - uses: docker/build-push-action@v5 with: context: . file: apps/clock/apps/web/Dockerfile platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max build-contacts-backend: name: Build contacts-backend runs-on: ubuntu-latest needs: detect-changes if: needs.detect-changes.outputs.contacts-backend == 'true' steps: - uses: actions/checkout@v4 - uses: docker/setup-qemu-action@v3 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/metadata-action@v5 id: meta with: images: ghcr.io/${{ github.repository_owner }}/contacts-backend tags: type=raw,value=latest - uses: docker/build-push-action@v5 with: context: . file: apps/contacts/apps/backend/Dockerfile platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max build-contacts-web: name: Build contacts-web runs-on: ubuntu-latest needs: detect-changes if: needs.detect-changes.outputs.contacts-web == 'true' steps: - uses: actions/checkout@v4 - uses: docker/setup-qemu-action@v3 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/metadata-action@v5 id: meta with: images: ghcr.io/${{ github.repository_owner }}/contacts-web tags: type=raw,value=latest - uses: docker/build-push-action@v5 with: context: . file: apps/contacts/apps/web/Dockerfile platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max # build-presi-backend: REMOVED — replaced by Hono server build-presi-web: name: Build presi-web runs-on: ubuntu-latest needs: detect-changes if: needs.detect-changes.outputs.presi-web == 'true' steps: - uses: actions/checkout@v4 - uses: docker/setup-qemu-action@v3 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/metadata-action@v5 id: meta with: images: ghcr.io/${{ github.repository_owner }}/presi-web tags: type=raw,value=latest - uses: docker/build-push-action@v5 with: context: . file: apps/presi/apps/web/Dockerfile platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max build-storage-backend: name: Build storage-backend runs-on: ubuntu-latest needs: detect-changes if: needs.detect-changes.outputs.storage-backend == 'true' steps: - uses: actions/checkout@v4 - uses: docker/setup-qemu-action@v3 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/metadata-action@v5 id: meta with: images: ghcr.io/${{ github.repository_owner }}/storage-backend tags: type=raw,value=latest - uses: docker/build-push-action@v5 with: context: . file: apps/storage/apps/backend/Dockerfile # Note: arm64 disabled due to QEMU emulation issues with native deps platforms: linux/amd64 push: true tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max build-storage-web: name: Build storage-web runs-on: ubuntu-latest needs: detect-changes if: needs.detect-changes.outputs.storage-web == 'true' steps: - uses: actions/checkout@v4 - uses: docker/setup-qemu-action@v3 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/metadata-action@v5 id: meta with: images: ghcr.io/${{ github.repository_owner }}/storage-web tags: type=raw,value=latest - uses: docker/build-push-action@v5 with: context: . file: apps/storage/apps/web/Dockerfile platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max build-telegram-stats-bot: name: Build telegram-stats-bot runs-on: ubuntu-latest needs: detect-changes if: needs.detect-changes.outputs.telegram-stats-bot == 'true' steps: - uses: actions/checkout@v4 - uses: docker/setup-qemu-action@v3 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/metadata-action@v5 id: meta with: images: ghcr.io/${{ github.repository_owner }}/telegram-stats-bot tags: type=raw,value=latest - uses: docker/build-push-action@v5 with: context: . file: services/telegram-stats-bot/Dockerfile platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max build-food-backend: name: Build food-backend runs-on: ubuntu-latest needs: detect-changes if: needs.detect-changes.outputs.food-backend == 'true' steps: - uses: actions/checkout@v4 - uses: docker/setup-qemu-action@v3 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/metadata-action@v5 id: meta with: images: ghcr.io/${{ github.repository_owner }}/food-backend tags: type=raw,value=latest - uses: docker/build-push-action@v5 with: context: . file: apps/food/apps/backend/Dockerfile platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max build-food-web: name: Build food-web runs-on: ubuntu-latest needs: detect-changes if: needs.detect-changes.outputs.food-web == 'true' steps: - uses: actions/checkout@v4 - uses: docker/setup-qemu-action@v3 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/metadata-action@v5 id: meta with: images: ghcr.io/${{ github.repository_owner }}/food-web tags: type=raw,value=latest - uses: docker/build-push-action@v5 with: context: . file: apps/food/apps/web/Dockerfile platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max # build-skilltree-backend: REMOVED — migrated to local-first build-skilltree-web: name: Build skilltree-web runs-on: ubuntu-latest needs: detect-changes if: needs.detect-changes.outputs.skilltree-web == 'true' steps: - uses: actions/checkout@v4 - uses: docker/setup-qemu-action@v3 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/metadata-action@v5 id: meta with: images: ghcr.io/${{ github.repository_owner }}/skilltree-web tags: type=raw,value=latest - uses: docker/build-push-action@v5 with: context: . file: apps/skilltree/apps/web/Dockerfile platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max # =========================================== # Quotes Backend: REMOVED — migrated to local-first