docs: document theme bootstrap invariant #276
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| JAVA_VERSION: '17' | |
| NODE_VERSION: '22.x' | |
| PNPM_VERSION: '10' | |
| jobs: | |
| # --- Changes detection --- | |
| changes: | |
| name: Detect Changes | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| outputs: | |
| backend: ${{ steps.filter.outputs.backend }} | |
| console: ${{ steps.filter.outputs.console }} | |
| management: ${{ steps.filter.outputs.management }} | |
| docker: ${{ steps.filter.outputs.docker }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Detect changed paths | |
| id: filter | |
| uses: dorny/paths-filter@v4 | |
| with: | |
| filters: | | |
| backend: | |
| - 'backend-spring/**' | |
| - 'init-db/migrations/**' | |
| - '.github/workflows/ci.yml' | |
| console: | |
| - 'console/**' | |
| - 'shared/**' | |
| - '.github/workflows/ci.yml' | |
| management: | |
| - 'management/**' | |
| - 'shared/**' | |
| - '.github/workflows/ci.yml' | |
| docker: | |
| - 'backend-spring/Dockerfile' | |
| - 'console/Dockerfile' | |
| - 'management/Dockerfile' | |
| - 'docker-compose*.yml' | |
| - '.dockerignore' | |
| - 'console/nginx.conf' | |
| - 'management/nginx.conf' | |
| - '.github/workflows/ci.yml' | |
| # --- Backend: Build --- | |
| backend-build: | |
| name: Backend Build | |
| needs: changes | |
| if: needs.changes.outputs.backend == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Setup JDK 17 | |
| uses: actions/setup-java@v5 | |
| with: | |
| distribution: 'temurin' | |
| java-version: ${{ env.JAVA_VERSION }} | |
| cache: 'maven' | |
| - name: Grant Maven wrapper execute permission | |
| run: chmod +x backend-spring/mvnw | |
| - name: Build with Maven | |
| run: cd backend-spring && ./mvnw compile -B | |
| # --- Backend: Test --- | |
| backend-test: | |
| name: Backend Test | |
| needs: [changes, backend-build] | |
| if: needs.changes.outputs.backend == 'true' | |
| runs-on: ubuntu-latest | |
| services: | |
| mysql: | |
| image: mysql:9.1 | |
| env: | |
| MYSQL_ROOT_PASSWORD: root | |
| MYSQL_DATABASE: ulticode | |
| MYSQL_USER: ulticode | |
| MYSQL_PASSWORD: ulticode | |
| ports: | |
| - 23306:3306 | |
| options: >- | |
| --health-cmd="mysqladmin ping -h localhost -u root -proot" | |
| --health-interval=10s | |
| --health-timeout=5s | |
| --health-retries=5 | |
| redis: | |
| image: redis:7-alpine | |
| ports: | |
| - 26379:6379 | |
| options: >- | |
| --health-cmd="redis-cli ping" | |
| --health-interval=10s | |
| --health-timeout=5s | |
| --health-retries=5 | |
| env: | |
| DB_HOST: localhost | |
| DB_PORT: 23306 | |
| DB_USER: ulticode | |
| DB_PASSWORD: ulticode | |
| DB_NAME: ulticode | |
| REDIS_HOST: localhost | |
| REDIS_PORT: 26379 | |
| JWT_SECRET: test-jwt-secret-key-for-ci-minimum-32-characters-long | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Setup JDK 17 | |
| uses: actions/setup-java@v5 | |
| with: | |
| distribution: 'temurin' | |
| java-version: ${{ env.JAVA_VERSION }} | |
| cache: 'maven' | |
| - name: Grant Maven wrapper execute permission | |
| run: chmod +x backend-spring/mvnw | |
| - name: Run tests with CI profile | |
| run: cd backend-spring && ./mvnw test -Dspring.profiles.active=ci -Dtest='!*IT' -B | |
| - name: Upload test results on failure | |
| if: failure() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: test-results-backend | |
| path: backend-spring/target/surefire-reports/ | |
| retention-days: 7 | |
| # --- Backend: Validate Migrations --- | |
| migrate-validate: | |
| name: Validate Migrations | |
| needs: changes | |
| if: needs.changes.outputs.backend == 'true' | |
| runs-on: ubuntu-latest | |
| services: | |
| mysql: | |
| image: mysql:9.1 | |
| env: | |
| MYSQL_ROOT_PASSWORD: root | |
| MYSQL_DATABASE: ulticode_test | |
| MYSQL_USER: ulticode | |
| MYSQL_PASSWORD: ulticode | |
| ports: | |
| - 23306:3306 | |
| options: >- | |
| --health-cmd="mysqladmin ping -h localhost -u root -proot" | |
| --health-interval=10s | |
| --health-timeout=5s | |
| --health-retries=5 | |
| env: | |
| DB_HOST: localhost | |
| DB_PORT: 23306 | |
| DB_USER: ulticode | |
| DB_PASSWORD: ulticode | |
| DB_NAME: ulticode_test | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Wait for MySQL | |
| run: | | |
| for i in $(seq 1 30); do | |
| if mysqladmin ping -h localhost -P 23306 -u ulticode -pulticode 2>/dev/null; then | |
| echo "MySQL is ready" | |
| break | |
| fi | |
| echo "Waiting for MySQL... ($i/30)" | |
| sleep 2 | |
| done | |
| - name: Run and validate migrations | |
| run: | | |
| docker run --rm --network host \ | |
| -v "$PWD/init-db/migrations:/flyway/sql:ro" \ | |
| flyway/flyway:10.17.0 \ | |
| -url="jdbc:mysql://127.0.0.1:23306/ulticode?allowPublicKeyRetrieval=true&useSSL=false" \ | |
| -user=ulticode -password=ulticode -connectRetries=10 migrate | |
| docker run --rm --network host \ | |
| -v "$PWD/init-db/migrations:/flyway/sql:ro" \ | |
| flyway/flyway:10.17.0 \ | |
| -url="jdbc:mysql://127.0.0.1:23306/ulticode?allowPublicKeyRetrieval=true&useSSL=false" \ | |
| -user=ulticode -password=ulticode validate | |
| secret-scan: | |
| name: Secret Scan | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Run Gitleaks | |
| run: | | |
| docker run --rm \ | |
| -v "$PWD:/repo:ro" \ | |
| zricethezav/gitleaks:v8.24.2 \ | |
| dir /repo --config=/repo/.gitleaks.toml --redact --no-banner | |
| # --- Frontend: Lint --- | |
| frontend-lint: | |
| name: Lint (${{ matrix.app }}) | |
| needs: changes | |
| if: needs.changes.outputs.console == 'true' || needs.changes.outputs.management == 'true' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| app: [console, management] | |
| steps: | |
| - name: Check if app changed | |
| id: should-run | |
| if: (matrix.app == 'console' && needs.changes.outputs.console == 'true') || (matrix.app == 'management' && needs.changes.outputs.management == 'true') | |
| run: echo "run=true" >> $GITHUB_OUTPUT | |
| - name: Checkout repository | |
| if: steps.should-run.outputs.run == 'true' | |
| uses: actions/checkout@v6 | |
| - name: Enable pnpm via corepack | |
| if: steps.should-run.outputs.run == 'true' | |
| run: corepack enable && corepack prepare pnpm@${{ env.PNPM_VERSION }} --activate | |
| - name: Setup Node.js | |
| if: steps.should-run.outputs.run == 'true' | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'pnpm' | |
| cache-dependency-path: ${{ matrix.app }}/pnpm-lock.yaml | |
| - name: Install dependencies | |
| if: steps.should-run.outputs.run == 'true' | |
| working-directory: ${{ matrix.app }} | |
| env: | |
| CI: "false" | |
| GITHUB_ACTIONS: "false" | |
| run: pnpm install | |
| - name: Run lint | |
| if: steps.should-run.outputs.run == 'true' | |
| working-directory: ${{ matrix.app }} | |
| run: pnpm lint | |
| # --- Frontend: Type Check --- | |
| frontend-type-check: | |
| name: Type Check (${{ matrix.app }}) | |
| needs: changes | |
| if: needs.changes.outputs.console == 'true' || needs.changes.outputs.management == 'true' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| app: [console, management] | |
| steps: | |
| - name: Check if app changed | |
| id: should-run | |
| if: (matrix.app == 'console' && needs.changes.outputs.console == 'true') || (matrix.app == 'management' && needs.changes.outputs.management == 'true') | |
| run: echo "run=true" >> $GITHUB_OUTPUT | |
| - name: Checkout repository | |
| if: steps.should-run.outputs.run == 'true' | |
| uses: actions/checkout@v6 | |
| - name: Enable pnpm via corepack | |
| if: steps.should-run.outputs.run == 'true' | |
| run: corepack enable && corepack prepare pnpm@${{ env.PNPM_VERSION }} --activate | |
| - name: Setup Node.js | |
| if: steps.should-run.outputs.run == 'true' | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'pnpm' | |
| cache-dependency-path: ${{ matrix.app }}/pnpm-lock.yaml | |
| - name: Install dependencies | |
| if: steps.should-run.outputs.run == 'true' | |
| working-directory: ${{ matrix.app }} | |
| env: | |
| CI: "false" | |
| GITHUB_ACTIONS: "false" | |
| run: pnpm install | |
| - name: Run type-check | |
| if: steps.should-run.outputs.run == 'true' | |
| working-directory: ${{ matrix.app }} | |
| run: pnpm type-check | |
| # --- Frontend: Test --- | |
| frontend-test: | |
| name: Test (${{ matrix.app }}) | |
| needs: changes | |
| if: needs.changes.outputs.console == 'true' || needs.changes.outputs.management == 'true' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| app: [console, management] | |
| steps: | |
| - name: Check if app changed | |
| id: should-run | |
| if: (matrix.app == 'console' && needs.changes.outputs.console == 'true') || (matrix.app == 'management' && needs.changes.outputs.management == 'true') | |
| run: echo "run=true" >> $GITHUB_OUTPUT | |
| - name: Checkout repository | |
| if: steps.should-run.outputs.run == 'true' | |
| uses: actions/checkout@v6 | |
| - name: Enable pnpm via corepack | |
| if: steps.should-run.outputs.run == 'true' | |
| run: corepack enable && corepack prepare pnpm@${{ env.PNPM_VERSION }} --activate | |
| - name: Setup Node.js | |
| if: steps.should-run.outputs.run == 'true' | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'pnpm' | |
| cache-dependency-path: ${{ matrix.app }}/pnpm-lock.yaml | |
| - name: Install dependencies | |
| if: steps.should-run.outputs.run == 'true' | |
| working-directory: ${{ matrix.app }} | |
| env: | |
| CI: "false" | |
| GITHUB_ACTIONS: "false" | |
| run: pnpm install | |
| - name: Run tests | |
| if: steps.should-run.outputs.run == 'true' | |
| working-directory: ${{ matrix.app }} | |
| run: pnpm test | |
| - name: Audit production dependencies | |
| if: steps.should-run.outputs.run == 'true' | |
| working-directory: ${{ matrix.app }} | |
| run: pnpm audit --prod --audit-level high | |
| shared-auth-test: | |
| name: Test shared auth core | |
| needs: changes | |
| if: needs.changes.outputs.console == 'true' || needs.changes.outputs.management == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Enable pnpm via corepack | |
| run: corepack enable && corepack prepare pnpm@${{ env.PNPM_VERSION }} --activate | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'pnpm' | |
| cache-dependency-path: shared/auth-core/pnpm-lock.yaml | |
| - name: Install dependencies | |
| working-directory: shared/auth-core | |
| run: pnpm install --frozen-lockfile | |
| - name: Test and type-check | |
| working-directory: shared/auth-core | |
| run: pnpm test && pnpm type-check | |
| # --- Frontend: i18n Check (management only) --- | |
| frontend-i18n-check: | |
| name: i18n Check (management) | |
| needs: changes | |
| if: needs.changes.outputs.management == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Enable pnpm via corepack | |
| run: corepack enable && corepack prepare pnpm@${{ env.PNPM_VERSION }} --activate | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'pnpm' | |
| cache-dependency-path: management/pnpm-lock.yaml | |
| - name: Install dependencies | |
| working-directory: management | |
| env: | |
| CI: "false" | |
| GITHUB_ACTIONS: "false" | |
| run: pnpm install | |
| - name: Run i18n completeness check | |
| working-directory: management | |
| run: pnpm check:i18n --json | |
| - name: Run i18n tests | |
| working-directory: management | |
| run: pnpm vitest run -- src/i18n/__tests__/ | |
| # --- Docker Build Verification --- | |
| docker-verify: | |
| name: Docker Build (${{ matrix.service.name }}) | |
| needs: changes | |
| if: needs.changes.outputs.docker == 'true' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| service: | |
| - name: backend | |
| dockerfile: ./backend-spring/Dockerfile | |
| - name: console | |
| dockerfile: ./console/Dockerfile | |
| - name: management | |
| dockerfile: ./management/Dockerfile | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Build Docker image (no push) | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: . | |
| file: ${{ matrix.service.dockerfile }} | |
| push: false | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max |