Twice now we've shipped a deploy that 500'd in production because
drizzle silently skipped freshly-generated migrations whose `when`
timestamps were older than a prior manually-bumped entry (0010/0011
in 1b7f553, then 0012/0013 in 2731888). Both times pnpm migrate
printed "Migrations applied." while the live DB schema lagged the
code's expectations.
Three layers of defence:
1. packages/db/src/journal-check.ts — pure helpers
- assertJournalMonotonic(entries): walks idx-sorted entries and
returns each one whose `when` <= the previous entry's `when`,
plus a suggested `when` value to bump it to.
- formatJournalViolations(result): renders an actionable
multi-line message that points at the offending file path.
2. packages/db/src/migrate.ts — pre-flight
Reads _journal.json BEFORE handing it to drizzle.migrate(). If
the journal is non-monotonic, it prints the violations + bump
instructions and exits with code 2. No more "Migrations applied."
while silently skipping.
3. apps/web/src/test/drizzle-journal-monotonic.test.ts — CI guard
Reads the committed _journal.json at test time. CI fails on the
PR before the bad commit can ship. Imports the helper through a
new "./journal-check" subpath export on @cmbot/db so the test
doesn't rely on a deep path into the package.
Together: a bad commit fails CI; if it somehow got through, migrate
itself refuses to run; if migrate is bypassed, the previous deploy's
schema stays intact (drizzle wouldn't have skipped anything in any
case where the journal is monotonic).
Web suite 480 → 482 tests, all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
46 lines
1.1 KiB
JSON
46 lines
1.1 KiB
JSON
{
|
|
"name": "@cmbot/db",
|
|
"version": "0.1.0",
|
|
"private": true,
|
|
"type": "module",
|
|
"main": "./dist/index.js",
|
|
"types": "./dist/index.d.ts",
|
|
"exports": {
|
|
".": {
|
|
"types": "./dist/index.d.ts",
|
|
"default": "./dist/index.js"
|
|
},
|
|
"./schema": {
|
|
"types": "./dist/schema.d.ts",
|
|
"default": "./dist/schema.js"
|
|
},
|
|
"./journal-check": {
|
|
"types": "./dist/journal-check.d.ts",
|
|
"default": "./dist/journal-check.js"
|
|
}
|
|
},
|
|
"scripts": {
|
|
"build": "tsc -p tsconfig.json",
|
|
"test": "echo 'no unit tests'",
|
|
"lint": "echo 'lint placeholder'",
|
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
"generate": "drizzle-kit generate",
|
|
"migrate": "tsx src/migrate.ts",
|
|
"studio": "drizzle-kit studio --host 0.0.0.0",
|
|
"seed": "tsx src/seed.ts"
|
|
},
|
|
"dependencies": {
|
|
"bcryptjs": "^3.0.3",
|
|
"drizzle-orm": "^0.36.0",
|
|
"pg": "^8.13.0"
|
|
},
|
|
"devDependencies": {
|
|
"@types/bcryptjs": "^3.0.0",
|
|
"@types/node": "^22.7.0",
|
|
"@types/pg": "^8.11.10",
|
|
"drizzle-kit": "^0.28.0",
|
|
"tsx": "^4.19.0",
|
|
"typescript": "^5.5.0"
|
|
}
|
|
}
|