Generate a .circleci/config.yml that compares your database schema on every branch push. Catches breaking changes before they become production incidents.
🚀 One config, zero friction
Copy this into your repo as .circleci/config.yml or let the wizard auto-detect your schema files.
# .circleci/config.yml# SchemaLens Schema Diff for CircleCI# Place this file at .circleci/config.yml in your repository.# Catches breaking database schema changes in every PR build.version: 2.1
executors:
node-executor:
docker:
- image: cimg/node:20.0
jobs:
schema-diff:
executor: node-executor
environment:
SCHEMA_PATH: db/schema.sql
DIALECT: postgres
FAIL_ON_BREAKING: "false"
SKIP_NO_SQL_CHANGE: "false"
POST_PR_COMMENT: "false"
FORMAT: markdown
steps:
- checkout
- run:
name: Check for SQL changes (smart skip)
command: |
if [ "$SKIP_NO_SQL_CHANGE" = "true" ]; then
CHANGED=$(git diff --name-only origin/${CIRCLE_BRANCH}..HEAD 2>/dev/null || git diff --name-only HEAD~1..HEAD || echo "")
if ! echo "$CHANGED" | grep -q '\.sql$'; then
echo "No .sql files changed — skipping schema diff."
circleci-agent step halt
fi
fi
- run:
name: Fetch base schema
command: |
BASE_BRANCH=$(echo "${CIRCLE_PULL_REQUEST:-}" | sed 's/.*\///' || echo "main")
if [ -n "$CIRCLE_PULL_REQUEST" ]; then
PR_NUM=$(echo "$CIRCLE_PULL_REQUEST" | sed 's/.*\///')
# Try to determine target branch from GitHub API
TARGET=$(curl -s "https://api.github.com/repos/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/pulls/${PR_NUM}" | jq -r '.base.ref // "main"')
else
TARGET="main"
fi
git fetch origin "$TARGET" 2>/dev/null || true
git show "origin/${TARGET}:$SCHEMA_PATH" > /tmp/schema_base.sql 2>/dev/null || echo "-- No base schema" > /tmp/schema_base.sql
- run:
name: Run SchemaLens diff
command: |
set -euo pipefail
ENDPOINT="https://schemalens.tech/api/free-diff"
LICENSE_HEADER=""
if [ -n "${SL_LICENSE_KEY:-}" ]; then
ENDPOINT="https://schemalens.tech/api/diff"
LICENSE_HEADER="-H \"X-License-Key: $SL_LICENSE_KEY\""
echo "[SchemaLens] Using Pro endpoint."
fi
BODY=$(jq -n \
--arg schemaA "$(cat /tmp/schema_base.sql)" \
--arg schemaB "$(cat "$SCHEMA_PATH")" \
--arg dialect "$DIALECT" \
--arg format "$FORMAT" \
'{schemaA: $schemaA, schemaB: $schemaB, dialect: $dialect, format: $format}')
HTTP_STATUS=0
for attempt in 1 2 3; do
HTTP_STATUS=$(curl -s -o /tmp/schemalens_response.json -w "%{http_code}" -X POST "$ENDPOINT" \
-H "Content-Type: application/json" \
${LICENSE_HEADER} \
-d "$BODY" || echo "000")
if [ "$HTTP_STATUS" = "200" ]; then break; fi
echo "[SchemaLens] Attempt $attempt failed (HTTP $HTTP_STATUS). Retrying..."
sleep $((attempt * 2))
done
if [ "$HTTP_STATUS" != "200" ]; then
echo "[SchemaLens] API failed after 3 attempts (status: $HTTP_STATUS)"
cat /tmp/schemalens_response.json 2>/dev/null || true
exit 1
fi
jq -r '.markdown // .migrationTeaser // "No output."' /tmp/schemalens_response.json > /tmp/schema_diff_report.md
cat /tmp/schema_diff_report.md
- run:
name: Extract metrics
command: |
BCOUNT=$(jq -r '(.summary.breakingChangeCount // (.breakingChanges | length) // 0)' /tmp/schemalens_response.json)
SCORE=$(jq -r '.riskScore.score // 0' /tmp/schemalens_response.json)
LABEL=$(jq -r '.riskScore.label // "Unknown"' /tmp/schemalens_response.json)
TA=$(jq -r '.summary.tablesAdded // 0' /tmp/schemalens_response.json)
TR=$(jq -r '.summary.tablesRemoved // 0' /tmp/schemalens_response.json)
TM=$(jq -r '.summary.tablesModified // 0' /tmp/schemalens_response.json)
echo "export SCHEMALENS_BC=$BCOUNT" >> "$BASH_ENV"
echo "export SCHEMALENS_SCORE=$SCORE" >> "$BASH_ENV"
echo "export SCHEMALENS_LABEL=$LABEL" >> "$BASH_ENV"
echo "export SCHEMALENS_TA=$TA" >> "$BASH_ENV"
echo "export SCHEMALENS_TR=$TR" >> "$BASH_ENV"
echo "export SCHEMALENS_TM=$TM" >> "$BASH_ENV"
echo "[SchemaLens] Risk: $LABEL ($SCORE/100) | +$TA -$TR ~$TM | $BCOUNT breaking"
- run:
name: Post PR comment
command: |
if [ "$POST_PR_COMMENT" = "true" ] && [ -n "${GITHUB_TOKEN:-}" ] && [ -n "${CIRCLE_PULL_REQUEST:-}" ]; then
PR_NUM=$(echo "$CIRCLE_PULL_REQUEST" | sed 's/.*\///')
REPORT=$(cat /tmp/schema_diff_report.md | sed 's/"/\\"/g' | sed ':a;N;$!ba;s/\n/\\n/g')
curl -s -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/repos/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/issues/${PR_NUM}/comments" \
-d "{\"body\": \"## SchemaLens Schema Diff Report\\n\\n${REPORT}\"}" || echo "Warning: failed to post PR comment."
fi
when: always
- run:
name: Fail on breaking changes
command: |
if [ "$FAIL_ON_BREAKING" = "true" ] && [ "$SCHEMALENS_BC" != "0" ]; then
echo "[SchemaLens] $SCHEMALENS_BC breaking change(s) detected. Failing build."
exit 1
fi
- store_artifacts:
path: /tmp/schema_diff_report.md
destination: schema_diff_report.md
workflows:
schema-diff-workflow:
jobs:
- schema-diff:
filters:
branches:
ignore:
- main
- master
🔍 SchemaLens Schema Diff Report