Repository navigation
123 lines (113 loc) · 5.1 KB
/
Copy pathmigrate.yml
File metadata and controls
123 lines (113 loc) · 5.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
name: migrate
on:
workflow_call:
inputs:
environment:
description: Target environment (production, staging, or dev)
required: true
type: string
secrets:
DATABASE_URL:
required: false
STAGING_DATABASE_URL:
required: false
DEV_DATABASE_URL:
required: false
MIGRATION_DATABASE_URL:
required: false
STAGING_MIGRATION_DATABASE_URL:
required: false
workflow_dispatch:
inputs:
environment:
description: Target environment
required: true
type: choice
options:
- production
- staging
- dev
permissions:
contents: read
jobs:
migrate:
name: migrate
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }}
# Bulk projection loads and concurrent index builds can outlast ordinary schema changes.
timeout-minutes: 300
# One migrate job per environment at a time, so a manual dispatch queues
# behind a CI run instead of racing it for migrate.ts's advisory lock, which
# gives up after 30 minutes. Job-level because a called workflow evaluates
# workflow-level concurrency with the caller's github context; the group
# must also never equal ci.yml's ci-<ref> group or the two deadlock.
concurrency:
group: migrate-${{ inputs.environment }}
cancel-in-progress: false
steps:
# An environment only migrates from its own branch, so a manual dispatch
# from a feature branch cannot apply that branch's migrations to a shared
# database. Under workflow_call github.ref is the caller's ref (ci.yml's
# push to main/staging/dev). This fails rather than skips: ci.yml's
# promote gates on migrate succeeding, and a skipped run must never read
# as a migration that happened. It runs before any secret is read.
- name: Require the environment's own branch
env:
ENVIRONMENT: ${{ inputs.environment }}
REF: ${{ github.ref }}
run: |
case "${ENVIRONMENT}" in
production) expected=refs/heads/main ;;
staging) expected=refs/heads/staging ;;
dev) expected=refs/heads/dev ;;
*)
echo "::error::Unknown migrate environment '${ENVIRONMENT}'"
exit 1
;;
esac
if [ "${REF}" != "${expected}" ]; then
echo "::error::Refusing to migrate ${ENVIRONMENT} from ${REF}; ${ENVIRONMENT} migrates only from ${expected}"
exit 1
fi
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
with:
persist-credentials: false
- name: Setup workspace
uses: ./.github/actions/setup
with:
provider: ${{ vars.CI_PROVIDER }}
node-version: ''
- name: Validate Project migration database prerequisites
if: inputs.environment != 'dev'
working-directory: ./packages/db
env:
DATABASE_URL: ${{ inputs.environment == 'production' && secrets.DATABASE_URL || inputs.environment == 'staging' && secrets.STAGING_DATABASE_URL || '' }}
MIGRATION_DATABASE_URL: ${{ inputs.environment == 'production' && secrets.MIGRATION_DATABASE_URL || inputs.environment == 'staging' && secrets.STAGING_MIGRATION_DATABASE_URL || '' }}
run: bun --no-env-file scripts/project-contract-required.ts
# 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: |
set -euo pipefail
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)"
# Dev deliberately forces direct schema reconciliation; staging and
# production use guarded versioned migrations in the other branch.
SIM_DEV_DB_PUSH=1 bun run db:push --force < /dev/null
else
echo "Applying versioned migrations (db:migrate)"
bun run ./scripts/migrate.ts
fi