Como configurar monorepo no GitHub com proteções de branch por pacote

1. Introdução ao Monorepo e Proteções de Branch por Pacote

Um monorepo (monolithic repository) é uma estratégia de versionamento onde múltiplos projetos ou pacotes são armazenados em um único repositório Git. Essa abordagem oferece benefícios significativos como compartilhamento de código entre pacotes, versionamento unificado, refatoração cross-package simplificada e padronização de ferramentas de build e CI/CD.

No entanto, monorepos apresentam desafios específicos de segurança e integridade. Uma mudança em um pacote crítico (como autenticação) pode impactar toda a aplicação, e sem controles granulares, qualquer desenvolvedor poderia introduzir alterações sensíveis sem a devida revisão. As proteções de branch no GitHub, quando configuradas por pacote, resolvem esse problema ao exigir revisão de times específicos para mudanças em diretórios específicos.

2. Estruturando o Monorepo para Controle Granular

A base para proteções por pacote é uma estrutura de diretórios bem definida. Considere esta organização:

meu-monorepo/
├── packages/
│   ├── auth/          # Pacote de autenticação
│   ├── ui/            # Componentes de interface
│   ├── api/           # API backend
│   └── shared/        # Código compartilhado
├── apps/
│   ├── web/           # Aplicação web
│   └── mobile/        # Aplicação mobile
├── .github/
│   ├── CODEOWNERS
│   └── workflows/
└── package.json

Cada pacote deve ter seu próprio package.json com nome e escopo definidos. Para ferramentas como Lerna ou Nx, configure:

// lerna.json
{
  "version": "independent",
  "packages": ["packages/*", "apps/*"]
}
// nx.json
{
  "tasksRunnerOptions": {
    "default": {
      "runner": "nx/tasks-runners/default"
    }
  }
}

3. Configurando Regras de Proteção de Branch no GitHub

Acesse as configurações do repositório no GitHub em Settings > Branches > Add branch protection rule. Configure para main e develop:

Branch name pattern: main
✅ Require a pull request before merging
   ✅ Require approvals: 2
✅ Dismiss stale pull request approvals when new commits are pushed
✅ Require status checks to pass before merging
✅ Require branches to be up to date
✅ Include administrators
✅ Require conversation resolution before merging

Para branches de release (ex: release/*), crie regras similares mas com menos exigências:

Branch name pattern: release/*
✅ Require a pull request before merging
   ✅ Require approvals: 1
✅ Require status checks to pass before merging

4. Implementando Proteções por Pacote com CODEOWNERS

O arquivo .github/CODEOWNERS é a peça central para proteções por pacote. Ele mapeia caminhos de diretório para times ou usuários que devem revisar mudanças naqueles arquivos.

Exemplo completo:

# Proteção global: todos os arquivos precisam de revisão do tech-lead
* @tech-lead

# Pacote de autenticação: revisão obrigatória do time de segurança
packages/auth/ @team-security @auth-leads

# Pacote de API: revisão do time de backend
packages/api/ @team-backend @api-architects

# Pacote de UI: revisão do time de frontend
packages/ui/ @team-frontend @design-system

# Código compartilhado: revisão de todos os leads
packages/shared/ @tech-lead @arch-leads

# Workflows do GitHub: revisão do time de DevOps
.github/workflows/ @team-devops

# Documentação: qualquer um pode revisar
docs/ @team-all

Quando um PR altera arquivos em packages/auth/, o GitHub automaticamente solicita revisão de @team-security e @auth-leads. Se o PR também altera packages/ui/, os revisores de frontend são adicionados.

5. Automatizando Status Checks por Pacote com GitHub Actions

Para garantir que mudanças em pacotes específicos passem por testes antes do merge, crie workflows com filtros de caminho:

# .github/workflows/test-auth.yml
name: Test Auth Package

on:
  pull_request:
    paths:
      - 'packages/auth/**'
      - 'packages/shared/**'  # Dependências compartilhadas
    branches:
      - main
      - develop

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npx jest packages/auth --coverage
      - run: npx eslint packages/auth

Workflow para pacote de API:

# .github/workflows/test-api.yml
name: Test API Package

on:
  pull_request:
    paths:
      - 'packages/api/**'
      - 'packages/shared/**'
    branches:
      - main
      - develop

jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: test
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm ci
      - run: npx jest packages/api --forceExit

Combine múltiplos status checks nas regras de proteção de branch. Se um PR altera packages/auth e packages/api, ambos os workflows devem passar.

6. Integração de Proteções com Ferramentas de Monorepo

Ferramentas como Lerna, Nx e Turborepo permitem executar comandos apenas nos pacotes afetados, otimizando a CI:

Com Lerna:

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

on:
  pull_request:
    branches:
      - main

jobs:
  test-affected:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Necessário para detectar mudanças
      - uses: actions/setup-node@v4
      - run: npm ci
      - run: npx lerna run test --since origin/main
      - run: npx lerna run lint --since origin/main

Com Nx:

# .github/workflows/ci-nx.yml
name: CI Nx

on:
  pull_request:
    branches:
      - main

jobs:
  affected:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: actions/setup-node@v4
      - run: npm ci
      - run: npx nx affected:test --base=origin/main --parallel=3
      - run: npx nx affected:lint --base=origin/main
      - run: npx nx affected:build --base=origin/main

Esses comandos detectam automaticamente quais pacotes foram alterados em relação à branch base e executam apenas os testes relevantes, reduzindo significativamente o tempo de CI.

7. Boas Práticas e Monitoramento Contínuo

Para manter o sistema funcionando:

  1. Documente as regras: Crie um CONTRIBUTING.md explicando como as proteções funcionam e como os times devem revisar PRs.

  2. Revise periodicamente: A cada sprint, verifique se os CODEOWNERS ainda refletem a estrutura atual da equipe.

  3. Exceções emergenciais: Configure um processo para bypass de proteções em situações críticas (ex: hotfix em produção). Use branches especiais como hotfix/* com regras menos restritivas.

  4. Monitore conflitos: Se um pacote depende de outro, ambos os CODEOWNERS devem revisar. Use paths-ignore para evitar duplicação desnecessária de status checks.

  5. Eduque a equipe: Realize workshops sobre como criar PRs que respeitem as proteções e como resolver conflitos de revisão.

Referências