Repository navigation
feat(dashboard): localize the tenant app with react-i18next (4/4) #486
Workflow file for this run
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: Frontend CI | |
| # Frontend pipeline for the two React apps (clients/admin, clients/dashboard). | |
| # Only does real work when clients/** changed; a backend-only PR still triggers | |
| # the workflow but the heavy jobs skip and the always-running "Frontend CI" gate | |
| # reports green so required status checks resolve. Playwright suites are | |
| # route-mocked (no backend needed) — the configs boot a Vite dev server. | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| permissions: | |
| contents: read | |
| jobs: | |
| changes: | |
| name: Detect changes | |
| runs-on: ubuntu-latest | |
| outputs: | |
| frontend: ${{ github.event_name != 'pull_request' || steps.filter.outputs.frontend == 'true' }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: dorny/paths-filter@v3 | |
| id: filter | |
| with: | |
| filters: | | |
| frontend: | |
| - 'clients/**' | |
| - '.github/workflows/frontend.yml' | |
| lint-build: | |
| name: Lint & Build (${{ matrix.app }}) | |
| needs: changes | |
| if: needs.changes.outputs.frontend == 'true' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| app: [admin, dashboard] | |
| defaults: | |
| run: | |
| working-directory: clients/${{ matrix.app }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| cache-dependency-path: clients/${{ matrix.app }}/package-lock.json | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Lint | |
| run: npm run lint | |
| # `build` is `tsc -b && vite build` — this is the typecheck + bundle gate. | |
| - name: Build | |
| run: npm run build | |
| e2e: | |
| name: E2E (${{ matrix.app }}) | |
| needs: changes | |
| if: needs.changes.outputs.frontend == 'true' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| app: [admin, dashboard] | |
| defaults: | |
| run: | |
| working-directory: clients/${{ matrix.app }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| cache-dependency-path: clients/${{ matrix.app }}/package-lock.json | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Install Playwright browser | |
| run: npx playwright install --with-deps chromium | |
| # The Playwright config starts the Vite dev server itself and mocks all | |
| # API calls via page.route(), so no backend is required. | |
| - name: Run Playwright tests | |
| run: npm run test:e2e | |
| - name: Upload Playwright report | |
| uses: actions/upload-artifact@v7 | |
| if: failure() | |
| with: | |
| name: playwright-report-${{ matrix.app }} | |
| path: clients/${{ matrix.app }}/playwright-report | |
| retention-days: 7 | |
| # The nginx image renders /config.json at every start. Restarting the same container | |
| # has to bring it back: the entrypoint once deleted its own template after the first | |
| # render, so any restart crash-looped, and nothing else here builds or runs the image. | |
| container: | |
| name: Container (${{ matrix.app }}) | |
| needs: changes | |
| if: needs.changes.outputs.frontend == 'true' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| app: [admin, dashboard] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Build image | |
| run: docker build -t fsh-${{ matrix.app }}:ci clients/${{ matrix.app }} | |
| - name: Smoke test start and restart | |
| env: | |
| IMAGE: fsh-${{ matrix.app }}:ci | |
| run: | | |
| set -euo pipefail | |
| docker run -d --name smoke -p 8080:80 \ | |
| -e FSH_API_URL=http://api.example.test -e FSH_DEFAULT_TENANT=acme \ | |
| -e FSH_DASHBOARD_URL=http://app.example.test "$IMAGE" | |
| check() { | |
| rm -f config.json | |
| for _ in $(seq 1 20); do | |
| curl -fsS http://localhost:8080/config.json -o config.json && break | |
| sleep 1 | |
| done | |
| [ "$(docker inspect -f '{{.State.Running}} {{.RestartCount}}' smoke)" = "true 0" ] | |
| jq -e '.apiBase == "http://api.example.test" and .defaultTenant == "acme"' config.json | |
| } | |
| check | |
| if curl -fsS http://localhost:8080/config.json.template | grep -qF '${FSH_API_URL}'; then | |
| echo "::error::config.json.template is served from the web root" | |
| exit 1 | |
| fi | |
| docker restart smoke | |
| check | |
| - name: Container logs | |
| if: failure() | |
| run: docker logs smoke | |
| # Single required status check — see backend.yml for the rationale. | |
| frontend-ci: | |
| name: Frontend CI | |
| needs: [changes, lint-build, e2e, container] | |
| if: always() | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Fail if a required frontend job failed | |
| if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }} | |
| run: | | |
| echo "::error::A required frontend job failed or was cancelled." | |
| exit 1 | |
| - name: Success | |
| run: echo "Frontend CI passed." |