Skip to content

Commit b653ef4

Browse files
ci: gate PRs and release builds on prisma migration order
Replace the standalone migration workflow with a composite action (.github/actions/check-prisma-migrations) and embed it in: - pr-gate.yml's build job, so migration drift or out-of-order timestamps fail the required PR check before merge. - _build.yml's build job (amd64 only) as a release-time backstop for schema drift. The action self-contains Postgres via docker run, detects whether Prisma files changed (skipping fast on PRs that don't), verifies migrations apply in sequence and reproduce schema.prisma, and checks no new migration predates the latest on the base branch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 01f8e7f commit b653ef4

5 files changed

Lines changed: 134 additions & 82 deletions

File tree

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
name: Check Prisma Migrations
2+
description: >-
3+
Verify Prisma migrations apply cleanly in order and reproduce schema.prisma
4+
(drift check), and that no new migration predates the latest on the base
5+
branch (ordering check). Designed to be embedded in an existing job so its
6+
failure turns that job's status red.
7+
8+
inputs:
9+
base-ref:
10+
description: >-
11+
Base git ref to diff migrations against (e.g. a PR's base branch). When
12+
set, the action skips work on PRs that don't touch migrations and runs the
13+
ordering check. When empty (release builds), the drift check always runs
14+
and the ordering check is skipped.
15+
required: false
16+
default: ""
17+
18+
runs:
19+
using: composite
20+
steps:
21+
- name: Detect Prisma changes
22+
id: detect
23+
shell: bash
24+
run: |
25+
if [ -z "${{ inputs.base-ref }}" ]; then
26+
echo "changed=true" >> "$GITHUB_OUTPUT"
27+
echo "No base-ref provided — running drift check unconditionally."
28+
exit 0
29+
fi
30+
git fetch --no-tags --depth=1 origin "+refs/heads/${{ inputs.base-ref }}:refs/remotes/origin/${{ inputs.base-ref }}"
31+
if git diff --name-only "origin/${{ inputs.base-ref }}" HEAD | grep -q '^packages/db/prisma/'; then
32+
echo "changed=true" >> "$GITHUB_OUTPUT"
33+
echo "Prisma changes detected — running migration checks."
34+
else
35+
echo "changed=false" >> "$GITHUB_OUTPUT"
36+
echo "No Prisma changes — skipping migration checks."
37+
fi
38+
39+
- name: Start Postgres
40+
if: steps.detect.outputs.changed == 'true'
41+
shell: bash
42+
run: |
43+
docker run -d --name prisma-check-pg \
44+
-e POSTGRES_USER=postgres \
45+
-e POSTGRES_PASSWORD=postgres \
46+
-e POSTGRES_DB=sourcebot \
47+
-p 5432:5432 postgres:16
48+
for i in $(seq 1 30); do
49+
if docker exec prisma-check-pg pg_isready -U postgres -q; then
50+
echo "Postgres ready."
51+
exit 0
52+
fi
53+
sleep 2
54+
done
55+
echo "Postgres failed to become ready." && exit 1
56+
57+
- name: Use Node.js
58+
if: steps.detect.outputs.changed == 'true'
59+
uses: actions/setup-node@v4
60+
with:
61+
node-version: "20.x"
62+
63+
- name: Install
64+
if: steps.detect.outputs.changed == 'true'
65+
shell: bash
66+
run: yarn install --frozen-lockfile
67+
68+
# Check 1: migrations apply cleanly in order AND reproduce schema.prisma.
69+
# `migrate deploy` fails if a migration is broken or applies out of sequence;
70+
# `migrate diff` exits 2 when the applied history drifts from the schema.
71+
- name: Apply migrations
72+
if: steps.detect.outputs.changed == 'true'
73+
shell: bash
74+
working-directory: packages/db
75+
env:
76+
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/sourcebot
77+
run: yarn prisma migrate deploy
78+
79+
- name: Check for schema drift
80+
if: steps.detect.outputs.changed == 'true'
81+
shell: bash
82+
working-directory: packages/db
83+
env:
84+
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/sourcebot
85+
run: |
86+
yarn prisma migrate diff \
87+
--from-database \
88+
--to-schema-datamodel prisma/schema.prisma \
89+
--exit-code \
90+
&& echo "✅ No drift: migrations reproduce schema.prisma" \
91+
|| (echo "❌ schema.prisma has changes not captured in a migration. Run: yarn dev:prisma:migrate:dev --name <name>" && exit 1)
92+
93+
# Check 2 (PRs only): no new migration predates the latest on the base branch.
94+
- name: Check migration ordering
95+
if: steps.detect.outputs.changed == 'true' && inputs.base-ref != ''
96+
shell: bash
97+
run: |
98+
MIG_DIR=packages/db/prisma/migrations
99+
BASE="origin/${{ inputs.base-ref }}"
100+
LATEST_ON_BASE=$(git ls-tree -r --name-only "$BASE" -- "$MIG_DIR" \
101+
| sed -n "s#$MIG_DIR/\([0-9]\{14\}\)_.*#\1#p" | sort | tail -1)
102+
echo "Latest migration on ${{ inputs.base-ref }}: ${LATEST_ON_BASE:-<none>}"
103+
NEW=$(comm -23 \
104+
<(ls "$MIG_DIR" | sed -n 's/^\([0-9]\{14\}\)_.*/\1/p' | sort -u) \
105+
<(git ls-tree -r --name-only "$BASE" -- "$MIG_DIR" | sed -n "s#$MIG_DIR/\([0-9]\{14\}\)_.*#\1#p" | sort -u))
106+
FAIL=0
107+
for ts in $NEW; do
108+
if [ -n "$LATEST_ON_BASE" ] && [ "$ts" -lt "$LATEST_ON_BASE" ]; then
109+
echo "❌ New migration $ts predates latest migration on ${{ inputs.base-ref }} ($LATEST_ON_BASE). Rename it with a current timestamp."
110+
FAIL=1
111+
fi
112+
done
113+
[ "$FAIL" -eq 0 ] && echo "✅ Migration ordering OK"
114+
exit $FAIL

.github/workflows/_build.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,14 @@ jobs:
7676
fetch-depth: 0
7777
token: ${{ inputs.use_app_token && steps.generate_token.outputs.token || github.token }}
7878

79+
# Release backstop: fail the build if migrations drift from schema.prisma.
80+
# Runs once (amd64 only) since the check is platform-independent. base-ref
81+
# is omitted, so the drift check always runs and the (PR-only) ordering
82+
# check is skipped.
83+
- name: Check Prisma migrations
84+
if: matrix.platform == 'linux/amd64'
85+
uses: ./.github/actions/check-prisma-migrations
86+
7987
# Extract metadata (tags, labels) for Docker
8088
# https://github.com/docker/metadata-action
8189
- name: Extract Docker metadata

.github/workflows/pr-gate.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
name: PR Gate
22

3-
# This gate simply validates that we can build the docker container.
3+
# This gate validates that Prisma migrations are in order and that we can build
4+
# the docker container.
45

56
on:
67
pull_request:
@@ -16,6 +17,15 @@ jobs:
1617
uses: actions/checkout@v4
1718
with:
1819
submodules: "true"
20+
# full history so migration checks can diff against the base branch
21+
fetch-depth: 0
22+
23+
# Fails fast (before the docker build) when migrations drift from
24+
# schema.prisma or a new migration is added out of timestamp order.
25+
- name: Check Prisma migrations
26+
uses: ./.github/actions/check-prisma-migrations
27+
with:
28+
base-ref: ${{ github.base_ref }}
1929

2030
- name: Build Docker image
2131
id: build

.github/workflows/prisma-migrations.yml

Lines changed: 0 additions & 81 deletions
This file was deleted.

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717
- [EE] Added a context-window usage gauge to the Ask Sourcebot chat details, showing how much of the selected model's context window each turn occupies. Window sizes are resolved from the models.dev catalog. [#1370](https://github.com/sourcebot-dev/sourcebot/pull/1370)
1818
- Added language model input-modality and document capability resolution, automatically resolved from the models.dev catalog (falls back to text-only for uncatalogued/self-hosted models). [#1372](https://github.com/sourcebot-dev/sourcebot/pull/1372)
1919
- [EE] Added DPoP sender-constrained OAuth tokens for MCP clients. [#1395](https://github.com/sourcebot-dev/sourcebot/pull/1395)
20+
- Added CI enforcement that Prisma migrations apply in order and reproduce `schema.prisma`, gating PRs (PR Gate) and release builds. [#1400](https://github.com/sourcebot-dev/sourcebot/pull/1400)
2021

2122
### Fixed
2223
- Send anonymous server-side PostHog events as personless so unauthenticated requests don't inflate person counts. [#1367](https://github.com/sourcebot-dev/sourcebot/pull/1367)

0 commit comments

Comments
 (0)