deploy-web.sh · CULTIVA IA
Script de despliegue de la web Astro a Vercel — generado aplicando los 10 patrones defensivos de Bash.
CULTIVA IA
Web Astro → Vercel
macOS / GitHub Actions
PATRON 1
Strict Mode
set -Eeuo pipefailPATRON 2
Error Trapping
trap ERR + EXIT para cleanup automatico
PATRON 5
Logging estructurado
Timestamps ISO + niveles INFO/WARN/ERROR
PATRON 3
Variable Safety
Todas las variables entre comillas dobles
PATRON 10
Dry-run
--dry-run para simular sin aplicar cambios
PATRON 8
Idempotencia
Seguro de re-ejecutar; checks de estado previo
🔒
1 · Strict Mode & cabecera del script
Fundacional
Por que importa
Sin strict mode, Bash ignora errores silenciosamente. Con
set -Eeuo pipefail, cualquier comando fallido detiene el script inmediatamente, evitando que un deploy roto llegue a produccion.
deploy-web.sh
bash
#!/usr/bin/env bash # ═══════════════════════════════════════════════════════════════ # deploy-web.sh — Despliegue web Astro a Vercel · CULTIVA IA # Uso: ./deploy-web.sh [--dry-run] [--env production|preview] # Deps: node >=20, npm, vercel, git, curl # ═══════════════════════════════════════════════════════════════ set -Eeuo pipefail # E=heredar ERR en funciones | e=exit on error # u=error en var indefinida | pipefail=pipes SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" SCRIPT_NAME="$(basename -- "${BASH_SOURCE[0]}")"
🪤
2 · Error Trapping & Cleanup automatico
Patron 2 + 3
deploy-web.sh — traps
bash
# ── Archivos temporales ────────────────────────────────────── TMPDIR=$(mktemp -d) || { log_error "No se pudo crear tmpdir"; exit 1; } BUILD_LOG="$TMPDIR/build.log" DEPLOY_LOG="$TMPDIR/deploy.log" # ── Cleanup al salir (EXIT = siempre; ERR = solo en error) ─── cleanup() { local exit_code="$?" log_info "Limpiando archivos temporales..." rm -rf -- "$TMPDIR" if [[ $exit_code -ne 0 ]]; then log_error "Script terminado con error (exit $exit_code)" notify_slack "❌ Deploy FALLIDO en $GIT_BRANCH (exit $exit_code)" "danger" fi } on_error() { log_error "Error inesperado en linea $BASH_LINENO → comando: $BASH_COMMAND" } trap cleanup EXIT trap on_error ERR
📋
5 · Logging estructurado
Patron 5
deploy-web.sh — logging
bash
log_info() { printf '[%s] \033[32mINFO\033[0m %s\n' "$(date +'%Y-%m-%d %H:%M:%S')" "$*" >&2; } log_warn() { printf '[%s] \033[33mWARN\033[0m %s\n' "$(date +'%Y-%m-%d %H:%M:%S')" "$*" >&2; } log_error() { printf '[%s] \033[31mERROR\033[0m %s\n' "$(date +'%Y-%m-%d %H:%M:%S')" "$*" >&2; } log_debug() { [[ "${DEBUG:-0}" == "1" ]] && \ printf '[%s] \033[36mDEBUG\033[0m %s\n' "$(date +'%Y-%m-%d %H:%M:%S')" "$*" >&2 || true } # Salida de ejemplo en terminal: # [2026-06-16 09:34:12] INFO Iniciando deploy de CULTIVA IA web # [2026-06-16 09:34:13] INFO Build completado (47s) # [2026-06-16 09:34:15] WARN Variable SLACK_WEBHOOK_URL no configurada # [2026-06-16 09:34:18] ERROR Vercel token invalido
📦
Dep · Validacion de dependencias
command -v (seguro)
Usa
command -v, nunca which
which no es POSIX y puede fallar en algunas shells. command -v es el estandar correcto y funciona en macOS, Linux y CI.
deploy-web.sh — check_dependencies
bash
check_dependencies() { local -a required=("node" "npm" "vercel" "git" "curl") local -a missing=() for cmd in "${required[@]}"; do if ! command -v "$cmd" >/dev/null 2>&1; then missing+=("$cmd") fi done if [[ ${#missing[@]} -gt 0 ]]; then log_error "Dependencias faltantes: ${missing[*]}" log_error "Instala con: npm i -g vercel" return 1 fi # Verificar versiones minimas local node_ver node_ver=$(node --version | tr -d 'v' | cut -d. -f1) if [[ $node_ver -lt 20 ]]; then log_error "Se requiere Node >= 20, encontrado: $node_ver" return 1 fi log_info "Dependencias OK (node v$node_ver)" } # Requerir variables de entorno criticas : "${VERCEL_TOKEN:?La variable VERCEL_TOKEN es obligatoria}"
⚙️
4 · Parsing de argumentos robusto
Patron 4
deploy-web.sh — parse_args
bash
# Valores por defecto DRY_RUN=false DEPLOY_ENV="preview" SKIP_TESTS=false VERBOSE=false usage() { cat <<EOF Uso: $SCRIPT_NAME [OPCIONES] Opciones: -d, --dry-run Simular sin ejecutar cambios reales -e, --env ENV Entorno destino: production|preview [default: preview] --skip-tests Omitir suite de tests -v, --verbose Salida DEBUG detallada -h, --help Mostrar esta ayuda EOF exit "${1:-0}" } while [[ $# -gt 0 ]]; do case "$1" in -d|--dry-run) DRY_RUN=true; shift ;; -e|--env) DEPLOY_ENV="$2"; shift 2 ;; --skip-tests) SKIP_TESTS=true; shift ;; -v|--verbose) VERBOSE=true; DEBUG=1; shift ;; -h|--help) usage 0 ;; --) shift; break ;; *) log_error "Opcion desconocida: $1"; usage 1 ;; esac done # Validar enum if [[ ! "$DEPLOY_ENV" =~ ^(production|preview)$ ]]; then log_error "--env debe ser 'production' o 'preview', recibido: '$DEPLOY_ENV'" exit 1 fi
🔍
10 · Dry-run mode
Patron 10
deploy-web.sh — run_cmd + dry-run
bash
run_cmd() { if [[ "$DRY_RUN" == "true" ]]; then log_info "[DRY-RUN] Simularia: $*" return 0 fi log_debug "Ejecutando: $*" "$@" } # Uso en el script principal: build_project() { log_info "Iniciando build de Astro..." run_cmd npm run build 2> "$BUILD_LOG" log_info "Build completado — artefactos en ./dist" } deploy_to_vercel() { local vercel_flags="" [[ "$DEPLOY_ENV" == "production" ]] && vercel_flags="--prod" log_info "Desplegando a Vercel ($DEPLOY_ENV)..." run_cmd vercel deploy $vercel_flags --token "$VERCEL_TOKEN" --yes \ 2> "$DEPLOY_LOG" } # Con --dry-run la salida seria: # [2026-06-16 09:41:00] INFO [DRY-RUN] Simularia: npm run build # [2026-06-16 09:41:00] INFO [DRY-RUN] Simularia: vercel deploy --token *** --yes
🚀
Script completo — flujo principal
Todos los patrones integrados
deploy-web.sh — main()
bash
main() { log_info "═══ Deploy CULTIVA IA Web ═══ (env: $DEPLOY_ENV)" [[ "$DRY_RUN" == "true" ]] && log_warn "Modo DRY-RUN activo — no se haran cambios reales" # 1. Verificar dependencias check_dependencies # 2. Validar repo limpio (no commitear archivos sin staging) if [[ -n "$(git status --porcelain 2>/dev/null)" ]]; then log_warn "El repo tiene cambios sin commitear" [[ "$DEPLOY_ENV" == "production" ]] && { log_error "No se puede desplegar a produccion con cambios sin commitear" exit 1 } fi # 3. Capturar rama actual (idempotente: info de contexto) GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown") GIT_SHA=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") log_info "Rama: $GIT_BRANCH · commit: $GIT_SHA" # 4. Instalar dependencias (idempotente) log_info "Instalando dependencias npm..." run_cmd npm ci --prefer-offline # 5. Tests (opcional) if [[ "$SKIP_TESTS" == "false" ]] && [[ -f package.json ]] && \ grep -q '"test"' package.json; then log_info "Ejecutando tests..." run_cmd npm test else log_warn "Tests omitidos" fi # 6. Build (si falla, el trap ERR detiene todo — no se despliega) build_project # 7. Deploy DEPLOY_URL=$(deploy_to_vercel) log_info "Deploy OK → $DEPLOY_URL" # 8. Notificar notify_slack "✅ Deploy OK · $GIT_BRANCH@$GIT_SHA → $DEPLOY_ENV · $DEPLOY_URL" "good" log_info "═══ Deploy completado ═══" } main "$@"
📌
Cheatsheet — Referencia rapida
| Patron | Snippet | Aplica cuando | Nivel |
|---|---|---|---|
| Strict mode | set -Eeuo pipefail |
Siempre, linea 1 tras shebang | esencial |
| Cleanup trap | trap 'rm -rf "$TMP"' EXIT |
Cuando usas archivos temporales | esencial |
| Quoting | cp "$src" "$dst" |
Toda expansion de variable | esencial |
| Require var | : "${VAR:?mensaje}" |
Variables de entorno criticas | esencial |
| Check cmd | command -v node |
Antes de usar herramientas externas | esencial |
| Dry-run | run_cmd vercel deploy |
Cualquier operacion destructiva | recomendado |
| Logging | printf '[%s] INFO %s\n' "$(date)" "$*" |
Toda salida informativa | recomendado |
| Idempotencia | [[ -d "$dir" ]] || mkdir -p "$dir" |
Scripts que crean recursos | recomendado |
⛔
Anti-patrones — Lo que NO hacer
diff — anti-patron vs patron correcto
- #!/bin/bash # sin strict mode + #!/usr/bin/env bash + set -Eeuo pipefail - cp $source $dest # sin comillas → falla con espacios + cp "$source" "$dest" - which node # no POSIX, puede no estar en PATH + command -v node - TMPFILE=/tmp/mi-script-temp # colision entre instancias paralelas + TMPFILE=$(mktemp) - result=`comando` # backticks: no anidables, poco legibles + result=$(comando) - if [ "$var" == "valor" ] # [ ] no soporta ==, usar = + if [[ "$var" == "valor" ]] # [[ ]] es Bash-nativo y mas seguro - echo "Error: algo salio mal" # echo a stdout, se mezcla con output util + log_error "algo salio mal" # stderr con timestamp y nivel
✅
Checklist de revision para scripts de produccion
- Strict mode habilitado al inicio
- Todas las variables entre comillas dobles
- trap EXIT limpia recursos temporales
- trap ERR registra linea del error
- command -v verifica dependencias
- mktemp para archivos temporales
- Logging con timestamps a stderr
- --dry-run soportado para debugging
- Validacion de inputs antes de operar
- Idempotencia verificada en re-ejecuciones