Refactor CLI into modular Commander commands (#946) #2717
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 / Docker | |
| on: | |
| push: | |
| branches: ['main'] | |
| pull_request: | |
| types: [opened, synchronize, reopened] | |
| merge_group: | |
| types: [checks_requested] | |
| workflow_dispatch: | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| lint: | |
| permissions: | |
| contents: read | |
| name: Lint Codebase | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: pnpm/action-setup@v5 | |
| name: Install pnpm | |
| with: | |
| run_install: false | |
| - name: Install Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 26 | |
| cache: 'pnpm' | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Run linter for code | |
| run: pnpm run lint | |
| - name: Run linter for documentation | |
| run: pnpm run doc:lint | |
| - name: Run license check (backend, production) | |
| working-directory: apps/backend | |
| run: pnpm dlx license-checker --production --onlyAllow "MIT;Apache-2.0;BSD-2-Clause;BSD-3-Clause;0BSD;ISC;OFL-1.1" | |
| - name: Run license check (client, production) | |
| working-directory: apps/client | |
| run: pnpm dlx license-checker --production --onlyAllow "MIT;Apache-2.0;BSD-2-Clause;BSD-3-Clause;0BSD;ISC;OFL-1.1" | |
| - name: Run knip | |
| run: pnpm knip --no-progress | |
| build-sdk-core: | |
| name: Build SDK Core | |
| permissions: | |
| contents: read | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: pnpm/action-setup@v5 | |
| name: Install pnpm | |
| with: | |
| run_install: false | |
| - name: Install Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 26 | |
| cache: 'pnpm' | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Build SDK Core | |
| run: pnpm --filter @eudiplo/sdk-core build | |
| build-cli: | |
| name: Build CLI | |
| permissions: | |
| contents: read | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: pnpm/action-setup@v5 | |
| name: Install pnpm | |
| with: | |
| run_install: false | |
| - name: Install Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 26 | |
| cache: 'pnpm' | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Build CLI | |
| run: pnpm --filter @eudiplo/cli build | |
| - name: Run CLI tests | |
| run: pnpm --filter @eudiplo/cli test | |
| - name: Build CLI SEA | |
| run: pnpm --filter @eudiplo/cli build:sea | |
| - name: Smoke test standalone CLI | |
| run: ./apps/cli/dist-sea/eudiplo --help | |
| build-backend: | |
| name: Build Backend | |
| permissions: | |
| contents: read | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: pnpm/action-setup@v5 | |
| name: Install pnpm | |
| with: | |
| run_install: false | |
| - name: Install Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 26 | |
| cache: 'pnpm' | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Build backend | |
| run: pnpm --filter @eudiplo/backend build | |
| build-client: | |
| name: Build Client | |
| permissions: | |
| contents: read | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: pnpm/action-setup@v5 | |
| name: Install pnpm | |
| with: | |
| run_install: false | |
| - name: Install Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 26 | |
| cache: 'pnpm' | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Build sdk core | |
| run: pnpm --filter @eudiplo/sdk-core build | |
| - name: Build client | |
| run: pnpm --filter @eudiplo/client build | |
| build-webhook: | |
| name: Build Webhook | |
| permissions: | |
| contents: read | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: pnpm/action-setup@v5 | |
| name: Install pnpm | |
| with: | |
| run_install: false | |
| - name: Install Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 26 | |
| cache: 'pnpm' | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Build webhook | |
| run: pnpm --filter test-rp build | |
| build-doc: | |
| name: Build Documentation | |
| permissions: | |
| contents: read | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: pnpm/action-setup@v5 | |
| name: Install pnpm | |
| with: | |
| run_install: false | |
| - name: Install Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 26 | |
| cache: 'pnpm' | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Set up Python | |
| uses: actions/setup-python@v7 | |
| with: | |
| python-version: '3.11' | |
| - name: Install Python dependencies | |
| run: pip install -r requirements.txt | |
| - name: Build documentation | |
| env: | |
| MASTER_SECRET: ci-docs-build-secret-minimum-32-chars | |
| AUTH_CLIENT_ID: ci-docs-client | |
| AUTH_CLIENT_SECRET: ci-docs-secret | |
| ENCRYPTION_KEY: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef | |
| run: pnpm run doc:generate && pnpm run compodoc && mkdocs build --strict | |
| - name: Upload built documentation | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: generated-docs | |
| path: site/ | |
| deploy-docs: | |
| name: Deploy Website & Documentation | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| needs: [build-doc] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v7 | |
| - uses: pnpm/action-setup@v5 | |
| name: Install pnpm | |
| with: | |
| run_install: false | |
| - name: Install Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 26 | |
| cache: 'pnpm' | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Set up Python | |
| uses: actions/setup-python@v7 | |
| with: | |
| python-version: '3.12' | |
| - name: Install Python dependencies | |
| run: pip install -r requirements.txt | |
| - name: Build generated docs content | |
| env: | |
| PUBLIC_URL: https://example.com | |
| ENABLE_SWAGGER: true | |
| MASTER_SECRET: ci-docs-build-secret-minimum-32-chars | |
| AUTH_CLIENT_ID: ci-docs-client | |
| AUTH_CLIENT_SECRET: ci-docs-secret | |
| ENCRYPTION_KEY: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef | |
| run: pnpm run doc:generate && pnpm run compodoc | |
| - name: Build versioned docs with mike under /docs | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| # Reuse existing gh-pages history when available so mike can maintain version metadata. | |
| git fetch origin gh-pages:gh-pages || true | |
| mike deploy --branch gh-pages --deploy-prefix docs --alias-type copy --update-aliases main latest-dev | |
| git worktree add gh-pages-out gh-pages | |
| git -C gh-pages-out pull --rebase origin gh-pages | |
| # Keep gh-pages root in sync with website/ from main while preserving docs/ generated by mike. | |
| rsync -a --delete \ | |
| --exclude='/.git' \ | |
| --exclude='/docs/' \ | |
| website/ gh-pages-out/ | |
| test -f gh-pages-out/.git | |
| test "$(git -C gh-pages-out branch --show-current)" = "gh-pages" | |
| git -C gh-pages-out add -A | |
| if ! git -C gh-pages-out diff --cached --quiet; then | |
| git -C gh-pages-out commit -m "chore(docs): sync website root from main (${GITHUB_SHA::7})" | |
| else | |
| echo "No gh-pages root changes detected" | |
| fi | |
| git -C gh-pages-out push origin gh-pages | |
| - name: Cleanup temporary worktree | |
| if: always() | |
| run: git worktree remove gh-pages-out --force || true | |
| test-e2e: | |
| permissions: | |
| contents: read | |
| name: E2E Tests (non-OIDF) | |
| runs-on: ubuntu-latest | |
| needs: [lint] | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Add entry to /etc/hosts | |
| run: echo "127.0.0.1 host.testcontainers.internal" | sudo tee -a /etc/hosts | |
| - uses: pnpm/action-setup@v5 | |
| name: Install pnpm | |
| with: | |
| run_install: false | |
| - name: Install Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 26 | |
| cache: 'pnpm' | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: setup test webhooks | |
| run: | | |
| nohup pnpm --filter test-rp dev & | |
| - name: Run E2E tests | |
| run: pnpm run --filter @eudiplo/backend test:e2e | |
| env: | |
| TESTCONTAINERS_HOST_OVERRIDE: host.testcontainers.internal | |
| test-e2e-oidf: | |
| permissions: | |
| contents: read | |
| name: E2E Tests (OIDF) | |
| runs-on: ubuntu-latest | |
| needs: [lint] | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Add entry to /etc/hosts | |
| run: echo "127.0.0.1 host.testcontainers.internal" | sudo tee -a /etc/hosts | |
| - uses: pnpm/action-setup@v5 | |
| name: Install pnpm | |
| with: | |
| run_install: false | |
| - name: Install Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 26 | |
| cache: 'pnpm' | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: setup test webhooks | |
| run: | | |
| nohup pnpm --filter test-rp dev & | |
| - name: Run OIDF E2E tests | |
| run: pnpm run --filter @eudiplo/backend test:oidf | |
| env: | |
| TESTCONTAINERS_HOST_OVERRIDE: host.testcontainers.internal | |
| - name: Upload OIDF test results | |
| if: ${{ !cancelled() }} | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: oidf-test-results | |
| path: tmp/oidf-logs | |
| if-no-files-found: ignore | |
| test-coverage-report: | |
| permissions: | |
| contents: read | |
| name: Test Coverage Report | |
| runs-on: ubuntu-latest | |
| needs: [lint] | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ github.event.pull_request.head.sha || github.sha }} | |
| - name: Add entry to /etc/hosts | |
| run: echo "127.0.0.1 host.testcontainers.internal" | sudo tee -a /etc/hosts | |
| - uses: pnpm/action-setup@v5 | |
| name: Install pnpm | |
| with: | |
| run_install: false | |
| - name: Install Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 26 | |
| cache: 'pnpm' | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: setup test webhooks | |
| run: | | |
| nohup pnpm --filter test-rp dev & | |
| - name: Run backend unit tests with coverage | |
| run: pnpm --filter @eudiplo/backend exec vitest run --coverage --config ./vitest.config.ts | |
| - name: Run backend E2E tests with coverage | |
| run: pnpm run --filter @eudiplo/backend test:e2e | |
| env: | |
| TESTCONTAINERS_HOST_OVERRIDE: host.testcontainers.internal | |
| - name: Upload Cobertura coverage artifacts | |
| if: ${{ !cancelled() }} | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: backend-cobertura-coverage | |
| path: | | |
| apps/backend/coverage/unit/cobertura-coverage.xml | |
| apps/backend/coverage/e2e/cobertura-coverage.xml | |
| if-no-files-found: error | |
| upload-code-coverage: | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| code-quality: write | |
| continue-on-error: true | |
| name: Upload Coverage to GitHub Code Quality | |
| runs-on: ubuntu-latest | |
| needs: [test-coverage-report] | |
| if: >- | |
| needs.test-coverage-report.result == 'success' && | |
| ( | |
| github.event_name != 'pull_request' || | |
| github.event.pull_request.head.repo.full_name == github.repository | |
| ) | |
| steps: | |
| - name: Download Cobertura coverage artifact | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: backend-cobertura-coverage | |
| path: apps/backend/coverage | |
| - uses: actions/upload-code-coverage@v1 | |
| with: | |
| file: apps/backend/coverage/unit/cobertura-coverage.xml | |
| language: TypeScript | |
| label: backend-unit | |
| fail-on-error: false | |
| - uses: actions/upload-code-coverage@v1 | |
| with: | |
| file: apps/backend/coverage/e2e/cobertura-coverage.xml | |
| language: TypeScript | |
| label: backend-e2e | |
| fail-on-error: false | |
| # ============================================================================= | |
| # Docker Backend: Build, Test & Push (multi-platform with native runners) | |
| # Uses native ARM runners instead of QEMU for ~4x faster arm64 builds. | |
| # Skip arm64 on PRs to save time - only build amd64 for validation. | |
| # ============================================================================= | |
| docker-backend: | |
| name: Build & Test Backend Docker (${{ matrix.arch }}) | |
| needs: [test-e2e, test-e2e-oidf] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # Dynamic matrix: include arm64 only on main branch push | |
| include: '${{ fromJSON(github.event_name == ''push'' && github.ref == ''refs/heads/main'' && ''[{"platform": "linux/amd64", "runner": "ubuntu-latest", "arch": "amd64"}, {"platform": "linux/arm64", "runner": "ubuntu-24.04-arm", "arch": "arm64"}]'' || ''[{"platform": "linux/amd64", "runner": "ubuntu-latest", "arch": "amd64"}]'') }}' | |
| runs-on: ${{ matrix.runner }} | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Docker meta | |
| id: meta | |
| uses: docker/metadata-action@v6 | |
| with: | |
| images: ghcr.io/openwallet-foundation/eudiplo | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| # --- Health test (amd64 only) --- | |
| - name: Build Backend Docker image (for testing) | |
| if: matrix.arch == 'amd64' | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: . | |
| target: eudiplo | |
| load: true | |
| tags: ghcr.io/openwallet-foundation/eudiplo:test | |
| build-args: VERSION=main | |
| cache-from: type=gha,scope=backend-${{ matrix.arch }} | |
| cache-to: type=gha,scope=backend-${{ matrix.arch }},mode=max | |
| - name: Run container (detached) | |
| if: matrix.arch == 'amd64' | |
| run: | | |
| docker run -d \ | |
| --name eudiplo-test \ | |
| -p 8080:8080 \ | |
| -e MASTER_SECRET=ci-docker-test-secret-min-32-chars \ | |
| -e AUTH_CLIENT_ID=ci-docker-client \ | |
| -e AUTH_CLIENT_SECRET=ci-docker-secret \ | |
| -e ENCRYPTION_KEY=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef \ | |
| ghcr.io/openwallet-foundation/eudiplo:test | |
| - name: Wait for healthy | |
| if: matrix.arch == 'amd64' | |
| run: | | |
| set -e | |
| # Check if container is running | |
| if ! docker ps -q -f name=eudiplo-test | grep -q .; then | |
| echo "Container failed to start or exited immediately ❌" | |
| echo "" | |
| echo "=== Container Status ===" | |
| docker ps -a -f name=eudiplo-test | |
| echo "" | |
| echo "=== Container Logs ===" | |
| docker logs eudiplo-test 2>&1 || echo "No logs available" | |
| exit 1 | |
| fi | |
| # wait up to ~90s (45 * 2s) for HEALTHCHECK to pass | |
| for i in $(seq 1 45); do | |
| # Check if container is still running | |
| if ! docker ps -q -f name=eudiplo-test | grep -q .; then | |
| echo "Container stopped unexpectedly ❌" | |
| echo "" | |
| echo "=== Container Status ===" | |
| docker ps -a -f name=eudiplo-test | |
| echo "" | |
| echo "=== Container Logs ===" | |
| docker logs eudiplo-test 2>&1 || true | |
| exit 1 | |
| fi | |
| STATUS=$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' eudiplo-test) | |
| if [ "$STATUS" = "healthy" ]; then | |
| echo "Container is healthy ✅" | |
| exit 0 | |
| fi | |
| if [ "$STATUS" = "unhealthy" ]; then | |
| echo "Container reported unhealthy ❌" | |
| echo "" | |
| echo "=== Health Check Log ===" | |
| docker inspect --format='{{range .State.Health.Log}}{{.Output}}{{end}}' eudiplo-test || true | |
| echo "" | |
| echo "=== Container Logs ===" | |
| docker logs eudiplo-test || true | |
| exit 1 | |
| fi | |
| sleep 2 | |
| done | |
| echo "Timed out waiting for healthy ❌" | |
| docker ps | |
| echo "" | |
| echo "=== Health Check Log ===" | |
| docker inspect --format='{{range .State.Health.Log}}{{.Output}}{{end}}' eudiplo-test || true | |
| echo "" | |
| echo "=== Container Logs ===" | |
| docker logs eudiplo-test || true | |
| exit 1 | |
| - name: Stop test container | |
| if: ${{ always() && matrix.arch == 'amd64' }} | |
| run: docker rm -f eudiplo-test || true | |
| # --- Build and push by digest (main push only) --- | |
| - name: Build and push by digest | |
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| id: push | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: . | |
| target: eudiplo | |
| platforms: ${{ matrix.platform }} | |
| build-args: VERSION=main | |
| labels: ${{ steps.meta.outputs.labels }} | |
| outputs: type=image,name=ghcr.io/openwallet-foundation/eudiplo,push-by-digest=true,name-canonical=true,push=true | |
| cache-from: type=gha,scope=backend-${{ matrix.arch }} | |
| cache-to: type=gha,scope=backend-${{ matrix.arch }},mode=max | |
| - name: Export digest | |
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| run: | | |
| mkdir -p ${{ runner.temp }}/digests | |
| digest="${{ steps.push.outputs.digest }}" | |
| touch "${{ runner.temp }}/digests/${digest#sha256:}" | |
| - name: Upload digest | |
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: backend-digest-${{ matrix.arch }} | |
| path: ${{ runner.temp }}/digests/* | |
| if-no-files-found: error | |
| retention-days: 1 | |
| docker-backend-manifest: | |
| name: Create Backend Docker Manifest | |
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| needs: [docker-backend] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - name: Download digests | |
| uses: actions/download-artifact@v8 | |
| with: | |
| pattern: backend-digest-* | |
| merge-multiple: true | |
| path: ${{ runner.temp }}/digests | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Docker meta | |
| id: meta | |
| uses: docker/metadata-action@v6 | |
| with: | |
| images: ghcr.io/openwallet-foundation/eudiplo | |
| tags: | | |
| type=raw,value=main | |
| type=raw,value=sha-${{ github.sha }} | |
| - name: Create manifest list and push | |
| working-directory: ${{ runner.temp }}/digests | |
| run: | | |
| docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ | |
| $(printf 'ghcr.io/openwallet-foundation/eudiplo@sha256:%s ' *) | |
| - name: Inspect image | |
| run: docker buildx imagetools inspect ghcr.io/openwallet-foundation/eudiplo:main | |
| # ============================================================================= | |
| # Docker Backend Demo: Build & Push (multi-platform with native runners) | |
| # Publishes ghcr.io/openwallet-foundation/eudiplo-demo:main on main branch. | |
| # ============================================================================= | |
| docker-backend-demo: | |
| name: Build & Push Backend Demo Docker (${{ matrix.arch }}) | |
| needs: [test-e2e, test-e2e-oidf] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: '${{ fromJSON(github.event_name == ''push'' && github.ref == ''refs/heads/main'' && ''[{"platform": "linux/amd64", "runner": "ubuntu-latest", "arch": "amd64"}, {"platform": "linux/arm64", "runner": "ubuntu-24.04-arm", "arch": "arm64"}]'' || ''[{"platform": "linux/amd64", "runner": "ubuntu-latest", "arch": "amd64"}]'') }}' | |
| runs-on: ${{ matrix.runner }} | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Docker meta | |
| id: meta | |
| uses: docker/metadata-action@v6 | |
| with: | |
| images: ghcr.io/openwallet-foundation/eudiplo-demo | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| # --- Build to validate (PRs/workflow_dispatch only) --- | |
| - name: Build to validate | |
| if: ${{ !(github.ref == 'refs/heads/main' && github.event_name == 'push') }} | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: . | |
| target: eudiplo-demo | |
| platforms: ${{ matrix.platform }} | |
| build-args: VERSION=main | |
| cache-from: type=gha,scope=backend-demo-${{ matrix.arch }} | |
| cache-to: type=gha,scope=backend-demo-${{ matrix.arch }},mode=max | |
| # --- Build and push by digest (main push only) --- | |
| - name: Build and push by digest | |
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| id: push | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: . | |
| target: eudiplo-demo | |
| platforms: ${{ matrix.platform }} | |
| build-args: VERSION=main | |
| labels: ${{ steps.meta.outputs.labels }} | |
| outputs: type=image,name=ghcr.io/openwallet-foundation/eudiplo-demo,push-by-digest=true,name-canonical=true,push=true | |
| cache-from: type=gha,scope=backend-demo-${{ matrix.arch }} | |
| cache-to: type=gha,scope=backend-demo-${{ matrix.arch }},mode=max | |
| - name: Export digest | |
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| run: | | |
| mkdir -p ${{ runner.temp }}/digests | |
| digest="${{ steps.push.outputs.digest }}" | |
| touch "${{ runner.temp }}/digests/${digest#sha256:}" | |
| - name: Upload digest | |
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: backend-demo-digest-${{ matrix.arch }} | |
| path: ${{ runner.temp }}/digests/* | |
| if-no-files-found: error | |
| retention-days: 1 | |
| docker-backend-demo-manifest: | |
| name: Create Backend Demo Docker Manifest | |
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| needs: [docker-backend-demo] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - name: Download digests | |
| uses: actions/download-artifact@v8 | |
| with: | |
| pattern: backend-demo-digest-* | |
| merge-multiple: true | |
| path: ${{ runner.temp }}/digests | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Docker meta | |
| id: meta | |
| uses: docker/metadata-action@v6 | |
| with: | |
| images: ghcr.io/openwallet-foundation/eudiplo-demo | |
| tags: | | |
| type=raw,value=main | |
| type=raw,value=sha-${{ github.sha }} | |
| - name: Create manifest list and push | |
| working-directory: ${{ runner.temp }}/digests | |
| run: | | |
| docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ | |
| $(printf 'ghcr.io/openwallet-foundation/eudiplo-demo@sha256:%s ' *) | |
| - name: Inspect image | |
| run: docker buildx imagetools inspect ghcr.io/openwallet-foundation/eudiplo-demo:main | |
| # ============================================================================= | |
| # Docker Client: Build & Push (multi-platform with native runners) | |
| # Skip arm64 on PRs to save time - only build amd64 for validation. | |
| # ============================================================================= | |
| docker-client: | |
| name: Build & Push Client Docker (${{ matrix.arch }}) | |
| needs: [lint] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # Dynamic matrix: include arm64 only on main branch push | |
| include: '${{ fromJSON(github.event_name == ''push'' && github.ref == ''refs/heads/main'' && ''[{"platform": "linux/amd64", "runner": "ubuntu-latest", "arch": "amd64"}, {"platform": "linux/arm64", "runner": "ubuntu-24.04-arm", "arch": "arm64"}]'' || ''[{"platform": "linux/amd64", "runner": "ubuntu-latest", "arch": "amd64"}]'') }}' | |
| runs-on: ${{ matrix.runner }} | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Docker meta | |
| id: meta | |
| uses: docker/metadata-action@v6 | |
| with: | |
| images: ghcr.io/openwallet-foundation/eudiplo-client | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| # --- Build and push by digest (main push only) --- | |
| - name: Build and push by digest | |
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| id: push | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: . | |
| target: client | |
| platforms: ${{ matrix.platform }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| outputs: type=image,name=ghcr.io/openwallet-foundation/eudiplo-client,push-by-digest=true,name-canonical=true,push=true | |
| cache-from: type=gha,scope=client-${{ matrix.arch }} | |
| cache-to: type=gha,scope=client-${{ matrix.arch }},mode=max | |
| # --- Build to validate (PRs/workflow_dispatch only) --- | |
| - name: Build to validate | |
| if: ${{ !(github.ref == 'refs/heads/main' && github.event_name == 'push') }} | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: . | |
| target: client | |
| platforms: ${{ matrix.platform }} | |
| cache-from: type=gha,scope=client-${{ matrix.arch }} | |
| cache-to: type=gha,scope=client-${{ matrix.arch }},mode=max | |
| - name: Export digest | |
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| run: | | |
| mkdir -p ${{ runner.temp }}/digests | |
| digest="${{ steps.push.outputs.digest }}" | |
| touch "${{ runner.temp }}/digests/${digest#sha256:}" | |
| - name: Upload digest | |
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: client-digest-${{ matrix.arch }} | |
| path: ${{ runner.temp }}/digests/* | |
| if-no-files-found: error | |
| retention-days: 1 | |
| docker-client-manifest: | |
| name: Create Client Docker Manifest | |
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| needs: [docker-client] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - name: Download digests | |
| uses: actions/download-artifact@v8 | |
| with: | |
| pattern: client-digest-* | |
| merge-multiple: true | |
| path: ${{ runner.temp }}/digests | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Docker meta | |
| id: meta | |
| uses: docker/metadata-action@v6 | |
| with: | |
| images: ghcr.io/openwallet-foundation/eudiplo-client | |
| tags: | | |
| type=raw,value=main | |
| type=raw,value=sha-${{ github.sha }} | |
| - name: Create manifest list and push | |
| working-directory: ${{ runner.temp }}/digests | |
| run: | | |
| docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ | |
| $(printf 'ghcr.io/openwallet-foundation/eudiplo-client@sha256:%s ' *) | |
| - name: Inspect image | |
| run: docker buildx imagetools inspect ghcr.io/openwallet-foundation/eudiplo-client:main |