Skip to content

fix(wallet): fetch activeTranscoderCount and delegatorsCount from sub… #810

fix(wallet): fetch activeTranscoderCount and delegatorsCount from sub…

fix(wallet): fetch activeTranscoderCount and delegatorsCount from sub… #810

Workflow file for this run

# =============================================================================
# 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 ==="