fix(wallet): fetch activeTranscoderCount and delegatorsCount from sub… #810
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
| # ============================================================================= | |
| # CI Pipeline — Path-Filtered Testing | |
| # ============================================================================= | |
| # Runs lint, typecheck, tests, and builds filtered by what actually changed. | |
| # Skips irrelevant jobs for faster PR feedback. | |
| # | |
| # Branch model: feature-branch-off-main (trunk-based). | |
| # All PRs target main. Vercel PR previews serve as staging. | |
| # ============================================================================= | |
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| concurrency: | |
| group: ci-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| NODE_VERSION: '20' | |
| NEXT_TELEMETRY_DISABLED: 1 | |
| NX_PACKAGE_MANAGER: npm | |
| jobs: | |
| # =========================================================================== | |
| # Path Filter — Detect what changed to skip irrelevant jobs | |
| # =========================================================================== | |
| paths-filter: | |
| name: Detect Changes | |
| runs-on: ubuntu-latest | |
| outputs: | |
| shell: ${{ steps.filter.outputs.shell }} | |
| packages: ${{ steps.filter.outputs.packages }} | |
| sdk: ${{ steps.filter.outputs.sdk }} | |
| backend: ${{ steps.filter.outputs.backend }} | |
| plugins: ${{ steps.filter.outputs.plugins }} | |
| plugins_list: ${{ steps.changed-plugins.outputs.plugins_list }} | |
| docs_only: ${{ steps.check-docs.outputs.docs_only }} | |
| build_relevant: ${{ steps.filter.outputs.build_relevant }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Run paths filter | |
| id: filter | |
| uses: dorny/paths-filter@v3 | |
| with: | |
| filters: | | |
| shell: | |
| - 'apps/web-next/**' | |
| packages: | |
| - 'packages/**' | |
| sdk: | |
| - 'packages/plugin-sdk/**' | |
| - 'packages/plugin-utils/**' | |
| - 'packages/plugin-build/**' | |
| backend: | |
| - 'services/**' | |
| plugins: | |
| - 'plugins/**' | |
| build_relevant: | |
| - 'apps/web-next/**' | |
| - 'packages/**' | |
| - 'plugins/**' | |
| - 'bin/**' | |
| - 'services/**' | |
| - 'next.config.*' | |
| - 'package.json' | |
| - 'package-lock.json' | |
| docs_only: | |
| - 'docs/**' | |
| - '*.md' | |
| - name: Check if docs-only change | |
| id: check-docs | |
| run: | | |
| # docs_only is true only when docs changed AND nothing else changed | |
| if [[ "${{ steps.filter.outputs.docs_only }}" == "true" \ | |
| && "${{ steps.filter.outputs.shell }}" == "false" \ | |
| && "${{ steps.filter.outputs.packages }}" == "false" \ | |
| && "${{ steps.filter.outputs.backend }}" == "false" \ | |
| && "${{ steps.filter.outputs.plugins }}" == "false" ]]; then | |
| echo "docs_only=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "docs_only=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Detect changed plugins | |
| id: changed-plugins | |
| run: | | |
| ALL_PLUGINS='["capacity-planner","community","marketplace","developer-api","plugin-publisher"]' | |
| if [[ "${{ steps.filter.outputs.plugins }}" == "true" ]]; then | |
| # Build a JSON array of plugins that actually changed | |
| CHANGED="[]" | |
| for plugin in capacity-planner community marketplace developer-api plugin-publisher; do | |
| if git diff --name-only ${{ github.event.pull_request.base.sha || 'HEAD~1' }} HEAD -- "plugins/${plugin}/" | grep -q .; then | |
| CHANGED=$(echo "$CHANGED" | jq -c ". + [\"${plugin}\"]") | |
| fi | |
| done | |
| echo "plugins_list=${CHANGED}" >> $GITHUB_OUTPUT | |
| else | |
| echo "plugins_list=[]" >> $GITHUB_OUTPUT | |
| fi | |
| # =========================================================================== | |
| # Lockfile Sync — Fail fast if package-lock.json is out of sync | |
| # =========================================================================== | |
| lockfile-sync: | |
| name: Lockfile Sync | |
| runs-on: ubuntu-latest | |
| needs: paths-filter | |
| if: needs.paths-filter.outputs.docs_only != 'true' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| - name: Verify package-lock.json is in sync | |
| run: npm ci --ignore-scripts --no-audit --no-fund | |
| # =========================================================================== | |
| # Dependency Audit — Fail on critical CVEs (e.g. next-mdx-remote CVE-2026-0969) | |
| # =========================================================================== | |
| audit: | |
| name: Audit | |
| runs-on: ubuntu-latest | |
| needs: [paths-filter, lockfile-sync] | |
| if: needs.paths-filter.outputs.docs_only != 'true' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Audit dependencies (fail on critical CVEs) | |
| run: npm audit --audit-level=critical | |
| # =========================================================================== | |
| # Lint & Type Check — Always runs (unless docs-only) | |
| # =========================================================================== | |
| lint-typecheck: | |
| name: Lint & TypeCheck | |
| runs-on: ubuntu-latest | |
| needs: [paths-filter, lockfile-sync] | |
| if: needs.paths-filter.outputs.docs_only != 'true' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Run ESLint | |
| run: npm run lint | |
| continue-on-error: true # Pre-existing .eslintrc migration issues in some packages | |
| - name: TypeScript check (web-next) | |
| run: cd apps/web-next && npm run typecheck | |
| continue-on-error: true # Pre-existing TS errors — tracked for separate cleanup | |
| # =========================================================================== | |
| # Shell Tests — Only when apps/web-next/** changed | |
| # =========================================================================== | |
| shell-tests: | |
| name: Shell Tests | |
| runs-on: ubuntu-latest | |
| needs: [paths-filter, lockfile-sync] | |
| if: needs.paths-filter.outputs.shell == 'true' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Run shell tests with coverage | |
| run: cd apps/web-next && npm run test:coverage | |
| - name: Upload coverage to Codecov | |
| uses: codecov/codecov-action@v5 | |
| with: | |
| files: apps/web-next/coverage/lcov.info | |
| flags: shell | |
| fail_ci_if_error: false | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| # =========================================================================== | |
| # SDK Tests — Only when packages/** changed | |
| # =========================================================================== | |
| sdk-tests: | |
| name: SDK Tests | |
| runs-on: ubuntu-latest | |
| needs: [paths-filter, lockfile-sync] | |
| if: needs.paths-filter.outputs.packages == 'true' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build SDK | |
| run: cd packages/plugin-sdk && npm run build | |
| - name: Run SDK tests | |
| run: cd packages/plugin-sdk && npm run test:coverage | |
| - name: Upload SDK coverage | |
| uses: codecov/codecov-action@v5 | |
| if: always() | |
| with: | |
| files: packages/plugin-sdk/coverage/lcov.info | |
| flags: sdk | |
| fail_ci_if_error: false | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| # =========================================================================== | |
| # SDK Compat Matrix — When SDK packages changed, test ALL plugins against it | |
| # =========================================================================== | |
| sdk-compat-matrix: | |
| name: SDK Compat — ${{ matrix.plugin }} | |
| runs-on: ubuntu-latest | |
| needs: [paths-filter, lockfile-sync] | |
| if: needs.paths-filter.outputs.sdk == 'true' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| plugin: | |
| - capacity-planner | |
| - community | |
| - marketplace | |
| - developer-api | |
| - plugin-publisher | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build SDK packages | |
| run: | | |
| cd packages/plugin-sdk && npm run build | |
| cd ../plugin-utils && npm run build --if-present | |
| cd ../plugin-build && npm run build --if-present | |
| - name: Test plugin against updated SDK | |
| run: | | |
| cd plugins/${{ matrix.plugin }}/frontend | |
| echo "::group::Running tests for ${{ matrix.plugin }}" | |
| npm test --if-present || echo "No tests configured for ${{ matrix.plugin }}" | |
| echo "::endgroup::" | |
| - name: Build plugin UMD bundle | |
| run: | | |
| cd plugins/${{ matrix.plugin }}/frontend | |
| echo "::group::Building UMD for ${{ matrix.plugin }}" | |
| npm run build:umd --if-present || echo "No UMD build configured for ${{ matrix.plugin }}" | |
| echo "::endgroup::" | |
| # =========================================================================== | |
| # Plugin Tests — Only test plugins that actually changed | |
| # =========================================================================== | |
| plugin-tests: | |
| name: Plugin Tests — ${{ matrix.plugin }} | |
| runs-on: ubuntu-latest | |
| needs: [paths-filter, lockfile-sync] | |
| if: needs.paths-filter.outputs.plugins == 'true' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| plugin: | |
| - capacity-planner | |
| - community | |
| - marketplace | |
| - developer-api | |
| - plugin-publisher | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check if plugin changed | |
| id: check | |
| run: | | |
| CHANGED=$(git diff --name-only ${{ github.event.pull_request.base.sha || 'HEAD~1' }} HEAD -- "plugins/${{ matrix.plugin }}/") | |
| if [ -n "$CHANGED" ]; then | |
| echo "changed=true" >> $GITHUB_OUTPUT | |
| echo "Plugin ${{ matrix.plugin }} has changes:" | |
| echo "$CHANGED" | |
| else | |
| echo "changed=false" >> $GITHUB_OUTPUT | |
| echo "Plugin ${{ matrix.plugin }} has no changes — skipping" | |
| fi | |
| - name: Setup Node.js | |
| if: steps.check.outputs.changed == 'true' | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'npm' | |
| - name: Install dependencies | |
| if: steps.check.outputs.changed == 'true' | |
| run: npm ci | |
| - name: Build SDK | |
| if: steps.check.outputs.changed == 'true' | |
| run: cd packages/plugin-sdk && npm run build | |
| - name: Run plugin frontend tests | |
| if: steps.check.outputs.changed == 'true' | |
| run: | | |
| cd plugins/${{ matrix.plugin }}/frontend | |
| npm test --if-present || echo "No tests configured for ${{ matrix.plugin }}" | |
| - name: Build plugin frontend | |
| if: steps.check.outputs.changed == 'true' | |
| run: | | |
| cd plugins/${{ matrix.plugin }}/frontend | |
| npm run build:umd --if-present || echo "No UMD build for ${{ matrix.plugin }}" | |
| - name: Upload plugin coverage | |
| if: steps.check.outputs.changed == 'true' && always() | |
| uses: codecov/codecov-action@v5 | |
| with: | |
| files: plugins/${{ matrix.plugin }}/frontend/coverage/lcov.info | |
| flags: plugin-${{ matrix.plugin }} | |
| fail_ci_if_error: false | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| # =========================================================================== | |
| # Lifecycle BDD Tests — Plugin lifecycle feature specs | |
| # =========================================================================== | |
| lifecycle-tests: | |
| name: Lifecycle BDD Tests | |
| runs-on: ubuntu-latest | |
| needs: [paths-filter, lockfile-sync] | |
| if: >- | |
| needs.paths-filter.outputs.sdk == 'true' || | |
| needs.paths-filter.outputs.plugins == 'true' || | |
| needs.paths-filter.outputs.backend == 'true' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Run lifecycle BDD feature specs | |
| run: | | |
| if [ -d "tests/lifecycle-bdd" ]; then | |
| npx vitest run --dir tests/lifecycle-bdd | |
| else | |
| echo "::notice::tests/lifecycle-bdd not present on this branch — skipping BDD specs" | |
| fi | |
| - name: Run lifecycle smoke check | |
| run: | | |
| if [ -x "bin/lifecycle-smoke.sh" ]; then | |
| ./bin/lifecycle-smoke.sh --skip-build | |
| else | |
| echo "::notice::bin/lifecycle-smoke.sh not present on this branch — skipping" | |
| fi | |
| - name: Run regression guard | |
| run: | | |
| if [ -x "bin/regression-guard.sh" ]; then | |
| ./bin/regression-guard.sh | |
| else | |
| echo "::notice::bin/regression-guard.sh not present on this branch — skipping" | |
| fi | |
| # =========================================================================== | |
| # Build — Vercel build simulation (same as production) | |
| # Catches missing deps (e.g. unzipper), CVE blocks, plugin bundle issues | |
| # Skips when only docs/config changed. Uses content-based caches to avoid | |
| # stale builds (exact keys only, no restore-keys fallback). | |
| # =========================================================================== | |
| build: | |
| name: Build | |
| runs-on: ubuntu-latest | |
| needs: [paths-filter, lockfile-sync] | |
| if: needs.paths-filter.outputs.docs_only != 'true' && needs.paths-filter.outputs.build_relevant == 'true' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Restore plugin build cache | |
| id: plugin-cache | |
| uses: actions/cache/restore@v5 | |
| with: | |
| path: dist/plugins | |
| key: ci-plugin-v1-${{ hashFiles('package-lock.json', 'plugins/**', 'packages/plugin-build/**', 'packages/plugin-utils/**') }} | |
| - name: Restore Next.js cache | |
| id: next-cache | |
| uses: actions/cache/restore@v5 | |
| with: | |
| path: apps/web-next/.next/cache | |
| key: ci-next-v1-${{ hashFiles('package-lock.json', 'apps/web-next/**', 'next.config*', 'packages/database/**') }} | |
| - name: Vercel build (plugin bundles + Next.js) | |
| id: vercel-build | |
| run: ./bin/vercel-build.sh | |
| env: | |
| DATABASE_URL: postgresql://test:test@localhost:5432/test | |
| NEXTAUTH_SECRET: test-secret-at-least-32-characters-long | |
| SKIP_PLUGIN_BUILD: ${{ steps.plugin-cache.outputs.cache-hit == 'true' }} | |
| - name: Save plugin build cache | |
| if: steps.vercel-build.outcome == 'success' | |
| uses: actions/cache/save@v5 | |
| with: | |
| path: dist/plugins | |
| key: ci-plugin-v1-${{ hashFiles('package-lock.json', 'plugins/**', 'packages/plugin-build/**', 'packages/plugin-utils/**') }} | |
| - name: Save Next.js cache | |
| if: steps.vercel-build.outcome == 'success' | |
| uses: actions/cache/save@v5 | |
| with: | |
| path: apps/web-next/.next/cache | |
| key: ci-next-v1-${{ hashFiles('package-lock.json', 'apps/web-next/**', 'next.config*', 'packages/database/**') }} | |
| - name: Upload build artifacts | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: nextjs-build | |
| path: apps/web-next/.next | |
| retention-days: 7 | |
| # =========================================================================== | |
| # Quality Gates — Final check that all required jobs passed | |
| # =========================================================================== | |
| quality-gates: | |
| name: Quality Gates | |
| runs-on: ubuntu-latest | |
| needs: [lockfile-sync, audit, lint-typecheck, build, sdk-tests, sdk-compat-matrix, plugin-tests, shell-tests, lifecycle-tests] | |
| if: always() && !cancelled() | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Verify required files | |
| run: | | |
| echo "=== Quality Gate Check ===" | |
| [ -f "apps/web-next/package.json" ] || { echo "FAIL: package.json missing"; exit 1; } | |
| [ -f "apps/web-next/next.config.js" ] || { echo "FAIL: next.config.js missing"; exit 1; } | |
| [ -f "apps/web-next/tsconfig.json" ] || { echo "FAIL: tsconfig.json missing"; exit 1; } | |
| - name: Check upstream job results | |
| run: | | |
| echo "=== Upstream Job Results ===" | |
| echo "lockfile-sync: ${{ needs.lockfile-sync.result }}" | |
| echo "audit: ${{ needs.audit.result }}" | |
| echo "lint-typecheck: ${{ needs.lint-typecheck.result }}" | |
| echo "build: ${{ needs.build.result }}" | |
| echo "sdk-tests: ${{ needs.sdk-tests.result }}" | |
| echo "sdk-compat-matrix: ${{ needs.sdk-compat-matrix.result }}" | |
| echo "plugin-tests: ${{ needs.plugin-tests.result }}" | |
| echo "shell-tests: ${{ needs.shell-tests.result }}" | |
| echo "lifecycle-tests: ${{ needs.lifecycle-tests.result }}" | |
| FAILED=0 | |
| if [[ "${{ needs.lockfile-sync.result }}" == "failure" ]]; then | |
| echo "::error::Lockfile sync failed — package-lock.json is out of sync with package.json" | |
| FAILED=1 | |
| fi | |
| if [[ "${{ needs.audit.result }}" == "failure" ]]; then | |
| echo "::error::Audit failed (critical vulnerabilities)" | |
| FAILED=1 | |
| fi | |
| if [[ "${{ needs.lint-typecheck.result }}" == "failure" ]]; then | |
| echo "::error::Lint/TypeCheck failed" | |
| FAILED=1 | |
| fi | |
| if [[ "${{ needs.build.result }}" == "failure" ]]; then | |
| echo "::error::Build failed" | |
| FAILED=1 | |
| fi | |
| if [[ "${{ needs.sdk-tests.result }}" == "failure" ]]; then | |
| echo "::error::SDK tests failed — coverage or test failures block merge" | |
| FAILED=1 | |
| fi | |
| if [[ "${{ needs.sdk-compat-matrix.result }}" == "failure" ]]; then | |
| echo "::error::SDK compatibility matrix failed — plugin regression detected" | |
| FAILED=1 | |
| fi | |
| if [[ "${{ needs.plugin-tests.result }}" == "failure" ]]; then | |
| echo "::error::Plugin tests failed — test or build failures block merge" | |
| FAILED=1 | |
| fi | |
| if [[ "${{ needs.shell-tests.result }}" == "failure" ]]; then | |
| echo "::error::Shell tests failed" | |
| FAILED=1 | |
| fi | |
| if [[ "${{ needs.lifecycle-tests.result }}" == "failure" ]]; then | |
| echo "::error::Lifecycle BDD tests failed" | |
| FAILED=1 | |
| fi | |
| if [[ "$FAILED" -eq 1 ]]; then | |
| echo "::error::One or more quality gates FAILED" | |
| exit 1 | |
| fi | |
| echo "=== All Quality Gates PASSED ===" |