Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions .github/workflows/production-smoke.yml
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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\"",
Expand Down
54 changes: 54 additions & 0 deletions scripts/smoke-health.sh
Original file line number Diff line number Diff line change
@@ -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"
Loading