From e7c9b7238f07c98e5d10de88bce2a744fbe7f4a7 Mon Sep 17 00:00:00 2001 From: "s.esmaeili" Date: Wed, 14 Oct 2026 10:45:30 +0330 Subject: [PATCH] fix(api): run baseline migration first so an empty database boots Migrations were applied in plain name order, so 0002_admin_schema.sql ran before 20260910113720_first and failed with relation "City" does not exist. sortMigrations now always puts the baseline first; existing databases are unaffected because their applied names are unchanged. Co-Authored-By: Claude Haiku 5.5 --- apps/api/src/prisma/migrate.spec.ts | 28 ++++++++++++++++++++++++++++ apps/api/src/prisma/migrate.ts | 18 +++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 apps/api/src/prisma/migrate.spec.ts diff --git a/apps/api/src/prisma/migrate.spec.ts b/apps/api/src/prisma/migrate.spec.ts new file mode 100644 index 0000000..f29ea7a --- /dev/null +++ b/apps/api/src/prisma/migrate.spec.ts @@ -0,0 +1,28 @@ +import { readdirSync } from 'fs'; +import { join } from 'path'; +import { sortMigrations } from './migrate'; + +describe('sortMigrations', () => { + it('puts the baseline before 0002 so an empty database can boot', () => { + const names = sortMigrations([ + { name: '20260914165255_sync_schema_drift' }, + { name: '0002_admin_schema.sql' }, + { name: '20260910113720_first' }, + ]).map((f) => f.name); + + expect(names).toEqual([ + '20260910113720_first', + '0002_admin_schema.sql', + '20260914165255_sync_schema_drift', + ]); + }); + + it('starts the real migrations folder with the baseline', () => { + const dir = join(__dirname, '..', '..', 'prisma', 'migrations'); + const entries = readdirSync(dir) + .filter((name) => name !== 'migration_lock.toml') + .map((name) => ({ name })); + + expect(sortMigrations(entries)[0].name).toBe('20260910113720_first'); + }); +}); diff --git a/apps/api/src/prisma/migrate.ts b/apps/api/src/prisma/migrate.ts index 89050a6..b989d3b 100644 --- a/apps/api/src/prisma/migrate.ts +++ b/apps/api/src/prisma/migrate.ts @@ -152,7 +152,23 @@ function discoverMigrationFiles(dir: string): MigrationFile[] { } } - return files.sort((a, b) => a.name.localeCompare(b.name)); + return sortMigrations(files); +} + +/** + * The baseline creates every table, so it must run first on an empty database. + * Plain name order put `0002_admin_schema.sql` ahead of `20260910113720_first`, + * and a fresh boot died on `relation "City" does not exist`. Existing databases + * never noticed because they already had the baseline applied. + */ +const BASELINE_MIGRATION = '20260910113720_first'; + +export function sortMigrations(files: T[]): T[] { + return [...files].sort((a, b) => { + if (a.name === BASELINE_MIGRATION) return -1; + if (b.name === BASELINE_MIGRATION) return 1; + return a.name.localeCompare(b.name); + }); } export async function runMigrations(prisma: PrismaClient): Promise {