From 510272256faca6280245d0d5f32c230f12773030 Mon Sep 17 00:00:00 2001 From: robertocarlous Date: Mon, 15 Jun 2026 20:29:39 +0100 Subject: [PATCH] Add production build smoke CI workflow (closes #152) --- .github/workflows/production-smoke.yml | 81 ++++++++++++++++++++++++++ package.json | 1 + scripts/smoke-health.sh | 54 +++++++++++++++++ 3 files changed, 136 insertions(+) create mode 100644 .github/workflows/production-smoke.yml create mode 100755 scripts/smoke-health.sh diff --git a/.github/workflows/production-smoke.yml b/.github/workflows/production-smoke.yml new file mode 100644 index 0000000..e50eff6 --- /dev/null +++ b/.github/workflows/production-smoke.yml @@ -0,0 +1,81 @@ +# Issue #152 — Verify the production artifact builds and the server starts. +# +# Builds with dev dependencies, compiles TypeScript, reinstalls production-only +# dependencies (matching the Docker runtime image), then starts the built server +# and asserts GET /health returns 200. + +name: Production build smoke + +on: + push: + branches: + - main + - develop + pull_request: + types: [opened, synchronize, reopened] + +jobs: + production-smoke: + name: Production build smoke check + runs-on: ubuntu-latest + services: + postgres: + image: postgres:14.4 + env: + POSTGRES_USER: prod_smoke_user + POSTGRES_PASSWORD: prod_smoke_pass + POSTGRES_DB: prod_smoke_db + ports: + - 5432:5432 + options: >- + --health-cmd="pg_isready -U prod_smoke_user" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + env: + NODE_ENV: test + PORT: 3001 + DATABASE_URL: postgresql://prod_smoke_user:prod_smoke_pass@localhost:5432/prod_smoke_db + STELLAR_NETWORK: testnet + STELLAR_RPC_URL: https://soroban-testnet.stellar.org + STELLAR_AGENT_SECRET_KEY: SXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + VAULT_CONTRACT_ID: CDUMMYVAULTCONTRACTID + USDC_TOKEN_ADDRESS: CDUMMYUSDC + ANTHROPIC_API_KEY: smoke-anthropic-key + JWT_SEED: a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2 + JWT_SESSION_TTL_HOURS: '24' + JWT_NONCE_TTL_MS: '300000' + JWT_CLEANUP_INTERVAL_MS: '86400000' + WALLET_ENCRYPTION_KEY: a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2 + TWILIO_AUTH_TOKEN: smoke-twilio-token + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: npm + + - name: Install dependencies (build) + run: npm ci + + - name: Generate Prisma client + run: npx prisma generate + + - name: Apply database migrations + run: npx prisma migrate deploy + + - name: Build production artifact + run: npm run build + + - name: Install production dependencies only + run: npm ci --omit=dev + + - name: Regenerate Prisma client for production install + run: npx prisma@5.22.0 generate + + - name: Run startup smoke check (/health) + run: npm run smoke:health diff --git a/package.json b/package.json index 6a48653..d6708b8 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "dev": "nodemon", "build": "tsc", "start": "node dist/index.js", + "smoke:health": "bash scripts/smoke-health.sh", "lint": "npm run lint:types && npm run lint:style", "lint:types": "tsc --noEmit", "lint:style": "eslint \"src/**/*.ts\" \"prisma/**/*.ts\"", diff --git a/scripts/smoke-health.sh b/scripts/smoke-health.sh new file mode 100755 index 0000000..c7f1092 --- /dev/null +++ b/scripts/smoke-health.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# smoke-health.sh — Start the built server and verify GET /health returns 200. +# +# Exits non-zero if the server fails to start or /health does not respond in time. +# Used by the production build smoke CI workflow (issue #152). +# +# Prerequisites: +# - dist/index.js exists (npm run build) +# - production node_modules installed +# - DATABASE_URL and other required env vars set +# - migrations applied + +set -euo pipefail + +PORT="${PORT:-3001}" +BASE_URL="http://127.0.0.1:${PORT}" +HEALTH_PATH="${SMOKE_HEALTH_PATH:-/health}" +TIMEOUT_SEC="${SMOKE_TIMEOUT_SEC:-120}" +SERVER_PID="" + +cleanup() { + if [[ -n "${SERVER_PID}" ]] && kill -0 "${SERVER_PID}" 2>/dev/null; then + kill "${SERVER_PID}" 2>/dev/null || true + wait "${SERVER_PID}" 2>/dev/null || true + fi +} +trap cleanup EXIT + +if [[ ! -f dist/index.js ]]; then + echo "::error::dist/index.js not found — run npm run build first" + exit 1 +fi + +echo "[smoke] Starting server (node dist/index.js)..." +node dist/index.js & +SERVER_PID=$! + +deadline=$((SECONDS + TIMEOUT_SEC)) +until curl -sf "${BASE_URL}${HEALTH_PATH}" > /dev/null; do + if ! kill -0 "${SERVER_PID}" 2>/dev/null; then + echo "::error::Server exited before ${HEALTH_PATH} returned 200" + exit 1 + fi + if (( SECONDS >= deadline )); then + echo "::error::Timed out after ${TIMEOUT_SEC}s waiting for ${HEALTH_PATH}" + exit 1 + fi + sleep 2 +done + +body="$(curl -sf "${BASE_URL}${HEALTH_PATH}")" +echo "[smoke] ${HEALTH_PATH} → 200" +echo "[smoke] Response: ${body}" +echo "[smoke] ✓ Production startup smoke check passed"