CI para Node.js com GitHub Actions

CI (Integração Contínua) para Node.js executa automaticamente testes, lint e verificação de tipos a cada pull request, impedindo que código quebrado entre no branch principal. Com GitHub Actions, o setup é declarativo em YAML e não exige servidor externo.

Workflow de CI completo para Node.js

Pipeline que roda em cada pull request e push:

yaml
# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  ci:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: ['20', '22']    # testar em múltiplas versões

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'

      - name: Instalar dependências
        run: npm ci

      - name: TypeScript check
        run: npx tsc --noEmit

      - name: ESLint
        run: npx eslint src/ --ext .ts,.tsx --max-warnings 0

      - name: Testes unitários com cobertura
        run: npm test -- --coverage --coverageReporters=text --coverageReporters=lcov

      - name: Upload cobertura para Codecov
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          file: ./coverage/lcov.info

      - name: Build
        run: npm run build

CI com banco de dados (PostgreSQL como service)

Para testes de integração que precisam de banco de dados real:

yaml
jobs:
  ci-with-db:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:16-alpine
        env:
          POSTGRES_USER: testuser
          POSTGRES_PASSWORD: testpass
          POSTGRES_DB: testdb
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

      redis:
        image: redis:7-alpine
        ports:
          - 6379:6379
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-retries 5

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '22'
          cache: 'npm'

      - run: npm ci

      - name: Rodar migrações de teste
        env:
          DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb
        run: npx prisma migrate deploy

      - name: Testes de integração
        env:
          DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb
          REDIS_URL: redis://localhost:6379
          NODE_ENV: test
        run: npm run test:integration

Configurar ESLint e Prettier no CI

Garanta que lint e formatação são verificados automaticamente:

bash
# package.json — scripts necessários
{
  "scripts": {
    "lint": "eslint src/ --ext .ts,.tsx",
    "lint:fix": "eslint src/ --ext .ts,.tsx --fix",
    "format:check": "prettier --check src/",
    "format": "prettier --write src/",
    "typecheck": "tsc --noEmit",
    "test": "jest",
    "test:integration": "jest --testPathPattern=integration",
    "test:coverage": "jest --coverage"
  }
}

# .github/workflows/ci.yml — verificar tudo:
      - name: Verificar formatação (Prettier)
        run: npm run format:check

      - name: Lint (ESLint)
        run: npm run lint

      - name: Type check (TypeScript)
        run: npm run typecheck

# Dica: use husky + lint-staged para rodar antes do commit local:
# npm install -D husky lint-staged
# npx husky init

Comentar cobertura de testes no PR

Adicione um comentário automático com a cobertura de testes em cada pull request:

yaml
      - name: Testes com cobertura
        run: npm test -- --coverage --json --outputFile=coverage/jest-results.json

      - name: Comentar cobertura no PR
        uses: ArtiomTr/jest-coverage-report-action@v2
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          test-script: npm test
          annotations: all
          threshold: 80    # falhar se cobertura < 80%

# Ou com geração manual do comentário:
      - name: Ler cobertura
        id: coverage
        run: |
          COVERAGE=$(cat coverage/coverage-summary.json | python3 -c "
          import sys,json
          data = json.load(sys.stdin)
          print(data['total']['lines']['pct'])
          ")
          echo "lines=$COVERAGE" >> $GITHUB_OUTPUT

      - name: Comentar no PR
        if: github.event_name == 'pull_request'
        uses: actions/github-script@v7
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `📊 Cobertura de testes: **${{ steps.coverage.outputs.lines }}%**`
            })

Otimizar tempo do CI com cache e paralelismo

Reduza o tempo de execução do CI com estratégias de otimização:

yaml
jobs:
  # Rodar lint, typecheck e build em paralelo (sem dependência entre si)
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '22', cache: 'npm' }
      - run: npm ci
      - run: npm run lint

  typecheck:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '22', cache: 'npm' }
      - run: npm ci
      - run: npm run typecheck

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '22', cache: 'npm' }
      - run: npm ci
      - run: npm test

  # Deploy só roda se lint, typecheck E test passarem
  deploy:
    needs: [lint, typecheck, test]
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - name: Deploy para VPS
        run: echo "Deploy aqui..."

Não quer configurar manualmente?

Implante o VPS em menos de 5 minutos com a Runstack. Infraestrutura da OPEN Datacenter, com servidores em São Paulo.

Perguntas frequentes

Sempre npm ci no CI/CD: instala exatamente as versões do package-lock.json (reproduzível), falha se lock file e package.json estiverem inconsistentes, e deleta node_modules antes de instalar (evita estado sujo). npm install pode atualizar o lock file, causando comportamento diferente entre runs.

GitHub Actions: integrado ao GitHub, marketplace enorme, sem servidor para gerenciar, gratuito para repos públicos. GitLab CI: melhor integração com GitLab, runners self-hosted mais fáceis de configurar. Jenkins: mais flexível e poderoso, mas exige servidor e manutenção. Para projetos no GitHub: Actions é a escolha natural.

As otimizações de maior impacto: (1) cache de node_modules com actions/setup-node cache, (2) rodar jobs independentes em paralelo (lint + typecheck + test simultâneos), (3) usar --bail no Jest (parar na primeira falha), (4) separar testes unitários (rápidos, sempre) dos de integração (lentos, só em PRs para main).

Jest suporta --changedSince: npx jest --changedSince=main. Isso roda apenas os testes de arquivos que mudaram em relação ao main. Útil para repositórios grandes com muitos testes. Combine com --passWithNoTests para não falhar quando nenhum teste é selecionado.

Não existe um número mágico. 80% é uma referência comum para projetos maduros. Mais importante que o número: tendência (está caindo?), cobertura de código crítico (autenticação, pagamentos, lógica de negócio deve ser >90%), e qualidade dos testes (cobertura de linha não garante cobertura de comportamento).