name: Build and Push Frontend on: push: branches: - main paths: # Código y configuración real del frontend. # Los cambios exclusivos de GitOps (frontend.yaml, ingress, patches, etc.) # no vuelven a construir la imagen. - 'workloads/ecommerce/Dockerfile' - 'workloads/ecommerce/.dockerignore' - 'workloads/ecommerce/.npmrc' - 'workloads/ecommerce/package.json' - 'workloads/ecommerce/package-lock.json' - 'workloads/ecommerce/next.config.*' - 'workloads/ecommerce/tsconfig.json' - 'workloads/ecommerce/tailwind.config.*' - 'workloads/ecommerce/postcss.config.*' - 'workloads/ecommerce/eslint.config.*' - 'workloads/ecommerce/*.js' - 'workloads/ecommerce/*.mjs' - 'workloads/ecommerce/*.ts' - 'workloads/ecommerce/*.tsx' - 'workloads/ecommerce/app/**' - 'workloads/ecommerce/src/**' - 'workloads/ecommerce/components/**' - 'workloads/ecommerce/lib/**' - 'workloads/ecommerce/public/**' - 'workloads/ecommerce/styles/**' - '.gitea/workflows/build.yaml' permissions: contents: write packages: write jobs: build: name: Construir y Subir Imagen runs-on: ubuntu-latest timeout-minutes: 20 env: APP_DIR: workloads/ecommerce MANIFEST_FILE: workloads/ecommerce/frontend.yaml IMAGE_NAME: gitea.cruzcloud.net/devops/ecommerce-frontend steps: - name: Checkout del código uses: actions/checkout@v3 with: fetch-depth: 1 persist-credentials: true # Conservamos el esquema estable v1.0.X basado en el número del pipeline. - name: Definir Versión Semántica id: vars shell: bash run: | set -euo pipefail echo "VERSION=v1.0.${{ github.run_number }}" >> "$GITHUB_OUTPUT" - name: Validar secretos del Registry shell: bash env: REGISTRY_USER: ${{ secrets.REGISTRY_USER }} REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} run: | set -euo pipefail test -n "${REGISTRY_USER}" || { echo "ERROR: REGISTRY_USER no está configurado." exit 1 } test -n "${REGISTRY_PASSWORD}" || { echo "ERROR: REGISTRY_PASSWORD no está configurado." exit 1 } echo "OK: secretos del Registry disponibles." # Evita que archivos de ejemplo entren en la compilación de Next.js. # Se eliminan únicamente del workspace temporal del runner. - name: Preparar contexto de producción shell: bash run: | set -euo pipefail test -f "${APP_DIR}/Dockerfile" test -f "${APP_DIR}/package.json" test -f "${APP_DIR}/package-lock.json" test -f "${MANIFEST_FILE}" rm -rf "${APP_DIR}/integration" find "${APP_DIR}" -type f \( -name '*.example.ts' -o -name '*.example.tsx' \) -delete echo "Contexto listo:" du -sh "${APP_DIR}" # Diagnóstico temprano para el hotfix de navegación. # No modifica el frontend, pero evita publicar silenciosamente un menú # que existe y no está conectado al Header/Layout real. - name: Validar integración del menú de categorías shell: bash run: | set -euo pipefail MENU_FILE="$( find "${APP_DIR}" -type f -name 'CategoryMenu.tsx' -print -quit )" if [ -z "${MENU_FILE}" ]; then echo "AVISO: no se encontró CategoryMenu.tsx." echo "El build continuará usando el menú actualmente integrado." exit 0 fi echo "CategoryMenu encontrado: ${MENU_FILE}" if ! grep -Eq 'useSearchParams|URLSearchParams|window\.location\.search' "${MENU_FILE}"; then echo "ERROR: CategoryMenu.tsx no deriva el estado activo desde la URL." exit 1 fi if ! grep -q 'collection=Marvel' "${MENU_FILE}"; then echo "ERROR: falta collection=Marvel para Acción y aventura." exit 1 fi if ! grep -q 'category=Mochilas' "${MENU_FILE}"; then echo "ERROR: falta category=Mochilas." exit 1 fi MENU_USAGE="$( grep -Rsl --include='*.tsx' --include='*.ts' 'CategoryMenu' "${APP_DIR}/app" "${APP_DIR}/src" "${APP_DIR}/components" 2>/dev/null | grep -Fvx "${MENU_FILE}" | head -n 1 || true )" if [ -z "${MENU_USAGE}" ]; then echo "ERROR: CategoryMenu.tsx existe, pero ningún Header/Layout lo importa." echo "Debe renderizarse en el encabezado que ve el usuario." exit 1 fi echo "CategoryMenu integrado desde: ${MENU_USAGE}" echo "Validación del menú: OK" - name: Login en Gitea Registry uses: docker/login-action@v2 with: registry: gitea.cruzcloud.net username: ${{ secrets.REGISTRY_USER }} password: ${{ secrets.REGISTRY_PASSWORD }} logout: true - name: Construir y Subir Imagen uses: docker/build-push-action@v4 with: context: workloads/ecommerce/ push: true tags: | gitea.cruzcloud.net/devops/ecommerce-frontend:${{ steps.vars.outputs.VERSION }} gitea.cruzcloud.net/devops/ecommerce-frontend:latest # Evita que una ejecución antigua actualice frontend.yaml después de que # ya exista un commit más reciente en main. - name: Verificar promoción segura id: promotion shell: bash run: | set -euo pipefail git fetch origin main CURRENT_SHA="${{ github.sha }}" REMOTE_SHA="$(git rev-parse origin/main)" echo "Commit del pipeline: ${CURRENT_SHA}" echo "Último commit remoto: ${REMOTE_SHA}" if [ "${CURRENT_SHA}" = "${REMOTE_SHA}" ]; then echo "promote=true" >> "$GITHUB_OUTPUT" echo "La imagen puede promocionarse." else echo "promote=false" >> "$GITHUB_OUTPUT" echo "La imagen fue publicada, pero no se actualizará el manifiesto." echo "Existe un commit más reciente y su pipeline debe realizar la promoción." fi - name: Actualizar Manifiesto GitOps (CD) if: steps.promotion.outputs.promote == 'true' shell: bash run: | set -euo pipefail VERSION="${{ steps.vars.outputs.VERSION }}" git config user.name "Cristian Felipe" git config user.email "cristiancruz0529@gmail.com" # Sincronizamos el workspace temporal con main. # No se fuerza ni se sobrescribe la rama remota. git fetch origin main git checkout -B main origin/main sed -i -E "s|(image: ${IMAGE_NAME}:).*|\1${VERSION}|g" "${MANIFEST_FILE}" git add "${MANIFEST_FILE}" if git diff --cached --quiet; then echo "No hay cambios en el manifiesto." exit 0 fi git commit -m "chore: release ${VERSION} [skip ci]" # Push normal: si main cambió durante el job, se detiene para evitar # sobrescribir commits ajenos o promocionar una versión antigua. git push origin HEAD:main - name: Resumen del pipeline if: always() shell: bash run: | echo "========================================" echo "ARI Shopping Frontend" echo "Versión: ${{ steps.vars.outputs.VERSION }}" echo "Commit: ${{ github.sha }}" echo "Promoción GitOps: ${{ steps.promotion.outputs.promote }}" echo "========================================"