managarten/.github/workflows/ci.yml
Till JS c7af693c6d feat(crypto): Phase C — build-time registry ↔ Dexie audit
Before: adding a new Dexie table left the encryption decision implicit.
If you forgot to register it, the table silently shipped in plaintext
forever — no error, no warning, no footprint anywhere. The architecture
audit flagged this as the root of Concern 1.

- `scripts/audit-crypto-registry.mjs` parses database.ts's `.stores()`
  blocks and registry.ts's entries, then enforces three invariants:
    1. Every Dexie table is either in the encryption registry OR in the
       new `plaintext-allowlist.ts` — one conscious classification per
       table.
    2. No dead registry entries (referring to tables that no longer
       exist in Dexie).
    3. No table appears in both — single authoritative source.
- `plaintext-allowlist.ts` auto-seeded from current state. 105 entries,
  each tagged `// TODO: audit` as an invitation to review whether the
  table truly holds nothing sensitive. The allowlist is intentionally
  a separate file so additions are reviewable on their own (not buried
  inside database.ts schema bumps).
- Wired into `pnpm run check:crypto` + CI validate job — a new table
  now fails the PR check instead of slipping past review.
- `check:crypto:seed` regenerates the allowlist if ever needed.

Verified: drift simulation (removing aiMissions from the allowlist)
fails the audit with a clear message pointing at the missing
classification. Current state passes: 187 Dexie tables, 82 encrypted,
105 explicit plaintext.

Concern 1 is now fully closed (A: typed registry entries, B: dev-mode
runtime drift check, C: build-time audit enforcing coverage).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 14:36:32 +02:00

1293 lines
49 KiB
YAML

# 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: Audit crypto registry (Dexie ↔ registry ↔ allowlist)
run: pnpm run check:crypto
- name: Type check
run: pnpm run type-check
- name: Format check
run: pnpm run format:check
- name: Lint
run: pnpm run lint
# Coverage is produced but not yet gating — we're establishing a baseline
# before flipping to hard-fail. `continue-on-error: true` surfaces a
# proper failure signal in the PR (unlike `|| true`, which silently
# swallowed everything) but still lets the overall job pass.
# Flip to blocking by removing `continue-on-error` once the suite is
# green on main for a full week. Tracked in docs/plans/concern-4-coverage.md.
- name: Test (with coverage)
id: tests
continue-on-error: true
run: pnpm run test:coverage
- name: Annotate test failure (non-blocking)
if: steps.tests.outcome == 'failure'
run: |
echo "::warning title=Test suite failed::pnpm run test:coverage returned a non-zero exit code. Coverage artifacts were still uploaded. See the 'Test (with coverage)' step above for details."
- name: Upload coverage reports
if: always()
uses: actions/upload-artifact@v4
with:
name: coverage-${{ github.run_id }}
path: |
**/coverage/lcov.info
**/coverage/coverage-summary.json
if-no-files-found: warn
retention-days: 14
- 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