name: Database Migrations on: workflow_call: inputs: environment: description: Target environment (production, staging, or dev) required: true type: string workflow_dispatch: inputs: environment: description: Target environment required: true type: choice options: - production - staging - dev permissions: contents: read jobs: migrate: name: Apply Database Migrations runs-on: blacksmith-4vcpu-ubuntu-2404 steps: - name: Checkout code uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - name: Setup Bun uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 with: bun-version: 1.3.13 - name: Cache Bun dependencies uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: path: | ~/.bun/install/cache node_modules **/node_modules key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }} restore-keys: | ${{ runner.os }}-bun- - name: Install dependencies run: bun install --frozen-lockfile # The expression maps the explicit environment input to exactly one repo # secret, so the job never holds another environment's database URL. An # unknown environment resolves to empty and the guard below fails the job. # MIGRATION_DATABASE_URL is the optional direct (non-pooled) DSN preferred # by migrate.ts; when the secret is unset it resolves to empty and the # script falls back to DATABASE_URL. - name: Apply database schema changes working-directory: ./packages/db env: DATABASE_URL: ${{ inputs.environment == 'production' && secrets.DATABASE_URL || inputs.environment == 'staging' && secrets.STAGING_DATABASE_URL || inputs.environment == 'dev' && secrets.DEV_DATABASE_URL || '' }} MIGRATION_DATABASE_URL: ${{ inputs.environment == 'production' && secrets.MIGRATION_DATABASE_URL || inputs.environment == 'staging' && secrets.STAGING_MIGRATION_DATABASE_URL || '' }} ENVIRONMENT: ${{ inputs.environment }} run: | if [ -z "$DATABASE_URL" ]; then echo "ERROR: no database URL secret resolved for environment '${ENVIRONMENT}'" >&2 exit 1 fi if [ "${ENVIRONMENT}" = "dev" ]; then echo "Dev environment — pushing schema directly (db:push)" # drizzle-kit push needs a TTY to resolve ambiguous renames (--force only # covers data-loss). In CI it throws "Interactive prompts require a TTY # terminal" but still exits 0, so the job goes green without applying the # change. tee keeps the output live in the log; we then fail on drizzle's # own TTY error. A genuine non-zero exit already fails via `set -e`. bun run db:push --force < /dev/null 2>&1 | tee /tmp/db-push.log if grep -q "Interactive prompts require a TTY terminal" /tmp/db-push.log; then echo "ERROR: db:push needs an interactive rename decision; land it as a versioned migration instead of relying on push." >&2 exit 1 fi else echo "Applying versioned migrations (db:migrate)" bun run ./scripts/migrate.ts fi