fix: website for page deployment (#854) #2412
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 Release | |
| on: | |
| push: | |
| branches: ['main'] | |
| pull_request: | |
| types: [opened, synchronize, reopened] | |
| workflow_dispatch: | |
| inputs: | |
| release: | |
| description: 'Create a new versioned release (Docker + npm)' | |
| required: false | |
| type: boolean | |
| default: false | |
| confirm_major: | |
| description: 'Type CONFIRM to allow major version bump (breaking changes)' | |
| required: false | |
| type: string | |
| default: '' | |
| 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@v6 | |
| name: Install pnpm | |
| with: | |
| run_install: false | |
| - name: Install Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 24 | |
| 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@v6 | |
| name: Install pnpm | |
| with: | |
| run_install: false | |
| - name: Install Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 24 | |
| cache: 'pnpm' | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Build SDK Core | |
| run: pnpm --filter @eudiplo/sdk-core build | |
| build-backend: | |
| name: Build Backend | |
| permissions: | |
| contents: read | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: pnpm/action-setup@v6 | |
| name: Install pnpm | |
| with: | |
| run_install: false | |
| - name: Install Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 24 | |
| 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@v6 | |
| name: Install pnpm | |
| with: | |
| run_install: false | |
| - name: Install Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 24 | |
| 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@v6 | |
| name: Install pnpm | |
| with: | |
| run_install: false | |
| - name: Install Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 24 | |
| 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@v6 | |
| name: Install pnpm | |
| with: | |
| run_install: false | |
| - name: Install Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 24 | |
| cache: 'pnpm' | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| 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: read | |
| pages: write | |
| id-token: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v7 | |
| - uses: pnpm/action-setup@v6 | |
| name: Install pnpm | |
| with: | |
| run_install: false | |
| - name: Install Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 24 | |
| cache: 'pnpm' | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: '3.12' | |
| - name: Install Python dependencies | |
| run: pip install -r requirements.txt | |
| - name: Configure GitHub Pages | |
| uses: actions/configure-pages@v5 | |
| - 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 | |
| mike set-default --branch gh-pages --deploy-prefix docs main | |
| git worktree add gh-pages-out gh-pages | |
| - name: Assemble Pages artifact | |
| run: | | |
| mkdir -p pages/docs | |
| cp -R website/. pages/ | |
| cp -R gh-pages-out/docs/. pages/docs/ | |
| touch pages/.nojekyll | |
| - name: Cleanup temporary worktree | |
| if: always() | |
| run: git worktree remove gh-pages-out --force || true | |
| - name: Upload Pages artifact | |
| uses: actions/upload-pages-artifact@v4 | |
| with: | |
| path: pages | |
| - name: Deploy to GitHub Pages | |
| id: deployment | |
| uses: actions/deploy-pages@v4 | |
| 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@v6 | |
| name: Install pnpm | |
| with: | |
| run_install: false | |
| - name: Install Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 24 | |
| 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@v6 | |
| name: Install pnpm | |
| with: | |
| run_install: false | |
| - name: Install Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 24 | |
| 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 | |
| # ============================================================================= | |
| # 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 | |
| - 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 | |
| - 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 | |
| - 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 | |
| # ============================================================================= | |
| # NPM Pre-release (Main Branch Only) | |
| # Publishes @eudiplo/sdk-core with a "main" tag as a pre-release version. | |
| # ============================================================================= | |
| npm-prerelease: | |
| name: Publish SDK Pre-release to npm | |
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| needs: [build-sdk-core] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| id-token: write | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - uses: pnpm/action-setup@v6 | |
| name: Install pnpm | |
| with: | |
| run_install: false | |
| - name: Install Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 24 | |
| cache: 'pnpm' | |
| registry-url: 'https://registry.npmjs.org' | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Build SDK | |
| run: pnpm --filter @eudiplo/sdk-core build | |
| - name: Set pre-release version | |
| working-directory: packages/eudiplo-sdk-core | |
| run: | | |
| # Base prerelease on the latest repository semver tag (e.g., v5.1.0 -> 5.1.0). | |
| # Fallback to package.json if tags are not available. | |
| LATEST_TAG=$(git tag --sort=-v:refname | grep -E '^v?[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1 || true) | |
| if [ -n "$LATEST_TAG" ]; then | |
| CURRENT_VERSION="${LATEST_TAG#v}" | |
| else | |
| CURRENT_VERSION=$(node -p "require('./package.json').version") | |
| fi | |
| SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7) | |
| PRE_VERSION="${CURRENT_VERSION}-main.${SHORT_SHA}" | |
| echo "Publishing version: ${PRE_VERSION}" | |
| npm version "${PRE_VERSION}" --no-git-tag-version | |
| - name: Publish to npm with main tag | |
| working-directory: packages/eudiplo-sdk-core | |
| run: npm publish --tag main --access public --provenance | |
| # ============================================================================= | |
| # Versioned Release (Manual Trigger Only) | |
| # Creates a new semantic version, publishes Docker images with version tags, | |
| # publishes npm package, and updates documentation. | |
| # ============================================================================= | |
| release: | |
| name: Create Versioned Release | |
| if: github.event_name == 'workflow_dispatch' && inputs.release == true | |
| needs: [test-e2e, docker-backend, docker-client] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| packages: write | |
| pull-requests: write | |
| actions: read | |
| pages: write | |
| id-token: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 # Fetch full history for mike versioning | |
| - uses: pnpm/action-setup@v6 | |
| name: Install pnpm | |
| with: | |
| run_install: false | |
| - name: Install Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 24 | |
| cache: 'pnpm' | |
| registry-url: 'https://registry.npmjs.org' | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Build SDK | |
| run: pnpm --filter @eudiplo/sdk-core build | |
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Check for major version bump | |
| id: version_check | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| echo "Running semantic-release dry-run to detect version bump..." | |
| DRY_OUTPUT=$(npx semantic-release --dry-run 2>&1) || true | |
| echo "$DRY_OUTPUT" | |
| # Extract the next version from dry-run output | |
| NEXT_VERSION=$(echo "$DRY_OUTPUT" | grep -oP "The next release version is \K[0-9]+\.[0-9]+\.[0-9]+" || echo "") | |
| CURRENT_VERSION=$(git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo "0.0.0") | |
| echo "Current version: $CURRENT_VERSION" | |
| echo "Next version: $NEXT_VERSION" | |
| if [ -z "$NEXT_VERSION" ]; then | |
| echo "No release will be created" | |
| echo "is_major=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| # Extract major versions | |
| CURRENT_MAJOR=$(echo "$CURRENT_VERSION" | cut -d. -f1) | |
| NEXT_MAJOR=$(echo "$NEXT_VERSION" | cut -d. -f1) | |
| if [ "$NEXT_MAJOR" -gt "$CURRENT_MAJOR" ]; then | |
| echo "⚠️ MAJOR VERSION BUMP DETECTED: $CURRENT_VERSION → $NEXT_VERSION" | |
| echo "is_major=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "Minor/patch release: $CURRENT_VERSION → $NEXT_VERSION" | |
| echo "is_major=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Abort if major release not confirmed | |
| if: steps.version_check.outputs.is_major == 'true' && github.event.inputs.confirm_major != 'CONFIRM' | |
| run: | | |
| echo "❌ MAJOR VERSION RELEASE BLOCKED" | |
| echo "" | |
| echo "A major version bump was detected but not confirmed." | |
| echo "To release a major version, trigger this workflow manually with:" | |
| echo " confirm_major: CONFIRM" | |
| echo "" | |
| echo "This safeguard prevents accidental breaking changes from being released." | |
| exit 1 | |
| - name: Run semantic-release | |
| id: semantic_release | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| NPM_CONFIG_PROVENANCE: true | |
| DOCKER_REGISTRY_USER: ${{ github.actor }} | |
| DOCKER_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| echo "Running semantic-release..." | |
| npx semantic-release | |
| # Check if a new release was created by checking if a new tag was created | |
| echo "Checking for new tags..." | |
| git fetch --tags | |
| NEW_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") | |
| echo "Latest tag found: $NEW_TAG" | |
| if [ -n "$NEW_TAG" ]; then | |
| echo "New release detected: $NEW_TAG" | |
| echo "new_release=true" >> $GITHUB_OUTPUT | |
| echo "version=$NEW_TAG" >> $GITHUB_OUTPUT | |
| else | |
| echo "No new release detected" | |
| echo "new_release=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Debug release detection | |
| run: | | |
| echo "New release: ${{ steps.semantic_release.outputs.new_release }}" | |
| echo "Version: ${{ steps.semantic_release.outputs.version }}" | |
| - name: Set up Python for docs | |
| if: steps.semantic_release.outputs.new_release == 'true' | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: '3.11' | |
| - name: Install Python dependencies for docs | |
| if: steps.semantic_release.outputs.new_release == 'true' | |
| run: pip install -r requirements.txt | |
| - name: Copy env file for docs | |
| if: steps.semantic_release.outputs.new_release == 'true' | |
| run: cp .env.example .env | |
| - name: Configure git for mike | |
| if: steps.semantic_release.outputs.new_release == 'true' | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| - name: Build and deploy release docs | |
| if: steps.semantic_release.outputs.new_release == 'true' | |
| 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: | | |
| FULL_VERSION="${{ steps.semantic_release.outputs.version }}" | |
| # Extract major.minor (e.g., 3.2.0 → 3.2) so patch releases update the same version slot | |
| MINOR_VERSION="${FULL_VERSION%.*}" | |
| pnpm run doc:generate | |
| pnpm run compodoc | |
| # Deploy under major.minor (e.g., 3.2), patches overwrite the same entry | |
| mike deploy --push --update-aliases "$MINOR_VERSION" | |
| # Deploy to latest and set as default | |
| mike deploy --push --update-aliases latest | |
| mike set-default --push latest |