Skip to main content

CI/CD Pipeline Overview

Purpose

Document the CI/CD pipeline architecture, job dependencies, test integration, and quality gates that ensure code quality before production deployment.

Scope

  • GitHub Actions workflow design
  • Job sequence and dependencies
  • Quality gates (lint, format, typecheck, tests)
  • Test job integration (unit + E2E)
  • Link-validation integration (registry validation + full Playwright E2E suite)
  • External evidence-link monitoring (scheduled/manual, non-blocking)
  • Coverage reporting and artifacts
  • Build promotion and deployment

Workflow Overview

The Portfolio App CI/CD pipeline enforces quality gates via GitHub Actions before build and deployment, with a three-tier environment strategy (Preview → Staging → Production).

Environment Tiers

EnvironmentBranchDeployment TriggerDomainPurpose
PreviewPR branchesAuto on PR creation*.vercel.app (auto-generated)Feature validation and PR review
StagingstagingManual (merge main)staging-bns-portfolio.vercel.appPre-production validation
ProductionmainAuto (after CI passes)bryce.seefieldt.caLive public site

Deployment flow: PR → Preview (auto) → Merge to main → CI runs → Production (auto) → Staging (manual) → Production validated

Job Sequence

Job Execution Details

1. Quality Job

Purpose: Enforce code quality standards (lint, format, type safety)

Runs on: ubuntu-latest (GitHub-hosted runner)

Timeout: 10 minutes

Permissions: contents: write (for Dependabot auto-format), pull-requests: read

Steps:

  1. Checkout code

    uses: actions/checkout@v6
  2. Setup pnpm

    uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e
    with:
    version: '10.0.0'
  3. Setup Node

    uses: actions/setup-node@v6
    with:
    node-version: '20'
    cache: 'pnpm'
  4. Install dependencies (frozen)

    pnpm install --frozen-lockfile
  5. Auto-format for Dependabot PRs (conditional)

if: ${{ github.actor == 'dependabot[bot]' }}
pnpm format:write || true
# Auto-commit if changes detected
  1. Lint (fails on warnings)

    pnpm lint
  2. Format check

    pnpm format:check
  3. Typecheck

    pnpm typecheck
  4. Dependency audit

    pnpm audit --audit-level=high
    • Gate: high/critical advisories fail the job
    • Visibility: lower severities are logged separately (non-blocking)

Outcome: Fails if any checks fail; blocks subsequent jobs

2. Secrets-Scan Job

Purpose: Detect accidental secrets in code and configuration

Runs on: ubuntu-latest

Timeout: 5 minutes

Permissions: contents: read

Conditional: Only runs on pull requests (avoids BASE==HEAD issue on push)

Tool: TruffleHog (hardened with --only-verified flag)

Command:

trufflesecurity/trufflehog@main \
--base=${{ github.event.repository.default_branch }} \
--head=HEAD \
--debug \
--only-verified

Outcome: Fails if verified secrets detected (blocks PR merge)

Evidence: ADR-XXXX (secrets scanning strategy)

3. Test Job

Purpose: Run unit and E2E tests before build

Runs on: ubuntu-latest

Timeout: 15 minutes

Permissions: contents: read

Dependencies: Requires quality job to pass

Conditional: if: needs.quality.result == 'success'

Steps:

  1. Checkout code

  2. Setup pnpm (version 10.0.0)

  3. Setup Node (version 20, cache: pnpm)

  4. Install dependencies (frozen)

    pnpm install --frozen-lockfile
  5. Run unit tests

    pnpm test:unit
    • Vitest unit suite execution (pnpm test:unit)
    • Fails if unit tests fail
  6. Install Playwright browsers

    npx playwright install --with-deps
  7. Start dev server

    pnpm dev &
  8. Wait for server readiness

    npx wait-on http://localhost:3000
  9. Run E2E tests

    pnpm test:e2e
    • Multi-browser: Chromium, Firefox
    • 66 tests covering core routes, slugs, 404s, metadata endpoints, evidence links, and security APIs
    • 2 retries in CI, 0 locally
    • HTML report generated
  10. Upload coverage reports (if always)

    uses: actions/upload-artifact@v7
    with:
    name: coverage-report
    path: coverage/
    retention-days: 7

Outcome: Fails if any tests fail or coverage targets not met

4. Build Job

Purpose: Compile production bundle and deploy to Vercel

Runs on: ubuntu-latest

Timeout: 15 minutes

Permissions: contents: read

Dependencies: Requires quality, test, and link-validation jobs to pass

Conditional: if: always() && needs.quality.result == 'success' && needs.test.result == 'success' && needs.link-validation.result == 'success'

Steps:

  1. Checkout code

  2. Setup pnpm (version 10.0.0)

  3. Setup Node (version 20, cache: pnpm)

  4. Install dependencies (frozen)

    pnpm install --frozen-lockfile
  5. Build

    pnpm build
    • Compiles Next.js app
    • Registry validation runs at build time
    • Fails if any build errors occur
  6. Deploy to Vercel (automatic)

    • Deployment happens automatically on main push
    • Preview deployments on PRs
    • Requires Vercel GitHub App integration

Outcome: Fails if build fails; deployment blocked until all gates pass

Purpose: Validate registry integrity and run deterministic Playwright E2E coverage as a dedicated gate.

Runs on: ubuntu-latest

Timeout: 15 minutes

Permissions: contents: read

Dependencies: Requires quality job to pass

Key steps:

  1. pnpm install --frozen-lockfile
  2. pnpm registry:validate
  3. Install Playwright browsers
  4. Start dev server + wait-on readiness
  5. pnpm links:check (maps to full Playwright E2E suite)

Outcome: Fails if registry validation or Playwright E2E checks fail.

Workflow: external-link-monitor

Job: check-external-evidence-links

Triggers:

  • Daily schedule (20 9 * * * UTC)
  • Manual dispatch (workflow_dispatch)

Command:

pnpm links:check:external

Design intent: monitor live external URL reachability without adding third-party uptime dependencies to required PR merge checks.

Build Blocking & Merge Gates

GitHub Ruleset Configuration

Branch: main

Required Status Checks (must pass before merge):

- ci / quality
- ci / test
- ci / link-validation
- ci / build

Note: external-link-monitor is intentionally non-blocking and not listed as a required PR merge check.

Dismiss Stale PR Reviews: False (reviewers' approvals invalidated by new commits)

Require Code Review: At least 1 approval required

Rationale:

  • Automation gates catch regressions early
  • Code review gate ensures human review
  • Build blocking prevents deployment of broken code

Promotion Path

  1. Feature branch → Create PR targeting main
  2. CI runs → Quality gate runs first, test gate runs second, build gate runs last
  3. All gates pass → PR is ready for merge
  4. Code review → Human review + approval required
  5. Merge to main → Automatically triggers deployment via Vercel

Environment Variables & Configuration

Public Configuration (CI Environment)

Portfolio App (.github/workflows/ci.yml env section):

env:
NEXT_PUBLIC_DOCS_BASE_URL: https://your-docs-domain.example
NEXT_PUBLIC_GITHUB_URL: https://github.com/bryce-seefieldt/portfolio-app
NEXT_PUBLIC_DOCS_GITHUB_URL: https://github.com/bryce-seefieldt/portfolio-docs
NEXT_PUBLIC_SITE_URL: https://bryce.seefieldt.ca

Rationale: Non-sensitive public URLs used for registry interpolation during CI builds

Secrets (GitHub)

None required for Portfolio App (all config is public)

Troubleshooting Common Failures

Dependabot PR remediation boundaries

Dependabot automation currently auto-formats PRs, but only for formatting drift. Maintainer intervention is still required when a Dependabot PR fails on:

  • typecheck
  • lint
  • test
  • build
  • link-validation

First-responder workflow:

  1. identify failing check and step from GitHub Actions logs
  2. checkout the Dependabot PR branch locally
  3. reproduce with repo verification command (pnpm verify)
  4. apply minimal fix and push back to the PR branch

Use Dependabot PR CI Remediation for full command-level procedure.

Known failure patterns (dependency upgrades)

  • TypeScript major updates can fail on deprecated tsconfig options (for example TS5101 around deprecated baseUrl usage).
  • Lockfile formatting is usually auto-healed for Dependabot PRs, but configuration and type-system failures are not.
  • Re-running CI without a code/config fix is only appropriate for transient infrastructure flakes.

Quality Job Failures

ESLint violations:

pnpm lint # Run locally to see errors
pnpm lint --fix # Auto-fix where possible

Prettier format violations:

pnpm format:write # Fix formatting

TypeScript type errors:

pnpm typecheck # Run locally to debug

Dependency audit failures:

pnpm audit --audit-level=high
pnpm up --latest

Test Job Failures

Unit tests fail:

pnpm test:unit # Run locally with details
pnpm test:coverage # View coverage report

E2E tests fail:

pnpm dev # Start dev server
pnpm test:e2e:ui # Debug in interactive UI

Timeout waiting for server:

  • Ensure dev server starts: pnpm dev
  • Check port 3000 availability
  • Increase wait-on timeout if needed

Because this workflow is non-blocking, failures should be triaged operationally:

  1. Re-run once to rule out transient outages.
  2. Validate failed URLs manually.
  3. Update registry evidence URLs when targets move.
  4. Track persistent upstream outages in issue/runbook workflow.

Build Job Failures

Build errors:

pnpm build # Reproduce locally

Registry validation errors:

pnpm registry:validate # Test registry schema
pnpm registry:list # List all projects

Performance Optimization

Cache Strategy

  • Dependencies: Cached via actions/setup-node@v6 with cache: pnpm
  • Playwright browsers: Installed fresh each run (pre-cached in runner images)
  • Build output: Not cached between runs (fresh build each time)

Parallelization

  • Browsers: E2E tests run in parallel across Chromium and Firefox
  • Workers: 1 worker in CI (sequential), unlimited locally
  • Jobs: Quality → Test → Build (sequential dependencies)

Runtime

  • Quality job: ~2 minutes
  • Test job: ~3-5 minutes (depends on E2E test duration)
  • Build job: ~2-3 minutes
  • Total: ~8-10 minutes (end-to-end)

Deployment Strategy

Vercel Integration

  • Auto-deploy: Enabled for main branch
  • Preview deployments: Enabled for all PRs
  • Promotion checks: Vercel checks run after GitHub checks pass

Promotion Flow

  1. PR created: Vercel creates preview deployment
  2. CI runs: All GitHub checks must pass
  3. Vercel checks run: Promotion to production requires passing checks
  4. Merge to main: Triggers production deployment to https://bryce-portfolio-app.vercel.app

Monitoring & Observability

Workflow Runs

  • GitHub Actions tab: View all workflow runs
  • PR Checks: See check status in PR UI
  • Commit Status: Badge in commit history

Coverage Reports

  • Artifacts: Download coverage reports from workflow run
  • HTML Report: Open coverage/index.html to view detailed metrics
  • Retention: 7 days (configured in upload-artifact action)

Test Reports

  • Playwright Report: Generated after E2E tests
  • Command: pnpm exec playwright show-report
  • Location: .playwright/report/

Maintenance & Evolution

Adding New Checks

  1. Update workflow file (.github/workflows/ci.yml)
  2. Document in this page (CI/CD Pipeline Overview)
  3. Update ADR if check represents new architectural decision
  4. Test locally before merging

Updating Node/pnpm Versions

  1. Update version in .nvmrc / package.json
  2. Update CI workflow (node-version, pnpm version)
  3. Test workflow in feature branch
  4. Document change in ADR or release notes

Onboarding New Team Members

  • Point to this documentation for CI/CD overview
  • Have them review .github/workflows/ci.yml
  • Run local verification commands (pnpm verify)
  • Confirm workflow passes on first PR