feat: refine SEO indexing metadata #76
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: CI e imagem Alpine | |
| on: | |
| push: | |
| pull_request: | |
| permissions: | |
| contents: read | |
| packages: write | |
| concurrency: | |
| group: ci-image-${{ github.sha }} | |
| cancel-in-progress: false | |
| env: | |
| V_COMMIT: ee3ef57ffded3cf182930a8d64fe6fa7b7b2ce1e | |
| VC_COMMIT: 9871c172787d9622611188b75cb55773b1db2020 | |
| VEEMARKER_COMMIT: 1510ef5a7cbf980f2e075f02baada7190748e3f7 | |
| DOTENV_COMMIT: 1d9477c8b1a3f5ca14b2eb042c4e6d52449b75d4 | |
| V_STRIPE_COMMIT: 2c999a60f9569794f4f912e33b565501e8373420 | |
| LOCAL_IMAGE: tabua-mare-api:ci-${{ github.sha }} | |
| GHCR_IMAGE: ghcr.io/ddiidev/tabua-mare-api:sha-${{ github.sha }} | |
| DB_SQLITE_PATH: ${{ github.workspace }}/taubinha.sqlite | |
| POSTGRESQL_CONN_STR: postgresql://postgres:postgres@127.0.0.1:5432/tabuamare_ci | |
| RUN_POSTGRES_TEST: "1" | |
| jobs: | |
| test-build-smoke: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| services: | |
| postgres: | |
| image: postgres:16-alpine | |
| env: | |
| POSTGRES_DB: tabuamare_ci | |
| POSTGRES_USER: postgres | |
| POSTGRES_PASSWORD: postgres | |
| ports: | |
| - 5432:5432 | |
| options: >- | |
| --health-cmd "pg_isready -U postgres -d tabuamare_ci" | |
| --health-interval 2s | |
| --health-timeout 3s | |
| --health-retries 30 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Login GHCR | |
| if: github.event_name == 'push' | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Impedir reatribuicao da tag SHA | |
| if: github.event_name == 'push' | |
| run: | | |
| set +e | |
| manifest_error="$(docker manifest inspect "$GHCR_IMAGE" 2>&1 >/dev/null)" | |
| manifest_status=$? | |
| set -e | |
| if [[ "$manifest_status" -eq 0 ]]; then | |
| echo "::error::A tag imutavel $GHCR_IMAGE ja existe; reatribuicao recusada." | |
| exit 1 | |
| fi | |
| if ! grep -Eqi 'manifest unknown|not found' <<<"$manifest_error"; then | |
| echo "::error::Falha ao consultar GHCR; publicacao recusada sem confirmar ausencia da tag." | |
| exit 1 | |
| fi | |
| - name: Dependencias nativas do V | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y --no-install-recommends \ | |
| build-essential libgc-dev libpq-dev libssl-dev libsqlite3-dev | |
| - name: V 0.5.2, VC e modulos fixados | |
| run: | | |
| set -euo pipefail | |
| git clone https://github.com/vlang/v.git /tmp/v | |
| git -C /tmp/v checkout --detach "$V_COMMIT" | |
| git clone https://github.com/vlang/vc.git /tmp/v/vc | |
| git -C /tmp/v/vc checkout --detach "$VC_COMMIT" | |
| make -C /tmp/v fresh_tcc | |
| make -C /tmp/v local=1 | |
| test "$(git -C /tmp/v rev-parse HEAD)" = "$V_COMMIT" | |
| test "$(git -C /tmp/v/vc rev-parse HEAD)" = "$VC_COMMIT" | |
| /tmp/v/v version | grep -F 'V 0.5.2' | |
| mkdir -p "$HOME/.vmodules/leafscale" "$HOME/.vmodules/ken0x0a" | |
| git clone https://github.com/leafscale/veemarker.git "$HOME/.vmodules/leafscale/veemarker" | |
| git -C "$HOME/.vmodules/leafscale/veemarker" checkout --detach "$VEEMARKER_COMMIT" | |
| git clone https://github.com/ken0x0a/v-dotenv.git "$HOME/.vmodules/ken0x0a/dotenv" | |
| git -C "$HOME/.vmodules/ken0x0a/dotenv" checkout --detach "$DOTENV_COMMIT" | |
| git clone https://github.com/Ddiidev/v-stripe.git "$HOME/.vmodules/v_stripe" | |
| git -C "$HOME/.vmodules/v_stripe" checkout --detach "$V_STRIPE_COMMIT" | |
| echo '/tmp/v' >> "$GITHUB_PATH" | |
| - name: Testes V com PostgreSQL efemero | |
| run: /tmp/v/v test tests/ | |
| - name: Contratos shell e ciclo de vida | |
| run: | | |
| set -euo pipefail | |
| bash ./scripts/test_seed_sqlite.sh | |
| bash ./scripts/test_alpine_image_static.sh | |
| bash ./scripts/test_coolify_deploy.sh | |
| bash ./scripts/test_health_lifecycle.sh | |
| - name: Node.js e build dos assets CSS | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| - name: Gerar CSS minificado por rota | |
| run: | | |
| npm ci | |
| npm run css:build | |
| - name: Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Build unico linux/amd64 | |
| run: | | |
| docker buildx build \ | |
| --platform linux/amd64 \ | |
| --load \ | |
| --tag "$LOCAL_IMAGE" \ | |
| --tag "$GHCR_IMAGE" \ | |
| . | |
| - name: Verificar Alpine, arquitetura e bibliotecas | |
| run: | | |
| set -euo pipefail | |
| test "$(docker image inspect "$LOCAL_IMAGE" --format '{{.Architecture}}')" = amd64 | |
| docker run --rm --entrypoint sh "$LOCAL_IMAGE" -eu -c \ | |
| "grep -Eq '^3[.]22[.]' /etc/alpine-release" | |
| docker run --rm --entrypoint sh "$LOCAL_IMAGE" -eu -c ' | |
| apk add --no-cache pax-utils >/dev/null | |
| scanelf --needed --nobanner /app/TabuaMareAPI | |
| ldd /app/TabuaMareAPI | tee /tmp/ldd.txt | |
| ! grep -Fq "not found" /tmp/ldd.txt | |
| ' | |
| - name: Ambiente isolado do smoke A/B | |
| run: | | |
| cat > "$RUNNER_TEMP/tabuamare-ci.env" <<'ENV' | |
| POSTGRESQL_CONN_STR=postgresql://postgres:postgres@127.0.0.1:5432/tabuamare_ci | |
| GOOGLE_CLIENT_ID=ci-client | |
| GOOGLE_CLIENT_SECRET=ci-secret | |
| GOOGLE_REDIRECT_URI=https://tabuamare.api.br/auth/google/callback | |
| SESSION_SECRET=ci-only-session-secret-with-more-than-32-bytes | |
| STRIPE_SECRET_KEY=sk_test_ci | |
| STRIPE_WEBHOOK_SECRET=whsec_ci | |
| URL_ENV=http://localhost:3330 | |
| ENV | |
| - name: Smoke da imagem exata em A/B | |
| run: | | |
| set -euo pipefail | |
| env_file="$RUNNER_TEMP/tabuamare-ci.env" | |
| a_name="tabuamare-ci-a-${GITHUB_RUN_ID}" | |
| b_name="tabuamare-ci-b-${GITHUB_RUN_ID}" | |
| a_volume="tabuamare-ci-a-${GITHUB_RUN_ID}" | |
| b_volume="tabuamare-ci-b-${GITHUB_RUN_ID}" | |
| cleanup() { docker rm -f "$a_name" "$b_name" >/dev/null 2>&1 || true; docker volume rm "$a_volume" "$b_volume" >/dev/null 2>&1 || true; } | |
| trap cleanup EXIT | |
| docker volume create "$a_volume" >/dev/null | |
| docker volume create "$b_volume" >/dev/null | |
| wait_http() { local url="$1" expected="$2" code=000; for _ in $(seq 1 180); do code="$(curl -sS -o /dev/null -w '%{http_code}' "$url" 2>/dev/null || true)"; [[ "$code" == "$expected" ]] && return 0; sleep 1; done; echo "$url: recebido $code, esperado $expected" >&2; docker logs "$a_name" >&2 || true; docker logs "$b_name" >&2 || true; return 1; } | |
| docker run -d --name "$a_name" --network host --env-file "$env_file" -e PORT=3330 -v "$a_volume:/app/data" "$LOCAL_IMAGE" 3330 | |
| wait_http http://127.0.0.1:3330/health/ready 204 | |
| docker run -d --name "$b_name" --network host --env-file "$env_file" -e PORT=3340 -v "$b_volume:/app/data" "$LOCAL_IMAGE" 3340 | |
| wait_http http://127.0.0.1:3340/health/ready 204 | |
| wait_http http://127.0.0.1:3330/api/v2/states 200 | |
| wait_http http://127.0.0.1:3340/api/v2/states 200 | |
| [[ "$(docker inspect --format '{{.State.Status}}' "$a_name")" == running ]] | |
| [[ "$(docker inspect --format '{{.State.Status}}' "$b_name")" == running ]] | |
| echo 'PASS: smoke direto A/B, readiness e API v2' | |
| - name: Publicar tag imutavel | |
| if: github.event_name == 'push' | |
| run: docker push "$GHCR_IMAGE" | |
| - name: Confirmar acesso publico anonimo | |
| if: github.event_name == 'push' | |
| run: | | |
| docker logout ghcr.io | |
| if ! docker manifest inspect "$GHCR_IMAGE" >/dev/null 2>&1; then | |
| echo "::warning::Pacote GHCR ainda privado. Torne tabua-mare-api publico antes do deploy." | |
| echo '## Acao obrigatoria' >> "$GITHUB_STEP_SUMMARY" | |
| echo 'Tornar o pacote GHCR `tabua-mare-api` publico. O deploy permanece bloqueado enquanto o manifesto anonimo falhar.' >> "$GITHUB_STEP_SUMMARY" | |
| fi |