diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 5e18a70..f2e652b 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -95,7 +95,7 @@ jobs: | Recurso | Link | |---|---| - | **🔗 URL de Preview** | [URL de Preview](${{ needs.deploy-preview.outputs.deployment_url }}) | + | **🔗 URL de Preview** | https://www.dpreview.com/(${{ needs.deploy-preview.outputs.deployment_url }}) | | **📜 Logs do Deploy** | [Ver logs da Action](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) | --- diff --git a/src/content/blog/ci-cd-saga.mdx b/src/content/blog/ci-cd-saga.mdx deleted file mode 100644 index dab637c..0000000 --- a/src/content/blog/ci-cd-saga.mdx +++ /dev/null @@ -1,151 +0,0 @@ ---- -title: "A Saga do CI/CD: Como Corrigimos uma Race Condition no GitHub Actions" -publishedAt: "2025-11-06" -summary: "Uma história real de depuração de um workflow de CI/CD, onde a concorrência de gatilhos e a ordem de execução causaram uma falha sutil, mas crítica, no nosso processo de release." ---- - -Todo desenvolvedor que já configurou um pipeline de CI/CD conhece a sensação: a satisfação de ver os checks verdes e a automação funcionando... e a frustração profunda quando uma falha misteriosa acontece. Recentemente, passei por uma dessas sagas ao tentar otimizar os workflows de um projeto, e a jornada para encontrar a solução foi cheia de lições valiosas. - -## O Problema Inicial: Workflows Lentos e Redundantes - -Tudo começou com um objetivo simples: fazer nossos workflows rodarem mais rápido. Em Pull Requests que alteravam apenas a documentação (arquivos `.md`), nossos jobs de teste, lint e deploy eram executados desnecessariamente, gastando tempo e recursos. - -A solução parecia óbvia: usar um filtro de caminho para pular os jobs se nenhuma alteração no código fosse detectada. - -## A Primeira Tentativa e o Primeiro Erro - -Implementamos a popular action `dorny/paths-filter`. A ideia era simples: se os arquivos alterados não estivessem em `src/`, `package.json`, etc., os steps seguintes seriam pulados. - -```yaml -- name: Check for code changes - id: filter - uses: dorny/paths-filter@v3 - with: - filters: | - code: - - 'src/**' - - '.github/workflows/**' - # ... outros caminhos - -- name: Install dependencies - if: steps.filter.outputs.code == 'true' - run: pnpm install -``` - -**Onde quebrou?** O workflow falhou com um erro enigmático: `Error: Can't find common ancestor`. - -- **Lição 1: O `fetch-depth` é Crucial.** A action `actions/checkout` por padrão faz um checkout "superficial" (`fetch-depth: 1`), baixando apenas o último commit. O `paths-filter` precisa do histórico completo para comparar a branch do PR com a branch base. A solução foi adicionar `fetch-depth: 0` ao checkout. - -## O Segundo Erro: A Duplicidade de Deployments - -Com o primeiro erro corrigido, notamos que cada PR criava **duas** entradas de deployment: uma atribuída a mim e outra ao bot `github-actions[bot]`. - -- **Lição 2: `environment:` Nativo vs. Actions Manuais.** Descobrimos que estávamos usando dois mecanismos para o mesmo fim. A chave `environment:` no nosso job já instruía o GitHub a criar um deployment (atribuído ao usuário que iniciou o workflow). Ao mesmo tempo, a action `bobheadxi/deployments` também criava um deployment (atribuído ao bot). A solução foi remover a action e confiar 100% no mecanismo nativo do GitHub, que hoje já é robusto o suficiente para gerenciar o ciclo de vida completo do deployment. - -## O Furo Final: A Race Condition - -Tudo parecia perfeito, até que uma análise mais profunda revelou uma falha crítica. Nosso workflow `preview.yml` era acionado por dois eventos: `push` em branches `release/*` e `pull_request`. - -O objetivo era: - -1. No `push` para `release/*`, criar uma tag beta com `semantic-release`. -2. No `pull_request`, apenas rodar testes e gerar um preview. - -O problema era a `concurrency: cancel-in-progress: true`. - -- **A Race Condition:** Quando um desenvolvedor fazia o push da branch de release e abria o PR em seguida, duas execuções do workflow eram disparadas. A `concurrency` cancelava a mais antiga (a do `push`), e a execução do `pull_request` continuava. Como o `semantic-release` só rodava no evento de `push`, a tag beta **nunca era criada**. - -- **Lição 3: Separe as Responsabilidades.** Um workflow não deve tentar servir a dois mestres. A solução foi dividir o `preview.yml` em dois: - 1. **`preview.yml`:** Focado apenas em validação de PRs (gatilho `pull_request`). - 2. **`create-beta-release.yml`:** Focado apenas em criar a release beta (gatilho `push`). - -Essa separação eliminou a competição entre os gatilhos e a race condition, garantindo que cada processo rode de forma confiável e independente. - -## O Capítulo Final: A Otimização Condicional - -Mas a saga não acabou aí. Após resolver a race condition, percebemos que nossa arquitetura ainda tinha uma falha fundamental: **os status checks ficavam inconsistentes**. - -### O Dilema dos Status Checks - -Quando implementamos o paths-filter no `preview.yml`, o job `deploy-preview` era completamente **pulados** quando não havia mudanças no código. Isso fazia com que os status checks aparecessem como "skipped" em vez de "success", quebrando a consistência dos required status checks. - -```yaml -# ❌ ANTES: Job pulado completamente -deploy-preview: - if: needs.test-and-lint.outputs.code-changed == 'true' # ← Isso fazia o job ser skipped - uses: ./.github/workflows/reusable-deploy-vercel.yml -``` - -### A Solução Arquitetural - -A resposta estava em mover o controle **para dentro** dos reusable workflows. Em vez de decidir no nível do job se rodar ou não, deixar os reusable workflows decidirem internamente quais steps executar. - -```yaml -# ✅ DEPOIS: Job sempre roda, controle interno -deploy-preview: - # Sem condição no job - sempre roda para status check consistente - uses: ./.github/workflows/reusable-deploy-vercel.yml -``` - -Dentro do `reusable-deploy-vercel.yml`, adicionamos o paths-filter e condições em todos os steps: - -```yaml -jobs: - deploy: - steps: - - name: Check for code changes - id: filter - uses: dorny/paths-filter@v3 - with: - filters: | - code: - - 'src/**' - - 'public/**' - - 'package.json' - # ... outros arquivos de código - - - name: Install Vercel CLI - if: steps.filter.outputs.code == 'true' # ← Condição no step - run: npm install --global vercel@latest - - - name: Build Project Artifacts - if: steps.filter.outputs.code == 'true' # ← Condição no step - run: vercel build - - - name: Deploy to Vercel - if: steps.filter.outputs.code == 'true' # ← Condição no step - run: vercel deploy -``` - -### O Resultado Perfeito - -Agora ambos os reusable workflows seguem o **mesmo padrão arquitetural**: - -- **`reusable-test-and-lint.yml`**: Paths-filter + steps condicionais -- **`reusable-deploy-vercel.yml`**: Paths-filter + steps condicionais - -**Comportamento final:** -- **PRs com mudanças no código**: Jobs rodam e fazem trabalho real ✅ -- **PRs só com documentação**: Jobs rodam (status checks verdes) mas pulam steps internos ✅ - -**Benefícios alcançados:** -- ✅ **Status checks consistentes** (sempre success) -- 💰 **Otimização de custos** (deploy condicional no Vercel) -- 🔄 **Arquitetura uniforme** nos reusable workflows -- 📋 **Compatível com required status checks** - -## Conclusão - -O que começou como uma simples otimização se tornou uma jornada profunda pela arquitetura do GitHub Actions. A lição final é clara: workflows de CI/CD são parte do código e merecem a mesma atenção à arquitetura, separação de responsabilidades e depuração que aplicamos à nossa aplicação. - -**Nunca subestime:** -- A importância de um bom `fetch-depth` -- O poder das race conditions em sistemas concorrentes -- A necessidade de status checks consistentes -- A beleza de uma arquitetura uniforme - -E lembre-se: em CI/CD, assim como na vida, **separar responsabilidades** é sempre a melhor solução! 🚀 - ---- - -\*Escrito com ❤️ por **Matheus Caiser, The Mr. Developer\*** diff --git a/src/content/blog/merge-vs-rebase.mdx b/src/content/blog/merge-vs-rebase.mdx deleted file mode 100644 index 393c174..0000000 --- a/src/content/blog/merge-vs-rebase.mdx +++ /dev/null @@ -1,71 +0,0 @@ ---- -title: "Merge Commit vs. Rebase: Qual a Melhor Estratégia para seu Histórico Git?" -publishedAt: "2025-11-06" -summary: "Um debate clássico no mundo Git: você deve preferir um histórico linear e limpo com rebase, ou um histórico rastreável e completo com merge commits? Vamos analisar os prós e contras de cada abordagem." ---- - -Se você já trabalhou em uma equipe de desenvolvimento, provavelmente já se deparou com este debate: qual é a maneira "certa" de incorporar as mudanças de uma feature branch na branch principal? A resposta geralmente se resume a duas estratégias principais oferecidas pelo GitHub: **Create a merge commit** e **Rebase and merge**. - -Ambas as abordagens têm o mesmo resultado final — o código da sua feature chega à branch de destino — mas elas contam a *história* de como ele chegou lá de maneiras drasticamente diferentes. - -## A Abordagem 1: "Create a merge commit" (O Historiador) - -Esta é a estratégia padrão do Git. Ela preserva a história exatamente como ela aconteceu. - -- **Como funciona:** Quando você faz o merge de um Pull Request, o Git cria um novo commit, o "merge commit". Este commit especial tem dois "pais": o último commit da branch de destino e o último commit da sua feature branch. Ele une os dois históricos. - -- **Como fica o `git log`:** - ``` - * Merge pull request #123 from feature/nova-feature (main) - |\ - | * feat: Adiciona nova funcionalidade (feature/nova-feature) - | * fix: Corrige bug na funcionalidade - * | commit anterior (main) - |/ - * ... - ``` - O histórico se torna um grafo, parecendo uma "árvore de natal". - -- **Prós:** - * **Rastreabilidade Absoluta:** É indiscutível *quando* o PR foi mergeado e de onde ele veio. O contexto do PR está permanentemente gravado no histórico do Git. - * **Não Reescreve a História:** Os commits originais da feature branch permanecem intocados, o que é considerado mais seguro por alguns. - -- **Contras:** - * **Histórico "Poluído":** O log fica cheio de commits de merge que, para alguns, são apenas ruído e dificultam a leitura da evolução linear do projeto. - -## A Abordagem 2: "Rebase and merge" (O Editor) - -Esta estratégia prioriza um histórico limpo e legível. - -- **Como funciona:** Antes de fazer o merge, o Git pega todos os commits da sua feature branch e os "reaplica", um por um, em cima do último commit da branch de destino. Depois disso, a branch de destino pode ser simplesmente "avançada" para incluir esses novos commits, sem a necessidade de um merge commit. - -- **Como fica o `git log`:** - ``` - * feat: Adiciona nova funcionalidade (main) - * fix: Corrige bug na funcionalidade - * commit anterior (main) - * ... - ``` - O histórico fica perfeitamente linear, como se todo o trabalho tivesse sido feito diretamente na branch principal. - -- **Prós:** - * **Histórico Limpo e Legível:** É extremamente fácil seguir a sequência de mudanças no projeto. - * **Facilita a Depuração:** Ferramentas como `git bisect` (para encontrar quando um bug foi introduzido) funcionam muito melhor em um histórico linear. - -- **Contras:** - * **Perda de Contexto do PR:** Você perde a informação explícita de "quando o PR #123 foi mergeado" diretamente no log do Git. Esse contexto passa a viver apenas na interface do GitHub. - * **Reescreve a História:** Os hashes dos seus commits originais são alterados durante o rebase. - -## Conclusão: Qual é o Melhor? - -Não há uma resposta "certa". É uma escolha filosófica para o seu projeto. - -- **Escolha "Merge Commit" se:** Você valoriza a rastreabilidade histórica e a integridade dos commits acima de tudo. É ótimo para projetos com auditorias rigorosas ou para equipes com muitos desenvolvedores juniores, pois é o fluxo mais simples de entender. - -- **Escolha "Rebase and Merge" se:** Você valoriza um histórico limpo e legível e está confortável em usar a interface do GitHub para encontrar o contexto de um PR. É uma abordagem muito popular em projetos de alta performance e equipes que priorizam a clareza do `git log`. - -No nosso projeto, optamos pelo **"Rebase and Merge"**. A clareza do histórico linear supera a perda do contexto do merge commit no log, e essa decisão nos ajuda a manter o projeto organizado e fácil de navegar. - ---- - -*Escrito com ❤️ por **Matheus Caiser, The Mr. Developer***