import { createHash } from 'node:crypto'; import { readFile, readdir, writeFile } from 'node:fs/promises'; import { resolve, join } from 'node:path'; const root = resolve( process.argv.find((value) => value.startsWith('--root='))?.slice(7) ?? '.', ); const folder = join(root, 'prisma', 'migrations'); const manifest = join(root, 'prisma', 'migration-checksums.json'); const hashes = JSON.parse(await readFile(manifest, 'utf8')); const canonicalHash = (sql) => createHash('sha256').update(sql.replaceAll('\r\n', '\n')).digest('hex'); for (const [name, expected] of Object.entries(hashes)) { const actual = canonicalHash( await readFile(join(folder, name, 'migration.sql'), 'utf8'), ); if (actual !== expected) throw new Error(`Immutable migration changed: ${name}`); } const names = (await readdir(folder, { withFileTypes: true })) .filter((entry) => entry.isDirectory()) .map((entry) => entry.name) .sort(); const stamps = new Set(); for (const name of names) { const stamp = name.split('_')[0]; if (stamps.has(stamp)) throw new Error(`Duplicate migration timestamp: ${stamp}`); stamps.add(stamp); if (hashes[name]) continue; if (!/^\d{14}_[a-z0-9_]+$/.test(name)) throw new Error(`New migration requires YYYYMMDDHHmmss timestamp: ${name}`); if (!process.argv.includes('--record-new')) throw new Error(`New migration checksum not recorded: ${name}`); hashes[name] = canonicalHash( await readFile(join(folder, name, 'migration.sql'), 'utf8'), ); } if (process.argv.includes('--record-new')) await writeFile(manifest, JSON.stringify(hashes, null, 2) + '\n'); console.log(`Verified ${Object.keys(hashes).length} immutable migrations`);