migrolint
A framework-agnostic linter for your database migrations folder. It catches
the boring, expensive mistakes that pass locally and only blow up when CI
actually runs the migrations — duplicate version numbers, sequence gaps, and
up migrations with no matching down. No database connection, no framework
lock-in, zero dependencies (pure standard library).
pip install migrolint
migrolint # auto-detects ./migrations, db/migrations, ...
migrolint db/migrations --strict
The problem
Two developers branch off main, each adds 0007_add_index.sql, both merge.
Now your migrations folder has two migrations claiming version 0007. Your
runner picks one and silently skips the other — or aborts the whole deploy.
This is a known, recurring failure
in every sequence-numbered migration tool.
The existing linters (django-migration-linter, Flyway's own checks) are tied
to one framework or need a live database. If you use raw SQL with goose,
dbmate, golang-migrate, or a hand-rolled folder, there's nothing that just
looks at the filenames and tells you they're sane. That's migrolint.
What it checks
| Rule | Severity | Meaning |
|---|---|---|
DUPE_NUM |
error | two migrations share a version number (the merge-collision bug) |
MISSING_DOWN |
warning | an up migration has no matching down (only flagged if the project uses up/down splits) |
SEQ_GAP |
warning | a hole in an integer sequence — usually a deleted or un-merged migration |
BAD_FORMAT |
warning | a file whose name no known convention recognizes |
Naming conventions it understands
migrolint reads version numbers out of the filename, across the conventions people actually use:
| Convention | Example |
|---|---|
| Flyway | V1__init.sql, U1__undo.sql, R__refresh.sql, V1.1__patch.sql |
| golang-migrate / dbmate | 0001_create_users.up.sql + 0001_create_users.down.sql |
| goose / Rails | 20230101120000_create_users.sql (timestamp prefix) |
| minimalist | 1_init.sql, 2-add-index.sql |
Timestamp-style versions are recognized but exempt from SEQ_GAP (they're not
meant to be contiguous). Well-known non-migration files (schema.rb,
structure.sql, seeds.rb, …) and any non-migration extension are skipped.
Usage
migrolint # scan the first migrations dir it finds
migrolint db/migrations # scan a specific dir
migrolint app/migrations svc/migrations # scan several
migrolint --json # machine-readable, for tooling
migrolint --strict # warnings become errors (exit 1) — good for CI
migrolint --ext .sql,.py # override which extensions count
migrolint --ignore baseline.sql,seed.sql
You can also run it as a module: python -m migrolint db/migrations.
As a pre-commit / CI gate
# .pre-commit-config.yaml
- repo: local
hooks:
- id: migrolint
name: migrolint
entry: migrolint --strict
language: system
pass_filenames: false
# GitHub Actions
- run: pipx run migrolint --strict
Example output
migrolint db/migrations (14 files, 12 migrations)
✗ DUPE_NUM version 0007 used by 2 migrations:
0007_add_index.up.sql
0007_add_orders_fk.up.sql
⚠ MISSING_DOWN 0009_drop_legacy.up.sql — no matching .down file
⚠ SEQ_GAP missing version(s): 5
1 error, 2 warnings.
Exit codes
| Code | Meaning |
|---|---|
0 |
clean (or only warnings, without --strict) |
1 |
errors found — or warnings, when --strict is set |
2 |
usage / IO error (no migrations dir, unreadable path) |
Also available for Node
Same checks, same flags: npx migrolint
(source: migrolint). Both ports read
filenames identically, so a mixed-language team gets the same verdict.
License
MIT
Metadata
Release files for migrolint 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| migrolint-0.1.0.tar.gz | 11.7 kB | Without provenance | Details for migrolint-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| migrolint-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for migrolint-0.1.0-py3-none-any.whl |
Total release size: 22.0 kB
Release files / migrolint-0.1.0.tar.gz
| Download URL | migrolint-0.1.0.tar.gz |
|---|---|
| Size | 11.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2f9219829e8107170f416022f50f3c3e6a5afcf9c3b4d238642ce4eb2d2d0852
|
|
BLAKE2b-256 checksum How to use checksums |
17d5d4651e2cb85207e3fe78c8f0b31dbd698cfc52f4fa008362b52511dd90dc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.7
|
Release files / migrolint-0.1.0-py3-none-any.whl
| Download URL | migrolint-0.1.0-py3-none-any.whl |
|---|---|
| Size | 10.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8545a7badb58b204e547931848ce322b7b28c799fa48f4b48e557e9011f4da2d
|
|
BLAKE2b-256 checksum How to use checksums |
5162dd053830d8564757395b9452fa59232fb3a0b612f6124e82ca56d7e6e177
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.7
|
Security for migrolint 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.