byrcsc/laravel-cartographer · 1.x
Continuous integration.
Fail a build when the committed diagram no longer matches the schema, with a GitHub Actions job, a database service, and the exit codes to check.
The check is three commands: migrate a throwaway database, regenerate the diagram, and fail if the working tree moved.
php artisan migrate --force
php artisan cartographer:erd
git diff --exit-code docs/erd.mdgit diff --exit-code returns 1 when the file changed, which fails the job
and prints the diff. Because the output is deterministic, a diff means the
schema or the models moved without the diagram being regenerated — not that CI
and a developer machine disagree.
Why CI needs a database
Cartographer reads structure from a live connection, so the job needs one, and it needs the migrations run against it. SQLite is enough when your migrations run on SQLite; use the same engine as production when they do not.
Type normalization is what makes this safe across engines. The same application
produces the same bigint, varchar, and timestamp on MySQL, PostgreSQL, and
SQLite, so a developer generating against SQLite and a build generating against
MySQL still produce the same file. See schema
introspection.
GitHub Actions with SQLite
The smallest version, with no service container:
name: ERD
on: pull_request
jobs:
erd:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
- run: composer install --prefer-dist --no-interaction
- run: touch database/database.sqlite
- run: php artisan migrate --force
env:
DB_CONNECTION: sqlite
DB_DATABASE: database/database.sqlite
- run: php artisan cartographer:erd
env:
DB_CONNECTION: sqlite
DB_DATABASE: database/database.sqlite
- name: Fail if the committed ERD is stale
run: git diff --exit-code docs/erd.mdInstall dev dependencies. Cartographer is one, and --no-dev leaves the command
unregistered.
GitHub Actions with MySQL
When your migrations use engine-specific SQL, run the check against the real engine:
jobs:
erd:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.4
env:
MYSQL_DATABASE: app
MYSQL_ROOT_PASSWORD: password
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-timeout=5s
--health-retries=5
env:
DB_CONNECTION: mysql
DB_HOST: 127.0.0.1
DB_PORT: 3306
DB_DATABASE: app
DB_USERNAME: root
DB_PASSWORD: password
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
- run: composer install --prefer-dist --no-interaction
- run: php artisan migrate --force
- run: php artisan cartographer:erd
- run: git diff --exit-code docs/erd.mdChecking a set of diagrams
Regenerate every diagram, then diff the directory rather than one file:
composer erd
git diff --exit-code docs/Scope the path to what the script writes. git diff --exit-code with no path
fails on any unrelated change the job made, which turns a useful check into a
confusing one.
Reporting the diff instead of failing
On a repository where a stale diagram should be visible but not blocking, write the regenerated file into the job summary and keep the step green:
php artisan cartographer:erd
if ! git diff --quiet docs/erd.md; then
{
echo '### The committed ERD is out of date'
echo
echo '```diff'
git diff docs/erd.md
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
fiExit codes to check
The command itself returns 0 on success and 1 on failure, so set -e is
enough to stop a job on a broken run. It fails on an unreachable connection, an
invalid option or config value, an unknown seed model, no discovered models, and
an output path it cannot write.
It does not fail on a skipped model. A table missing on the CI database produces a warning and a smaller diagram, which the diff step then catches:
WARN Skipping model [App\Models\Ghost]: table [missing_ghosts] does not exist.If a build fails on a diff full of removed entities, the migrations did not all run.
What to read next
- Console commands for every exit code and option.
- Keeping the diagram current for the local half of the same workflow.
- Troubleshooting for what a specific failure means.