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 <noreply@anthropic.com>
This commit is contained in:
s.esmaeili
2026-10-14 10:45:30 +03:30
co-authored by Claude Haiku 5.5
parent 2a33c0f290
commit e7c9b7238f
2 changed files with 45 additions and 1 deletions
+28
View File
@@ -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');
});
});
+17 -1
View File
@@ -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<T extends { name: string }>(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<MigrationResult> {