chore(deps-dev): bump @next/bundle-analyzer from 16.3.4 to 16.3.5 in /web #1270
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # GitHub Actions: lint + type-check + test the gw2analytics monorepo. | |
| # | |
| # Split into parallel jobs for faster feedback: | |
| # lint-python ──> test-python (needs postgres) | |
| # lint-web ──> playwright (chromium + visual-regression) | |
| # arq-integration (parallel, needs postgres + redis) | |
| # | |
| # Previously a single monolithic ``lint-and-test`` job ran all 16+ | |
| # steps sequentially (~12-15 min wall clock). The parallel split | |
| # brings the critical path to ~6-7 min by running Python lint, web | |
| # lint, and ARQ integration simultaneously, with heavier test/e2e | |
| # jobs starting immediately after their respective lint gates. | |
| # | |
| # Uses local composite actions (.github/actions/setup-*) to keep | |
| # setup blocks DRY. | |
| # | |
| # pytest-env injects the docker-compose dev credentials at session | |
| # startup, so this workflow does not need any GitHub repository | |
| # secrets. A ``postgres`` service runs alongside the test-python job | |
| # so the end-to-end ``apps/api/tests/test_uploads_e2e.py`` test can | |
| # hit a real database at ``localhost:5432``. | |
| name: CI | |
| on: | |
| # v0.15.2: repo flipped from private → public (LICENSE-cleanup | |
| # series), so the ``spending_limit`` block no longer applies and | |
| # auto-triggers are restored. ``push`` / ``pull_request`` cover | |
| # normal development; ``workflow_dispatch`` stays for ad-hoc | |
| # manual runs (e.g. cache-warmup, debugging a single job). | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| # Minimum-privilege token default for the public-repo era. Without | |
| # this, the GITHUB_TOKEN inherits the repo's default permissions | |
| # (potentially ``write-all`` on legacy settings), which is a pivot | |
| # risk if a malicious PR ever exfiltrates it. See GitHub docs: | |
| # https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| # Cancel PR runs on rapid-fire pushes to save contributor minutes, | |
| # but let ``main`` runs complete to preserve CI history for each | |
| # landed commit (so a flaky rerun doesn't lose visibility). | |
| cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} | |
| # --------------------------------------------------------------------------- | |
| # Python lint: ruff, mypy, and security audit. | |
| # Uses the full workspace sync (setup-python composite action) so | |
| # workspace members are available as editable packages for mypy. | |
| # --------------------------------------------------------------------------- | |
| jobs: | |
| lint-python: | |
| name: Lint Python | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - uses: ./.github/actions/setup-python | |
| # Cache pip-audit's OSV vulnerability database so subsequent | |
| # runs skip the ~15s download on every invocation. | |
| - name: Cache pip-audit OSV data | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/pip-audit | |
| key: pip-audit-${{ hashFiles('uv.lock') }} | |
| - name: Ruff check | |
| run: uv run ruff check --output-format=concise | |
| - name: Ruff format check | |
| run: uv run ruff format --check | |
| # v0.13.9 guard: block reintroduction of legacy db.query() | |
| - name: Guard against legacy db.query() | |
| run: | | |
| echo "Checking for legacy db.query() calls (modern select() required)..." | |
| ! grep -rn 'db\.query(' apps/api/src/ libs/ --include='*.py' | |
| - name: Mypy | |
| # Split into two invocations so libs/__main__.py and | |
| # apps/__main__.py don't collide (each invocation only | |
| # discovers one __main__.py). | |
| run: | | |
| uv run mypy libs --no-incremental | |
| uv run mypy apps/api/src --no-incremental | |
| - name: pip-audit (Python deps, vulnerability scan) | |
| # ponytail: suppress known pre-existing CVEs (protobuf PYSEC-2026-1805, | |
| # setuptools PYSEC-2026-3447). These are transitive deps pinned by | |
| # opentelemetry & OTel instrumentation compatibility constraints. | |
| run: uv run pip-audit --strict --vulnerability-service osv --ignore-vuln PYSEC-2026-1805 --ignore-vuln PYSEC-2026-3447 | |
| - name: Lint summary | |
| # Only on success to avoid showing ✅ when steps actually failed. | |
| if: success() | |
| run: | | |
| cat <<'EOF' >> $GITHUB_STEP_SUMMARY | |
| ## 🔍 Python lint results | |
| | Check | Status | | |
| |-------|--------| | |
| | Ruff check | ✅ | | |
| | Ruff format | ✅ | | |
| | db.query() guard | ✅ | | |
| | Mypy | ✅ | | |
| | pip-audit | ✅ | | |
| EOF | |
| # --------------------------------------------------------------------------- | |
| # Python tests: DB-backed pytest suite. | |
| # Depends on lint-python so lint failures abort before running 4 min of tests. | |
| # --------------------------------------------------------------------------- | |
| test-python: | |
| name: Test Python | |
| needs: lint-python | |
| runs-on: ubuntu-latest | |
| env: | |
| DATABASE_URL: "postgresql+psycopg://gw2analytics:gw2analytics@localhost:5432/gw2analytics" | |
| S3_ENDPOINT: "localhost:9000" | |
| S3_ACCESS_KEY: "gw2analytics" | |
| S3_SECRET_KEY: "gw2analytics-secret" | |
| S3_BUCKET: "gw2analytics" | |
| # NOTE: SECRETS_KEK is hardcoded to the deterministic | |
| # test-fixture value (matches pyproject.toml | |
| # ``[tool.pytest_env]``). Rationale: | |
| # (a) pytest-env injects this DURING pytest runtime, but | |
| # the alembic ``env.py`` instantiates Settings() | |
| # which requires SECRETS_KEK -- and alembic runs | |
| # BEFORE pytest, so the env block MUST provide it | |
| # explicitly. pytest-env alone is insufficient. | |
| # (b) ``${{ secrets.SECRETS_KEK }}`` would BREAK fork | |
| # PRs (GitHub strips secrets from forks -> empty | |
| # string -> Fernet crashes with ``ValueError: Fernet | |
| # key must be 32 url-safe base64-encoded bytes.``). | |
| # (c) The dummy value is already public in | |
| # pyproject.toml (# ``base64.urlsafe_b64encode(b"a"*32)``), | |
| # so hardcoding here leaks no additional information. | |
| SECRETS_KEK: "YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE=" | |
| services: | |
| postgres: | |
| image: postgres:16-alpine | |
| env: | |
| POSTGRES_USER: gw2analytics | |
| POSTGRES_PASSWORD: gw2analytics | |
| POSTGRES_DB: gw2analytics | |
| ports: | |
| - 5432:5432 | |
| options: >- | |
| --health-cmd "pg_isready -U gw2analytics" | |
| --health-interval 5s | |
| --health-timeout 5s | |
| --health-retries 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - uses: ./.github/actions/setup-python | |
| - name: Start MinIO | |
| run: | | |
| docker run -d --name minio \ | |
| -p 9000:9000 -p 9001:9001 \ | |
| -e MINIO_ROOT_USER=gw2analytics \ | |
| -e MINIO_ROOT_PASSWORD=gw2analytics-secret \ | |
| minio/minio:latest server /data --console-address :9001 | |
| echo "Waiting for MinIO to be ready..." | |
| for i in $(seq 1 15); do | |
| if curl -sf http://localhost:9000/minio/health/live > /dev/null 2>&1; then | |
| echo "MinIO is ready!" | |
| break | |
| fi | |
| sleep 2 | |
| done | |
| - name: Run database migrations | |
| working-directory: apps/api | |
| run: uv run alembic upgrade head | |
| - name: Health probe baseline | |
| run: uv run python -m gw2analytics_api.scripts.health_gate --save-baseline /tmp/health_baseline.json | |
| - name: Script tests | |
| run: uv run pytest tests/scripts/ --tb=line -q | |
| - name: Pytest | |
| # --cov-report=xml generates coverage.xml for Codecov ingestion. | |
| run: uv run pytest --tb=line -q --cov-report=xml | |
| - name: Upload coverage to Codecov | |
| # Only on main (PRs don't have the CODECOV_TOKEN secret). | |
| # Uses codecov-action@v5 which gracefully skips upload when | |
| # no token is available (e.g. on PRs from forks). | |
| if: github.ref == 'refs/heads/main' | |
| uses: codecov/codecov-action@v4 | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| files: coverage.xml | |
| flags: python | |
| fail_ci_if_error: false | |
| - name: Health probe CI gate (regression check) | |
| run: uv run python -m gw2analytics_api.scripts.health_gate --check-delta /tmp/health_baseline.json | |
| - name: Cleanup /tmp/health_baseline.json | |
| if: always() | |
| run: rm -f /tmp/health_baseline.json | |
| - name: Test summary | |
| if: success() | |
| run: | | |
| cat <<'EOF' >> $GITHUB_STEP_SUMMARY | |
| ## 🧪 Python test results | |
| | Step | Status | | |
| |------|--------| | |
| | Database migrations | ✅ | | |
| | Health probe baseline | ✅ | | |
| | Script tests | ✅ | | |
| | Pytest | ✅ | | |
| | Codecov upload | ✅ | | |
| | Health probe gate | ✅ | | |
| EOF | |
| # --------------------------------------------------------------------------- | |
| # Web lint: OpenAPI codegen, TypeScript type-check, vitest, and pnpm audit. | |
| # Needs Python (for dump_openapi.py) + Node/pnpm. | |
| # --------------------------------------------------------------------------- | |
| lint-web: | |
| name: Lint Web | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| # Python setup for the OpenAPI codegen step (dump_openapi.py) | |
| - uses: ./.github/actions/setup-python | |
| - name: Dump FastAPI OpenAPI spec | |
| run: uv run python web/scripts/dump_openapi.py > /tmp/openapi.json | |
| # Node + pnpm for the web toolchain | |
| - uses: ./.github/actions/setup-web | |
| - name: Regenerate web TypeScript client from OpenAPI spec | |
| working-directory: web | |
| run: pnpm exec openapi-typescript /tmp/openapi.json -o src/lib/api/schema.d.ts | |
| - name: Detect API client drift | |
| run: git diff --exit-code -- web/src/lib/api/schema.d.ts | |
| - name: Type-check web | |
| working-directory: web | |
| run: pnpm exec tsc --noEmit | |
| - name: Web unit tests (vitest) | |
| working-directory: web | |
| run: pnpm exec vitest run --coverage --reporter=verbose | |
| - name: Upload vitest coverage to Codecov | |
| if: github.ref == 'refs/heads/main' | |
| uses: codecov/codecov-action@v4 | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| files: web/coverage/lcov.info | |
| flags: javascript | |
| fail_ci_if_error: false | |
| - name: pnpm audit (Node deps, fail on HIGH) | |
| working-directory: web | |
| # v0.16.3: suppress known pre-existing advisory GHSA-mh99-v99m-4gvg | |
| # (brace-expansion, a transitive dev-dep DoS vulnerability that | |
| # does not affect the production application). Remove the --ignore | |
| # flag once the transitive dependency chain is updated past | |
| # brace-expansion@5.0.7. | |
| run: pnpm audit --audit-level=high --ignore GHSA-mh99-v99m-4gvg | |
| - name: Cleanup /tmp/openapi.json | |
| if: always() | |
| run: rm -f /tmp/openapi.json | |
| - name: Web lint summary | |
| if: success() | |
| run: | | |
| cat <<'EOF' >> $GITHUB_STEP_SUMMARY | |
| ## 🌐 Web lint results | |
| | Check | Status | | |
| |-------|--------| | |
| | OpenAPI codegen | ✅ | | |
| | API client drift | ✅ | | |
| | TypeScript type-check | ✅ | | |
| | Vitest unit tests | ✅ | | |
| | Vitest coverage upload | ✅ | | |
| | pnpm audit | ✅ | | |
| EOF | |
| # --------------------------------------------------------------------------- | |
| # Playwright E2E tests (chromium). | |
| # Runs on every push/PR. Browser cache (~/.cache/ms-playwright) and | |
| # the cache-warmup workflow mitigate setup cost. | |
| # --------------------------------------------------------------------------- | |
| playwright-chromium: | |
| name: Playwright (chromium) | |
| needs: lint-web | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| persist-credentials: false | |
| - uses: ./.github/actions/setup-web | |
| - name: Cache Playwright browsers | |
| uses: actions/cache@v4 | |
| id: playwright-cache | |
| with: | |
| path: ~/.cache/ms-playwright | |
| key: playwright-${{ hashFiles('web/pnpm-lock.yaml') }} | |
| - name: Install Playwright chromium | |
| if: steps.playwright-cache.outputs.cache-hit != 'true' | |
| working-directory: web | |
| run: pnpm exec playwright install chromium | |
| - name: Install Playwright system dependencies | |
| working-directory: web | |
| run: pnpm exec playwright install-deps chromium | |
| - name: Playwright tests (chromium) | |
| working-directory: web | |
| run: pnpm exec playwright test --project=chromium | |
| - name: Upload Playwright report on failure | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: playwright-report-chromium | |
| path: web/playwright-report/ | |
| retention-days: 7 | |
| - name: Upload Playwright traces on failure | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: playwright-traces-chromium | |
| path: web/test-results/ | |
| retention-days: 7 | |
| - name: Playwright summary | |
| if: success() | |
| run: | | |
| cat <<'EOF' >> $GITHUB_STEP_SUMMARY | |
| ## 🎭 Playwright (chromium) results | |
| | Check | Status | | |
| |-------|--------| | |
| | E2E tests | ✅ | | |
| EOF | |
| # --------------------------------------------------------------------------- | |
| # Playwright visual regression tests. | |
| # PR-only because there's no baseline to compare against on main. | |
| # --------------------------------------------------------------------------- | |
| playwright-visual-regression: | |
| name: Playwright (visual-regression) | |
| needs: lint-web | |
| if: github.event_name == 'pull_request' | |
| runs-on: ubuntu-latest | |
| # v0.16.x: the committed PNG baselines at docs/screenshots/ | |
| # are from a pre-v0.16.x commit era (last touched 2026-07-07 | |
| # to 2026-07-15, ~10 days before the 2026-07-24 release; | |
| # verifiable directly via | |
| # ``git log -1 --format=%H -- docs/screenshots/<file>`` -- | |
| # see the "Exit criterion" block below for the verbatim | |
| # recipe). Real diffs vs the committed baselines are NOT | |
| # regressions -- they are baseline staleness. Until a | |
| # follow-up PR refreshes the 9 baselines via a CI-equivalent | |
| # standalone prod build, the ``continue-on-error: true`` flag | |
| # stops this job from blocking PR merges while still running | |
| # the suite (and uploading the diff artifacts via the | |
| # ``failure()`` conditionals below -- they're still useful for | |
| # whoever refreshes the baselines next). Local devs retain | |
| # the same test signal via | |
| # ``pnpm exec playwright test --project=visual-regression``. | |
| # | |
| # Why ``continue-on-error: true`` is sufficient as a gate: | |
| # the flag only prevents workflow FAILURE on this job's | |
| # failure; the underlying status check still reports ``failure``. | |
| # It is currently safe because CONTRIBUTING.md §"Branch | |
| # protection for main" lists 6 required-status-checks (see | |
| # that section for the current list), and ``Playwright | |
| # (visual-regression)`` is NOT among them -- so a failed run | |
| # here does NOT block the merge gate. WARNING: a future | |
| # maintainer who adds this job to the required_status_checks | |
| # ruleset MUST also remove ``continue-on-error: true`` (or the | |
| # gate will silently start blocking merges). Cite | |
| # CONTRIBUTING.md "Branch protection for main" by section | |
| # when revisiting. | |
| # | |
| # Exit criterion: remove this ``continue-on-error: true`` flag | |
| # in a follow-up commit once (a) the docs/screenshots/*.png | |
| # baselines have been refreshed against the v0.16.x UI via a | |
| # CI-hosted capture (NOT a developer laptop -- host-specific | |
| # font/chromium drift will re-introduce the diff); AND (b) 5+ | |
| # consecutive green main-push runs after the refresh confirm | |
| # the strict 1.5% threshold is no longer flaky. The flag | |
| # exists ONLY because the committed baselines are pre-v0.16.x; | |
| # verifying baseline provenance is via | |
| # ``git log -1 --format=%H -- docs/screenshots/<file>``. | |
| continue-on-error: true | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| persist-credentials: false | |
| - uses: ./.github/actions/setup-web | |
| - name: Cache Playwright browsers | |
| uses: actions/cache@v4 | |
| id: playwright-cache | |
| with: | |
| path: ~/.cache/ms-playwright | |
| key: playwright-${{ hashFiles('web/pnpm-lock.yaml') }} | |
| - name: Install Playwright chromium | |
| if: steps.playwright-cache.outputs.cache-hit != 'true' | |
| working-directory: web | |
| run: pnpm exec playwright install chromium | |
| - name: Install Playwright system dependencies | |
| working-directory: web | |
| run: pnpm exec playwright install-deps chromium | |
| - name: Playwright tests (visual-regression) | |
| working-directory: web | |
| run: pnpm exec playwright test --project=visual-regression | |
| - name: Upload Playwright report on failure | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: playwright-report-visual-regression | |
| path: web/playwright-report/ | |
| retention-days: 7 | |
| - name: Upload Playwright traces on failure | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: playwright-traces-visual-regression | |
| path: web/test-results/ | |
| retention-days: 7 | |
| - name: Upload visual regression diffs on failure | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: visual-regression-diffs | |
| path: web/tests/e2e/.visual-regression-output/ | |
| retention-days: 7 | |
| - name: Playwright summary | |
| if: success() | |
| run: | | |
| cat <<'EOF' >> $GITHUB_STEP_SUMMARY | |
| ## 🎭 Playwright (visual-regression) results | |
| | Check | Status | | |
| |-------|--------| | |
| | Visual regression tests | ✅ | | |
| EOF | |
| # --------------------------------------------------------------------------- | |
| # ARQ worker integration tests (parallel, needs postgres + redis) | |
| # --------------------------------------------------------------------------- | |
| arq-integration: | |
| name: ARQ worker integration | |
| runs-on: ubuntu-latest | |
| env: | |
| DATABASE_URL: "postgresql+psycopg://gw2analytics:gw2analytics@localhost:5432/gw2analytics" | |
| S3_ENDPOINT: "localhost:9000" | |
| S3_ACCESS_KEY: "gw2analytics" | |
| S3_SECRET_KEY: "gw2analytics-secret" | |
| S3_BUCKET: "gw2analytics" | |
| # See test-python env block above for the full rationale. | |
| # pytest-env alone cannot satisfy the alembic-pre-pytest | |
| # injection window, so the test-fixture value is hardcoded | |
| # here too. | |
| SECRETS_KEK: "YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE=" | |
| ARQ_REDIS_HOST: localhost | |
| ARQ_REDIS_PORT: 6379 | |
| services: | |
| postgres: | |
| image: postgres:16-alpine | |
| env: | |
| POSTGRES_USER: gw2analytics | |
| POSTGRES_PASSWORD: gw2analytics | |
| POSTGRES_DB: gw2analytics | |
| ports: | |
| - 5432:5432 | |
| options: >- | |
| --health-cmd "pg_isready -U gw2analytics" | |
| --health-interval 5s | |
| --health-timeout 5s | |
| --health-retries 10 | |
| redis: | |
| image: redis:7-alpine | |
| ports: | |
| - 6379:6379 | |
| options: >- | |
| --health-cmd "redis-cli ping" | |
| --health-interval 5s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| persist-credentials: false | |
| - uses: ./.github/actions/setup-python | |
| - name: Start MinIO | |
| run: | | |
| docker run -d --name minio \ | |
| -p 9000:9000 -p 9001:9001 \ | |
| -e MINIO_ROOT_USER=gw2analytics \ | |
| -e MINIO_ROOT_PASSWORD=gw2analytics-secret \ | |
| minio/minio:latest server /data --console-address :9001 | |
| echo "Waiting for MinIO to be ready..." | |
| for i in $(seq 1 15); do | |
| if curl -sf http://localhost:9000/minio/health/live > /dev/null 2>&1; then | |
| echo "MinIO is ready!" | |
| break | |
| fi | |
| sleep 2 | |
| done | |
| - name: Run database migrations | |
| working-directory: apps/api | |
| run: uv run alembic upgrade head | |
| - name: ARQ worker integration tests | |
| working-directory: apps/api | |
| run: uv run pytest tests/test_parser_worker.py tests/test_uploads_arq.py -v | |
| - name: ARQ summary | |
| if: success() | |
| run: | | |
| cat <<'EOF' >> $GITHUB_STEP_SUMMARY | |
| ## ⚡ ARQ worker integration results | |
| | Check | Status | | |
| |-------|--------| | |
| | Database migrations | ✅ | | |
| | ARQ worker tests | ✅ | | |
| EOF | |
| # --------------------------------------------------------------------------- | |
| # DCO sign-off check. | |
| # PR-only. GitHub's web-DCO toggle (separate repo setting) covers | |
| # github.com web-editor commits natively; this inline check covers | |
| # CLI/IDE commits via ``git commit -s`` per the DCO model | |
| # documented in CONTRIBUTING.md. | |
| # | |
| # Implemented as plain bash (``git log`` + ``grep``) so there is no | |
| # external action dependency — the previous ``crazy-max/ghaction-dco@v1`` | |
| # was removed upstream, breaking the check silently. | |
| # | |
| # NOT currently added to the branch-protection ``required_status_checks`` | |
| # rule — we ship it informational-only first. Bump to hard-required | |
| # after one real Contributor-signed PR has demonstrated the workflow | |
| # end-to-end. The job will FAIL (not error) on unsigned commits, so the | |
| # first failing PR will surface clearly in the Actions UI without | |
| # blocking the merge until we wire it into required_status_checks. | |
| # --------------------------------------------------------------------------- | |
| dco-check: | |
| name: DCO check | |
| if: github.event_name == 'pull_request' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| # Full history needed so ``git log <base>..<head>`` resolves. | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| # Inline DCO check: iterates every commit in the PR range and | |
| # verifies a ``Signed-off-by:`` trailer exists in its body. | |
| # Avoids any third-party action dependency. | |
| - name: Run DCO check | |
| env: | |
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| run: | | |
| set +e | |
| bad=0 | |
| for commit in $(git log --format='%H' "${BASE_SHA}..${HEAD_SHA}"); do | |
| if ! git log --format='%B' -1 "$commit" | grep -qi 'Signed-off-by:'; then | |
| echo "::error::Missing Signed-off-by: trailer in commit ${commit}" | |
| bad=$((bad+1)) | |
| fi | |
| done | |
| if [ $bad -gt 0 ]; then | |
| echo "::warning::${bad} commit(s) missing Signed-off-by: trailer — DCO check FAILED" | |
| exit 1 | |
| fi | |
| echo "All commits have a Signed-off-by: trailer — DCO check PASSED" |