From b723c320ab002923fc62e731e0086809c2620be7 Mon Sep 17 00:00:00 2001 From: Matheus Henrique Caiser Barrozo Date: Thu, 6 Nov 2025 22:59:12 -0300 Subject: [PATCH 1/3] fix: rename workflow jobs to prevent status check conflicts - Rename jobs with unique prefixes to avoid confusion when multiple workflows run - Preview jobs: 'Preview - Test & Lint', 'Preview - Deploy to Vercel' - Production jobs: 'Production - Test & Lint', 'Production - Deploy to Vercel' - Develop jobs: 'Develop - Test & Lint', 'Develop - Deploy to Vercel' - This resolves duplicate job names in PR status checks Note: Rulesets need manual update via web interface to match new job names --- .github/workflows/develop.yml | 4 ++-- .github/workflows/preview.yml | 4 ++-- .github/workflows/production.yml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml index 9267de0..8d513cc 100644 --- a/.github/workflows/develop.yml +++ b/.github/workflows/develop.yml @@ -15,13 +15,13 @@ on: jobs: test-and-lint: - name: Test & Lint + name: Develop - Test & Lint uses: ./.github/workflows/reusable-test-and-lint.yml with: node-version: "22.x" deploy-develop: - name: Deploy to Vercel Preview + name: Develop - Deploy to Vercel needs: test-and-lint uses: ./.github/workflows/reusable-deploy-vercel.yml with: diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 483929b..c98549a 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -18,7 +18,7 @@ concurrency: jobs: test-and-lint: - name: Test & Lint + name: Preview - Test & Lint uses: ./.github/workflows/reusable-test-and-lint.yml with: node-version: "22.x" @@ -31,7 +31,7 @@ jobs: run: echo "✅ PR validation completed" deploy-preview: - name: Deploy to Vercel Preview + name: Preview - Deploy to Vercel needs: test-and-lint if: ${{ needs.test-and-lint.outputs.code-changed == 'true' }} uses: ./.github/workflows/reusable-deploy-vercel.yml diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml index 5050553..941f2b9 100644 --- a/.github/workflows/production.yml +++ b/.github/workflows/production.yml @@ -16,14 +16,14 @@ on: jobs: test-and-lint: - name: Test & Lint + name: Production - Test & Lint if: ${{ !contains(github.event.head_commit.message, 'chore(release)') }} uses: ./.github/workflows/reusable-test-and-lint.yml with: node-version: "22.x" deploy-production: - name: Deploy to Vercel Production + name: Production - Deploy to Vercel needs: test-and-lint uses: ./.github/workflows/reusable-deploy-vercel.yml with: From 929d44a020d68f304529e1cf80d79368c940e334 Mon Sep 17 00:00:00 2001 From: Matheus Henrique Caiser Barrozo Date: Fri, 7 Nov 2025 09:31:14 -0300 Subject: [PATCH 2/3] refactor: implement conditional deploy logic in reusable workflows - Move deploy control from caller to reusable-deploy-vercel.yml - Add paths-filter back to reusable-deploy-vercel.yml for internal control - Remove job-level condition from preview.yml deploy-preview - Both jobs now always run (green status checks) but skip steps when no code changes - Optimizes Vercel costs: deploy only when src/public/package files change Before: deploy-preview job skipped entirely when no code changes After: deploy-preview job runs but skips internal steps, maintaining status checks --- .github/copilot-instructions.md | 17 ++ .github/workflows/create-beta-tag.yml | 117 ---------- .github/workflows/develop.yml | 5 +- .github/workflows/preview.yml | 57 ++--- .github/workflows/production.yml | 21 +- .github/workflows/reusable-create-tag.yml | 155 -------------- .github/workflows/reusable-deploy-vercel.yml | 22 +- .github/workflows/reusable-release.yml | 62 ------ tutorial-github-actions-schema.md | 214 +++++++++++++++++++ 9 files changed, 298 insertions(+), 372 deletions(-) delete mode 100644 .github/workflows/create-beta-tag.yml delete mode 100644 .github/workflows/reusable-create-tag.yml delete mode 100644 .github/workflows/reusable-release.yml create mode 100644 tutorial-github-actions-schema.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index dc3f2a8..d6ed348 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -354,6 +354,23 @@ pnpm lint:fix # Correção automática - `reusable-release.yml`: Semantic release automation - Cache de build artifacts (`.vite`, `node_modules/.cache`, `.eslintcache`) +**Debugging de Workflows do GitHub Actions:** + +- **Tutorial completo:** Consulte `tutorial-github-actions-schema.md` para guia detalhado +- **Passo 1:** Execute `pnpm lint:yaml` para verificar sintaxe YAML +- **Passo 2:** Execute `pnpm lint` para verificar código relacionado +- **Passo 3:** Valide YAML com Python se necessário +- **Passo 4:** Analise erros específicos do linter (ex: condições `if`) +- **Regra:** YAML válido ≠ Workflow válido - sempre teste todas as camadas + +**Correção de Erros de Lint em Workflows:** + +- **Sintaxe YAML:** Use `pnpm lint:yaml` para validar arquivos `.github/workflows/*.yml` +- **Condições `if`:** Não use aspas em expressões GitHub Actions + - ❌ Errado: `if: "always() && !contains(...)"` + - ✅ Correto: `if: always() && !contains(...)` +- **Validação:** YAML válido não significa workflow válido - teste sempre + ### Workflow Preview - Otimizações Recentes **Implementado em novembro de 2025 - Resolução de duplicação e status checks quebrados:** diff --git a/.github/workflows/create-beta-tag.yml b/.github/workflows/create-beta-tag.yml deleted file mode 100644 index e4cc798..0000000 --- a/.github/workflows/create-beta-tag.yml +++ /dev/null @@ -1,117 +0,0 @@ -name: Create Beta Tag - -# Workflow manual para criar tags beta em release branches -# Executado manualmente via workflow_dispatch -# Realiza testes, deploy e cria tag beta automática - -on: - workflow_dispatch: - inputs: - branch: - description: "Release branch (e.g., release/1.1.0)" - required: true - type: string - -env: - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} - -jobs: - validate-branch: - name: Validate Branch - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: read - outputs: - has_pr: ${{ steps.check_pr.outputs.has_pr }} - version: ${{ steps.extract_version.outputs.version }} - commit_sha: ${{ steps.get_commit.outputs.commit_sha }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - ref: ${{ inputs.branch }} - fetch-depth: 0 - - - name: Check if PR exists for this branch - id: check_pr - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - BRANCH_NAME="${{ inputs.branch }}" - PR_COUNT=$(gh pr list --head "$BRANCH_NAME" --state open --json number --jq 'length') - if [ "$PR_COUNT" -gt 0 ]; then - echo "::warning::PR exists for branch $BRANCH_NAME. Beta tag creation may interfere with PR." - echo "has_pr=true" >> $GITHUB_OUTPUT - else - echo "No open PR found for branch $BRANCH_NAME. Safe to proceed." - echo "has_pr=false" >> $GITHUB_OUTPUT - fi - - - name: Extract version from branch name - id: extract_version - run: | - BRANCH_NAME="${{ inputs.branch }}" - VERSION=$(echo "$BRANCH_NAME" | sed 's|^release/||') - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "Version extracted: $VERSION" - - - name: Get commit SHA - id: get_commit - run: | - COMMIT_SHA=$(git rev-parse HEAD) - echo "commit_sha=$COMMIT_SHA" >> $GITHUB_OUTPUT - echo "Commit SHA: $COMMIT_SHA" - - test-and-lint: - needs: validate-branch - uses: ./.github/workflows/reusable-test-and-lint.yml - with: - node-version: "22.x" - - deploy-preview: - needs: [validate-branch, test-and-lint] - uses: ./.github/workflows/reusable-deploy-vercel.yml - with: - environment: Preview - ref: ${{ needs.validate-branch.outputs.commit_sha }} - prebuilt: true - secrets: - vercel-token: ${{ secrets.VERCEL_TOKEN }} - github-token: ${{ secrets.GITHUB_TOKEN }} - - create-beta-tag: - needs: [validate-branch, deploy-preview] - uses: ./.github/workflows/reusable-create-tag.yml - with: - tag-type: beta - version: ${{ needs.validate-branch.outputs.version }} - commit-sha: ${{ needs.validate-branch.outputs.commit_sha }} - create-version-commit: false - secrets: inherit - permissions: - contents: write - - summary: - name: Deployment Summary - needs: [validate-branch, deploy-preview, create-beta-tag] - if: always() - runs-on: ubuntu-latest - steps: - - name: Create Summary - run: | - echo "## 🚀 Beta Tag Creation Summary" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Branch:** \`${{ inputs.branch }}\`" >> $GITHUB_STEP_SUMMARY - echo "**Version:** \`${{ needs.validate-branch.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY - echo "**Commit:** \`${{ needs.validate-branch.outputs.commit_sha }}\`" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Results:" >> $GITHUB_STEP_SUMMARY - echo "- ✅ Tests & Lint: ${{ needs.test-and-lint.result }}" >> $GITHUB_STEP_SUMMARY - echo "- ✅ Deploy: ${{ needs.deploy-preview.result }}" >> $GITHUB_STEP_SUMMARY - echo "- ✅ Tag Creation: ${{ needs.create-beta-tag.result }}" >> $GITHUB_STEP_SUMMARY - if [ "${{ needs.validate-branch.outputs.has_pr }}" = "true" ]; then - echo "" >> $GITHUB_STEP_SUMMARY - echo "⚠️ **Warning:** An open PR exists for this branch." >> $GITHUB_STEP_SUMMARY - fi diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml index 8d513cc..c2872c9 100644 --- a/.github/workflows/develop.yml +++ b/.github/workflows/develop.yml @@ -23,6 +23,9 @@ jobs: deploy-develop: name: Develop - Deploy to Vercel needs: test-and-lint + # ✅ CORREÇÃO: Aplicando o tutorial + # 'if:' sem aspas externas + if: needs.test-and-lint.outputs.code-changed == 'true' uses: ./.github/workflows/reusable-deploy-vercel.yml with: environment: Development @@ -31,4 +34,4 @@ jobs: prebuilt: true secrets: vercel-token: ${{ secrets.VERCEL_TOKEN }} - github-token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index c98549a..f2e652b 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -1,70 +1,77 @@ name: Preview Environment # Workflow para deploy do ambiente de preview/staging -# Executado em Pull Requests para validar mudanças com testes, lint e um deploy de preview. +# Executado em: +# - Pull Requests (para preview de mudanças) +# - Push em branches release/* (para testes de release) +# Realiza testes, linting, build, deploy para Vercel (ambiente preview) +# e atualização automática de comentários nos PRs env: VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} +# ✅ MÁGICA 1: Adicionado 'concurrency' para evitar disparos duplos +# (push + synchronize) no mesmo commit. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.head.sha || github.sha }} + cancel-in-progress: true + on: pull_request: types: [opened, synchronize, reopened] + push: + branches: + - release/** workflow_dispatch: -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref }} - cancel-in-progress: true - jobs: test-and-lint: name: Preview - Test & Lint + # Este job SEMPRE roda (para satisfazer o check obrigatório) + # O 'dummy pass' acontece DENTRO do 'reusable-lint' uses: ./.github/workflows/reusable-test-and-lint.yml with: node-version: "22.x" - always-run-check: - name: Basic Validation - runs-on: ubuntu-latest - steps: - - name: Always pass for required checks - run: echo "✅ PR validation completed" - deploy-preview: name: Preview - Deploy to Vercel needs: test-and-lint - if: ${{ needs.test-and-lint.outputs.code-changed == 'true' }} + # ❌ REMOVIDO: O 'if: needs.test-and-lint.outputs.code-changed == 'true'' + # Este job TEM que rodar sempre para o status check uses: ./.github/workflows/reusable-deploy-vercel.yml with: - environment: Preview-${{ github.event.pull_request.number }} + environment: Preview vercel-environment: preview - ref: ${{ github.event.pull_request.head.sha }} + ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} prebuilt: true prod: false secrets: vercel-token: ${{ secrets.VERCEL_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }} - validate-branch-for-main: - name: Validate Branch for Main - if: github.base_ref == 'main' + validate-release-branch: + # ✅ SEGUINDO O TUTORIAL: 'if:' sem aspas + if: github.event_name == 'pull_request' && github.base_ref == 'main' runs-on: ubuntu-latest steps: - name: Check source branch name run: | SOURCE_BRANCH="${{ github.head_ref }}" echo "Source branch: $SOURCE_BRANCH" - if [[ "$SOURCE_BRANCH" == release/* ]] || [[ "$SOURCE_BRANCH" == hotfix/* ]]; then - echo "Source branch '$SOURCE_BRANCH' is valid for merging into 'main'." - else - echo "::error::Pull Request from branch '$SOURCE_BRANCH' cannot be merged into 'main'. Only branches starting with 'release/' or 'hotfix/' are allowed." + if [[ "$SOURCE_BRANCH" != release/* ]]; then + echo "::error::Pull Request from branch '$SOURCE_BRANCH' cannot be merged into 'main'. Only branches starting with 'release/' are allowed." exit 1 + else + echo "Source branch '$SOURCE_BRANCH' is a valid release branch." fi update-pr-comment: + # ✅ SEGUINDO O TUTORIAL: 'if:' sem aspas + if: github.event_name == 'pull_request' name: Update PR Comment + # Este 'needs' garante que ele espere o deploy terminar (ou ser pulado) needs: deploy-preview - if: ${{ needs.deploy-preview.result == 'success' }} runs-on: ubuntu-latest permissions: pull-requests: write @@ -88,9 +95,9 @@ 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 }}) | --- *Commit: `${{ github.event.pull_request.head.sha }}`* - edit-mode: replace + edit-mode: replace \ No newline at end of file diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml index 941f2b9..f9df569 100644 --- a/.github/workflows/production.yml +++ b/.github/workflows/production.yml @@ -17,7 +17,8 @@ on: jobs: test-and-lint: name: Production - Test & Lint - if: ${{ !contains(github.event.head_commit.message, 'chore(release)') }} + # Este job SEMPRE roda para satisfazer o Required Check + # O filtro de paths está DENTRO do reusable-test-and-lint uses: ./.github/workflows/reusable-test-and-lint.yml with: node-version: "22.x" @@ -25,10 +26,14 @@ jobs: deploy-production: name: Production - Deploy to Vercel needs: test-and-lint + # ✅ CORREÇÃO: Aplicando o tutorial + # 'if:' sem aspas externas + # Pula o deploy se 'code-changed' for falso + if: needs.test-and-lint.outputs.code-changed == 'true' uses: ./.github/workflows/reusable-deploy-vercel.yml with: environment: Production - vercel-environment: production + vercel-environment: production # ref: ${{ github.sha }} prebuilt: true prod: true @@ -38,8 +43,14 @@ jobs: run-semantic-release: name: Run Semantic Release - needs: deploy-production - if: ${{ !contains(github.event.head_commit.message, 'chore(release)') }} + needs: [test-and-lint, deploy-production] # Depende dos dois + + # ✅ CORREÇÃO: Aplicando o tutorial + # 'if:' sem aspas externas + # 'always()' garante que ele rode mesmo se 'deploy-production' for 'skipped' + # 'not(contains...)' é a trava anti-loop (substituindo o '!') + if: always() && !contains(github.event.head_commit.message, 'chore(release)') + runs-on: ubuntu-latest permissions: contents: write @@ -50,7 +61,7 @@ jobs: uses: actions/checkout@v5 with: fetch-depth: 0 - persist-credentials: false + persist-credentials: false # - name: Setup pnpm uses: pnpm/action-setup@v4 diff --git a/.github/workflows/reusable-create-tag.yml b/.github/workflows/reusable-create-tag.yml deleted file mode 100644 index 47ef57d..0000000 --- a/.github/workflows/reusable-create-tag.yml +++ /dev/null @@ -1,155 +0,0 @@ -name: Reusable Create Tag - -# Workflow reutilizável para criação de tags -# Consolidação de lógica para evitar duplicação -# Suporta tags de release (production) e beta (staging) - -on: - workflow_call: - inputs: - tag-type: - description: "Tipo da tag: 'release' ou 'beta'" - required: true - type: string - version: - description: "Versão a ser tagueada (ex: 1.1.0)" - required: true - type: string - commit-sha: - description: "SHA do commit a ser tagueado" - required: true - type: string - create-version-commit: - description: "Se deve criar commit de version bump" - required: false - type: boolean - default: false - outputs: - tag-name: - description: "Nome da tag criada" - value: ${{ jobs.create-tag.outputs.tag_name }} - tag-created: - description: "Se a tag foi criada com sucesso" - value: ${{ jobs.create-tag.outputs.tag_created }} - -jobs: - create-tag: - name: Create Git Tag - runs-on: ubuntu-latest - permissions: - contents: write - outputs: - tag_name: ${{ steps.create_tag.outputs.tag_name }} - tag_created: ${{ steps.create_tag.outputs.tag_created }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - ref: ${{ inputs.commit-sha }} - fetch-depth: 0 - - - name: Setup pnpm - if: inputs.create-version-commit - uses: pnpm/action-setup@v4 - - - name: Setup Node.js - if: inputs.create-version-commit - uses: actions/setup-node@v4 - with: - node-version: "22.x" - cache: "pnpm" - - - name: Validate inputs - run: | - VERSION="${{ inputs.version }}" - TAG_TYPE="${{ inputs.tag-type }}" - - # Validar tipo de tag - if [[ "$TAG_TYPE" != "release" && "$TAG_TYPE" != "beta" ]]; then - echo "::error::Invalid tag type: $TAG_TYPE. Must be 'release' or 'beta'." - exit 1 - fi - - # Validar formato de versão (básico) - if [[ -z "$VERSION" ]]; then - echo "::error::Version cannot be empty." - exit 1 - fi - - echo "✅ Input validation passed" - - - name: Prepare tag information - id: prepare_tag - run: | - VERSION="${{ inputs.version }}" - TAG_TYPE="${{ inputs.tag-type }}" - - # Remover prefixo release/ se existir - VERSION=$(echo "$VERSION" | sed 's|^release/||') - - if [ "$TAG_TYPE" = "beta" ]; then - TAG_NAME="v${VERSION}-beta.${{ github.run_id }}" - TAG_MESSAGE="Beta Release ${TAG_NAME}" - else - TAG_NAME="v${VERSION}" - TAG_MESSAGE="Release ${TAG_NAME}" - fi - - echo "TAG_NAME=$TAG_NAME" >> $GITHUB_ENV - echo "TAG_MESSAGE=$TAG_MESSAGE" >> $GITHUB_ENV - echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT - echo "📦 Prepared tag: $TAG_NAME" - - - name: Check if tag already exists - id: check_tag - run: | - git fetch --tags - if git rev-parse "refs/tags/${{ env.TAG_NAME }}" >/dev/null 2>&1; then - echo "::warning::Tag ${{ env.TAG_NAME }} already exists. Skipping tag creation." - echo "tag_exists=true" >> $GITHUB_OUTPUT - echo "tag_created=false" >> $GITHUB_OUTPUT - else - echo "Tag ${{ env.TAG_NAME }} does not exist. Proceeding with tag creation." - echo "tag_exists=false" >> $GITHUB_OUTPUT - echo "tag_created=true" >> $GITHUB_OUTPUT - fi - - - name: Update package.json version - if: inputs.create-version-commit && steps.check_tag.outputs.tag_exists == 'false' - run: | - git config --global user.name "github-actions[bot]" - git config --global user.email "github-actions[bot]@users.noreply.github.com" - - # Extrair apenas a versão sem o prefixo beta - VERSION=$(echo "${{ env.TAG_NAME }}" | sed 's/^v//') - - pnpm version "$VERSION" --no-git-tag-version - git add package.json pnpm-lock.yaml - git commit -m "chore: bump version to $VERSION [skip ci]" - git push - - - name: Create Git tag - id: create_tag - if: steps.check_tag.outputs.tag_exists == 'false' - run: | - git config --global user.name "github-actions[bot]" - git config --global user.email "github-actions[bot]@users.noreply.github.com" - - git tag "${{ env.TAG_NAME }}" -m "${{ env.TAG_MESSAGE }}" - git push origin "${{ env.TAG_NAME }}" - - echo "✅ Tag ${{ env.TAG_NAME }} created successfully!" - - - name: Summary - if: always() - run: | - if [ "${{ steps.check_tag.outputs.tag_exists }}" = "true" ]; then - echo "ℹ️ Tag ${{ env.TAG_NAME }} already exists. No action taken." >> $GITHUB_STEP_SUMMARY - else - echo "✅ Tag ${{ env.TAG_NAME }} created successfully!" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Tag Details:**" >> $GITHUB_STEP_SUMMARY - echo "- Name: \`${{ env.TAG_NAME }}\`" >> $GITHUB_STEP_SUMMARY - echo "- Type: \`${{ inputs.tag-type }}\`" >> $GITHUB_STEP_SUMMARY - echo "- Commit: \`${{ inputs.commit-sha }}\`" >> $GITHUB_STEP_SUMMARY - fi diff --git a/.github/workflows/reusable-deploy-vercel.yml b/.github/workflows/reusable-deploy-vercel.yml index a90a7c8..a4c0045 100644 --- a/.github/workflows/reusable-deploy-vercel.yml +++ b/.github/workflows/reusable-deploy-vercel.yml @@ -29,6 +29,10 @@ on: deployment_url: description: "URL do deployment no Vercel" value: ${{ jobs.deploy.outputs.deployment_url }} + # ✅ MÁGICA: Exportando o resultado do filtro + code-changed: + description: "Indica se o deploy realmente rodou" + value: ${{ jobs.deploy.outputs.code-changed }} jobs: deploy: @@ -36,6 +40,7 @@ jobs: runs-on: ubuntu-latest outputs: deployment_url: ${{ steps.deploy_step.outputs.deployment_url }} + code-changed: ${{ steps.filter.outputs.code }} environment: name: ${{ inputs.environment }} url: ${{ steps.deploy_step.outputs.deployment_url }} @@ -49,6 +54,7 @@ jobs: with: fetch-depth: 0 + # ✅ MÁGICA: O paths-filter ESTÁ DE VOLTA - name: Check for code changes id: filter uses: dorny/paths-filter@v3 @@ -64,6 +70,11 @@ jobs: - 'eslint.config.mjs' - '.github/workflows/**' + - name: Set code-changed output + id: set-output + run: echo "code-changed=${{ steps.filter.outputs.code }}" >> $GITHUB_OUTPUT + + # ✅ MÁGICA: Todos os steps de deploy agora dependem do filtro - name: Install Vercel CLI if: steps.filter.outputs.code == 'true' run: npm install --global vercel@latest @@ -71,7 +82,9 @@ jobs: - name: Pull Vercel Environment Information if: steps.filter.outputs.code == 'true' run: | - vercel pull --yes --environment=${{ inputs.vercel-environment }} --token=${{ secrets.vercel-token }} + # Correção do case-sensitive (Preview vs preview) + ENV_NAME=$(echo "${{ inputs.vercel-environment }}" | tr '[:upper:]' '[:lower:]') + vercel pull --yes --environment=$ENV_NAME --token=${{ secrets.vercel-token }} - name: Ensure Vercel cache directory exists if: steps.filter.outputs.code == 'true' @@ -105,11 +118,6 @@ jobs: exit $DEPLOY_STATUS fi - - name: Report Skipped Deployment - if: steps.filter.outputs.code != 'true' - run: | - echo "Deployment skipped: No code changes detected." - - name: Upload debug logs on failure if: failure() uses: actions/upload-artifact@v4 @@ -126,4 +134,4 @@ jobs: /tmp/vercel-* /tmp/node-* /tmp/npm-* - /tmp/pnpm-* + /tmp/pnpm-* \ No newline at end of file diff --git a/.github/workflows/reusable-release.yml b/.github/workflows/reusable-release.yml deleted file mode 100644 index 6ad0351..0000000 --- a/.github/workflows/reusable-release.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: Reusable Release - -on: - workflow_call: - inputs: - version: - required: true - type: string - node-version: - required: true - type: string - -jobs: - release: - name: Create Git Tag and Bump Version - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: ${{ inputs.node-version }} - cache: "pnpm" - - - name: Get version - id: get_version - run: | - echo "VERSION=${{ inputs.version }}" >> $GITHUB_ENV - echo "TAG_VERSION=v${{ inputs.version }}" >> $GITHUB_ENV - - - name: Check if tag already exists - id: check_tag - run: | - git fetch --tags - if git rev-parse "refs/tags/${{ env.TAG_VERSION }}" >/dev/null 2>&1; then - echo "Tag ${{ env.TAG_VERSION }} already exists. Skipping release." - echo "SKIP_ALL=true" >> $GITHUB_OUTPUT - else - echo "Tag ${{ env.TAG_VERSION }} does not exist. Proceeding with release." - echo "SKIP_ALL=false" >> $GITHUB_OUTPUT - fi - - - name: Update version and create tag - if: steps.check_tag.outputs.SKIP_ALL == 'false' - run: | - git config --global user.name "github-actions[bot]" - git config --global user.email "github-actions[bot]@users.noreply.github.com" - pnpm version ${{ env.VERSION }} --no-git-tag-version - git add package.json pnpm-lock.yaml - git commit -m "chore: Bump version to ${{ env.VERSION }}" - git tag ${{ env.TAG_VERSION }} -m "Release ${{ env.TAG_VERSION }}" - git push - git push origin ${{ env.TAG_VERSION }} diff --git a/tutorial-github-actions-schema.md b/tutorial-github-actions-schema.md new file mode 100644 index 0000000..9209adb --- /dev/null +++ b/tutorial-github-actions-schema.md @@ -0,0 +1,214 @@ +# Tutorial: Schema do GitHub Actions - Entendendo Workflows + +## 🎯 Introdução + +Este tutorial explica como funciona o **schema do GitHub Actions**, com foco em erros comuns e melhores práticas. Baseado em experiências reais de debugging. + +## 📋 O que é o Schema do GitHub Actions? + +O schema define **regras específicas** para escrever workflows YAML. Diferente do YAML comum, o GitHub Actions tem **sintaxe própria** para certas funcionalidades. + +### ❌ Erro Comum: YAML válido ≠ Workflow válido + +```yaml +# ❌ YAML válido, mas ERRO no GitHub Actions +if: "always() && !contains(github.event.head_commit.message, 'chore(release)')" + +# ✅ Correto no GitHub Actions +if: always() && !contains(github.event.head_commit.message, 'chore(release)') +``` + +## 🔧 Condições `if` - A Armadilha Mais Comum + +### Regras Fundamentais + +1. **Não use aspas** em expressões com funções +2. **Aspas só** para strings literais +3. **Funções** como `always()`, `contains()`, `startsWith()` não precisam de aspas + +### Exemplos Práticos + +#### ✅ Correto + +```yaml +# Funções sem aspas +if: always() +if: contains(github.event.head_commit.message, 'fix') +if: startsWith(github.event.ref, 'refs/tags/') + +# Combinações +if: always() && !contains(github.event.head_commit.message, 'chore(release)') +if: github.event_name == 'pull_request' && github.event.action == 'opened' + +# Com variáveis +if: needs.test-job.outputs.success == 'true' +``` + +#### ❌ Errado + +```yaml +# Aspas desnecessárias +if: "always()" +if: "contains(github.event.head_commit.message, 'fix')" + +# Aspas em combinações (QUEBRAM tudo) +if: "always() && !contains(github.event.head_commit.message, 'chore(release)')" +``` + +### Por que isso acontece? + +- GitHub Actions trata aspas como **strings literais** +- `"always()"` vira uma string, não uma função +- O parser espera uma **expressão booleana**, não uma string + +## 🏗️ Estrutura de um Workflow + +### Jobs e Dependências + +```yaml +jobs: + job-a: + runs-on: ubuntu-latest + steps: + - run: echo "Job A" + + job-b: + needs: job-a # ✅ Correto: referência simples + if: needs.job-a.result == 'success' # ✅ Correto: expressão + runs-on: ubuntu-latest +``` + +### Outputs entre Jobs + +```yaml +jobs: + test: + outputs: + code-changed: ${{ steps.filter.outputs.code }} + steps: + - id: filter + run: echo "code=true" >> $GITHUB_OUTPUT + + deploy: + needs: test + if: needs.test.outputs.code-changed == 'true' # ✅ Correto + runs-on: ubuntu-latest +``` + +## 🎭 Contextos e Variáveis + +### Contextos Disponíveis + +- `github.*` - Informações do evento +- `env.*` - Variáveis de ambiente +- `vars.*` - Variáveis do repositório +- `secrets.*` - Segredos +- `needs.*` - Outputs de jobs +- `steps.*` - Outputs de steps + +### Exemplos de Uso + +```yaml +# Contexto github +if: github.event_name == 'pull_request' +if: github.base_ref == 'main' + +# Contexto needs +if: needs.build.result == 'success' + +# Contexto env +if: env.NODE_ENV == 'production' +``` + +## 🔄 Estratégias de Debugging + +### Checklist Sistemático + +1. **Sintaxe YAML básica** + + ```bash + pnpm lint:yaml + ``` + +2. **Validação de estrutura** + + ```bash + python3 -c "import yaml; yaml.safe_load(open('workflow.yml'))" + ``` + +3. **Teste de expressões** + + - Verifique condições `if` sem aspas + - Teste funções uma por vez + - Use `always()` para debug + +4. **Validação no GitHub** + - Push e veja se workflow roda + - Verifique logs de erro específicos + +### Erros Comuns e Soluções + +| Erro | Causa | Solução | +| ------------------------------------ | ---------------------- | -------------------------- | +| `Unexpected symbol: '"always'` | Aspas em funções | Remova aspas | +| `needs.job-a.outputs is not defined` | Job não tem outputs | Defina outputs no job | +| `contains is not defined` | Função não reconhecida | Use `contains()` sem aspas | +| Workflow não dispara | Problema no `on:` | Verifique triggers | + +## 🚀 Melhores Práticas + +### 1. Teste Incremental + +```yaml +# Comece simples +if: always() + +# Adicione complexidade gradualmente +if: always() && github.event_name == 'push' + +# Teste final +if: always() && !contains(github.event.head_commit.message, 'skip') +``` + +### 2. Use IDs em Steps + +```yaml +steps: + - id: test + run: echo "success=true" >> $GITHUB_OUTPUT + + - name: Deploy + if: steps.test.outputs.success == 'true' + run: echo "Deploying..." +``` + +### 3. Valide Sempre + +- Use `pnpm lint:yaml` antes de commitar +- Teste workflows em branches separadas +- Leia logs de erro com atenção + +### 4. Documente Lógica Complexa + +```yaml +# ❌ Sem comentário +if: always() && !contains(github.event.head_commit.message, 'chore(release)') + +# ✅ Com explicação +# Sempre rode, mas pule se for commit de release automático +if: always() && !contains(github.event.head_commit.message, 'chore(release)') +``` + +## 🎯 Conclusão + +O schema do GitHub Actions é **poderoso mas rigoroso**. Os erros mais comuns vêm de: + +1. **Aspas desnecessárias** em condições `if` +2. **Sintaxe incorreta** de funções +3. **Referências erradas** a contextos + +**Lembre-se:** Debugging sistemático > Tentativa e erro! + +--- + +_Baseado em experiências reais de debugging de workflows complexos._ From 0bd646d0c90bd5b9031b409377622a08363ac468 Mon Sep 17 00:00:00 2001 From: Matheus Henrique Caiser Barrozo Date: Fri, 7 Nov 2025 10:17:20 -0300 Subject: [PATCH 3/3] Revert "Merge pull request #109 from JaegerCaiser/docs/blog-post-ci-cd-saga" This reverts commit 1933a066155e02859d2955c039c5bf2e661586d7, reversing changes made to b983a4b72400e381a11111695a7672dd697a4221. --- src/content/blog/ci-cd-saga.mdx | 151 --------------------------- src/content/blog/merge-vs-rebase.mdx | 71 ------------- 2 files changed, 222 deletions(-) delete mode 100644 src/content/blog/ci-cd-saga.mdx delete mode 100644 src/content/blog/merge-vs-rebase.mdx 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***