Repository navigation
Análise de Estrutura do Projeto #13
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: "Análise de Estrutura do Projeto" | |
| on: | |
| push: | |
| branches: [main, develop] | |
| paths: | |
| - "app/src/**" | |
| - "supabase/**" | |
| pull_request: | |
| branches: [main, develop] | |
| paths: | |
| - "app/src/**" | |
| - "supabase/**" | |
| schedule: | |
| - cron: "0 8 * * 1" # toda segunda às 8h UTC | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| issues: write | |
| pull-requests: write | |
| jobs: | |
| analyze-structure: | |
| name: Análise de Estrutura | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Verificar estrutura esperada | |
| id: structure | |
| run: | | |
| WARNINGS="" | |
| ERRORS="" | |
| INFO="" | |
| echo "🔍 Analisando estrutura do projeto..." | |
| # --- Verificar diretórios obrigatórios --- | |
| REQUIRED_DIRS=( | |
| "app/src/components" | |
| "app/src/pages" | |
| "app/src/services" | |
| "app/src/contexts" | |
| "app/src/routes" | |
| "app/src/types" | |
| "app/src/utils" | |
| "app/src/layout" | |
| "app/public" | |
| "supabase" | |
| ) | |
| for dir in "${REQUIRED_DIRS[@]}"; do | |
| if [ ! -d "$dir" ]; then | |
| if [ "$dir" = "app/public" ]; then | |
| WARNINGS+="⚠️ Diretório recomendado ausente: \`$dir\`\n" | |
| else | |
| WARNINGS+="⚠️ Diretório ausente: \`$dir\`\n" | |
| fi | |
| fi | |
| done | |
| # --- Verificar arquivos críticos --- | |
| REQUIRED_FILES=( | |
| "app/src/App.tsx" | |
| "app/src/main.tsx" | |
| "app/package.json" | |
| "app/tsconfig.json" | |
| "app/vite.config.ts" | |
| "app/index.html" | |
| "app/env.example" | |
| ) | |
| for file in "${REQUIRED_FILES[@]}"; do | |
| if [ ! -f "$file" ]; then | |
| ERRORS+="❌ Arquivo crítico ausente: \`$file\`\n" | |
| fi | |
| done | |
| # --- Verificar arquivos de config na raiz --- | |
| ROOT_FILES=(".gitignore" "README.md") | |
| for file in "${ROOT_FILES[@]}"; do | |
| if [ ! -f "$file" ]; then | |
| WARNINGS+="⚠️ Arquivo recomendado ausente na raiz: \`$file\`\n" | |
| fi | |
| done | |
| # --- Contar arquivos por tipo --- | |
| TSX_COUNT=$(find app/src -name "*.tsx" 2>/dev/null | wc -l) | |
| TS_COUNT=$(find app/src -name "*.ts" 2>/dev/null | wc -l) | |
| CSS_COUNT=$(find app/src -name "*.css" 2>/dev/null | wc -l) | |
| SQL_COUNT=$(find supabase -name "*.sql" 2>/dev/null | wc -l) | |
| INFO+="### 📊 Métricas do Projeto\n\n" | |
| INFO+="| Tipo | Quantidade |\n" | |
| INFO+="|------|------------|\n" | |
| INFO+="| Componentes (.tsx) | $TSX_COUNT |\n" | |
| INFO+="| TypeScript (.ts) | $TS_COUNT |\n" | |
| INFO+="| CSS (.css) | $CSS_COUNT |\n" | |
| INFO+="| Arquivos SQL | $SQL_COUNT |\n\n" | |
| # --- Verificar arquivos grandes (>300 linhas) --- | |
| LARGE_FILES="" | |
| while IFS= read -r file; do | |
| if [ -f "$file" ]; then | |
| LINES=$(wc -l < "$file") | |
| if [ "$LINES" -gt 300 ]; then | |
| LARGE_FILES+="⚠️ \`$file\` — $LINES linhas (considerar dividir)\n" | |
| fi | |
| fi | |
| done < <(find app/src -name "*.tsx" -o -name "*.ts" 2>/dev/null) | |
| if [ -n "$LARGE_FILES" ]; then | |
| WARNINGS+="\n### 📏 Arquivos Grandes (>300 linhas)\n\n$LARGE_FILES\n" | |
| fi | |
| # --- Verificar imports circulares potenciais --- | |
| BARREL_EXPORTS=$(find app/src -name "index.ts" -o -name "index.tsx" 2>/dev/null | wc -l) | |
| if [ "$BARREL_EXPORTS" -gt 10 ]; then | |
| WARNINGS+="⚠️ Muitos barrel exports ($BARREL_EXPORTS index files) — risco de imports circulares\n" | |
| fi | |
| # --- Verificar se .env está no repo --- | |
| if [ -f "app/.env" ]; then | |
| ERRORS+="❌ **SEGURANÇA:** \`app/.env\` está no repositório! Adicione ao .gitignore\n" | |
| fi | |
| if [ -f ".env" ]; then | |
| ERRORS+="❌ **SEGURANÇA:** \`.env\` está no repositório! Adicione ao .gitignore\n" | |
| fi | |
| # --- Verificar dependências esperadas --- | |
| if [ -f "app/package.json" ]; then | |
| HAS_SUPABASE=$(grep -c "supabase" app/package.json || true) | |
| HAS_REACT_ROUTER=$(grep -c "react-router" app/package.json || true) | |
| HAS_TAILWIND=$(grep -c "tailwindcss" app/package.json || true) | |
| if [ "$HAS_SUPABASE" -eq 0 ]; then | |
| WARNINGS+="⚠️ \`@supabase/supabase-js\` não encontrado no package.json\n" | |
| fi | |
| if [ "$HAS_REACT_ROUTER" -eq 0 ]; then | |
| WARNINGS+="⚠️ \`react-router-dom\` não encontrado no package.json\n" | |
| fi | |
| if [ "$HAS_TAILWIND" -eq 0 ]; then | |
| WARNINGS+="⚠️ \`tailwindcss\` não encontrado no package.json\n" | |
| fi | |
| fi | |
| # --- Verificar estrutura Supabase --- | |
| if [ -d "supabase" ]; then | |
| SQL_COUNT=$(ls supabase/*.sql 2>/dev/null | wc -l) | |
| INFO+="### 🗃️ Arquivos Supabase\n\n" | |
| if [ "$SQL_COUNT" -gt 0 ]; then | |
| INFO+="| Arquivo | Status |\n" | |
| INFO+="|---------|--------|\n" | |
| for f in supabase/*.sql; do | |
| [ -f "$f" ] || continue | |
| NAME=$(basename "$f") | |
| INFO+="| ✅ | \`$NAME\` |\n" | |
| done | |
| INFO+="\n" | |
| else | |
| WARNINGS+="⚠️ Nenhum arquivo SQL encontrado em \`supabase/\`\n" | |
| fi | |
| fi | |
| # --- Verificar páginas principais --- | |
| EXPECTED_PAGES=("LoginPage" "DashboardPage" "GoalsPage" "ProjectsPage") | |
| MISSING_PAGES="" | |
| for page in "${EXPECTED_PAGES[@]}"; do | |
| if [ ! -f "app/src/pages/${page}.tsx" ]; then | |
| MISSING_PAGES+="⚠️ Página principal ausente: \`${page}.tsx\`\n" | |
| fi | |
| done | |
| if [ -n "$MISSING_PAGES" ]; then | |
| WARNINGS+="\n### 📄 Páginas Principais\n\n$MISSING_PAGES\n" | |
| fi | |
| # --- Verificar services esperados --- | |
| EXPECTED_SERVICES=("supabase" "goals") | |
| MISSING_SERVICES="" | |
| for svc in "${EXPECTED_SERVICES[@]}"; do | |
| if [ ! -f "app/src/services/${svc}.ts" ]; then | |
| MISSING_SERVICES+="⚠️ Service esperado ausente: \`${svc}.ts\`\n" | |
| fi | |
| done | |
| if [ -n "$MISSING_SERVICES" ]; then | |
| WARNINGS+="\n### 🔧 Services Esperados\n\n$MISSING_SERVICES\n" | |
| fi | |
| # --- Montar resultado final --- | |
| HAS_ERRORS="false" | |
| if [ -n "$ERRORS" ]; then | |
| HAS_ERRORS="true" | |
| fi | |
| HAS_WARNINGS="false" | |
| if [ -n "$WARNINGS" ]; then | |
| HAS_WARNINGS="true" | |
| fi | |
| echo "has_errors=$HAS_ERRORS" >> $GITHUB_OUTPUT | |
| echo "has_warnings=$HAS_WARNINGS" >> $GITHUB_OUTPUT | |
| BODY="## 🏗️ Análise de Estrutura — Virex\n\n" | |
| BODY+="**Branch:** \`${{ github.ref_name }}\`\n" | |
| BODY+="**Data:** $(date '+%Y-%m-%d %H:%M UTC')\n\n" | |
| BODY+="---\n\n" | |
| BODY+="$INFO" | |
| if [ -n "$ERRORS" ]; then | |
| BODY+="### ❌ Erros Críticos\n\n$ERRORS\n" | |
| fi | |
| if [ -n "$WARNINGS" ]; then | |
| BODY+="### ⚠️ Avisos e Melhorias\n\n$WARNINGS\n" | |
| fi | |
| if [ -z "$ERRORS" ] && [ -z "$WARNINGS" ]; then | |
| BODY+="### ✅ Tudo OK!\n\nA estrutura do projeto está alinhada com a arquitetura definida.\n" | |
| fi | |
| BODY+="\n---\n" | |
| BODY+="*Análise automática baseada na estrutura do projeto Virex*" | |
| EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) | |
| echo "body<<$EOF" >> $GITHUB_OUTPUT | |
| echo -e "$BODY" >> $GITHUB_OUTPUT | |
| echo "$EOF" >> $GITHUB_OUTPUT | |
| - name: Criar/Atualizar Issue de Estrutura | |
| if: (steps.structure.outputs.has_errors == 'true' || steps.structure.outputs.has_warnings == 'true') && github.event_name != 'pull_request' | |
| env: | |
| STRUCTURE_BODY: ${{ toJSON(steps.structure.outputs.body) }} | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const body = JSON.parse(process.env.STRUCTURE_BODY); | |
| const existingIssues = await github.rest.issues.listForRepo({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| state: 'open', | |
| labels: 'structure,ci-automated' | |
| }); | |
| const title = '🏗️ Análise de Estrutura — Pendências Detectadas'; | |
| const structureIssue = existingIssues.data.find( | |
| i => i.title.includes('Análise de Estrutura') | |
| ); | |
| if (structureIssue) { | |
| await github.rest.issues.update({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: structureIssue.number, | |
| body | |
| }); | |
| console.log(`Issue #${structureIssue.number} atualizada`); | |
| } else { | |
| const issue = await github.rest.issues.create({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| title, | |
| body, | |
| labels: ['structure', 'ci-automated'] | |
| }); | |
| console.log(`Issue #${issue.data.number} criada`); | |
| } | |
| - name: Comentar no PR | |
| if: github.event_name == 'pull_request' | |
| env: | |
| STRUCTURE_BODY: ${{ toJSON(steps.structure.outputs.body) }} | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const body = JSON.parse(process.env.STRUCTURE_BODY); | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body | |
| }); | |
| - name: Fechar issue se tudo OK | |
| if: steps.structure.outputs.has_errors == 'false' && steps.structure.outputs.has_warnings == 'false' && github.event_name != 'pull_request' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const issues = await github.rest.issues.listForRepo({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| state: 'open', | |
| labels: 'structure,ci-automated' | |
| }); | |
| for (const issue of issues.data) { | |
| if (issue.title.includes('Análise de Estrutura')) { | |
| await github.rest.issues.update({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issue.number, | |
| state: 'closed' | |
| }); | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issue.number, | |
| body: '✅ **Estrutura OK!** Todas as pendências foram resolvidas.' | |
| }); | |
| } | |
| } | |
| - name: Resumo | |
| run: | | |
| echo "📋 Análise de estrutura concluída" | |
| echo "Erros: ${{ steps.structure.outputs.has_errors }}" | |
| echo "Avisos: ${{ steps.structure.outputs.has_warnings }}" |