(config?: DatabaseConfig)
| 154 | * Run all pending migrations |
| 155 | */ |
| 156 | export async function runMigrations(config?: DatabaseConfig): Promise<void> { |
| 157 | // Initialize database if config provided |
| 158 | if (config) { |
| 159 | initializeDatabase(config); |
| 160 | } |
| 161 | |
| 162 | // Create migrations table |
| 163 | await createMigrationsTable(); |
| 164 | |
| 165 | // Load all migrations |
| 166 | const migrations = await loadMigrations(); |
| 167 | const appliedMigrations = await getAppliedMigrations(); |
| 168 | const appliedVersions = new Set(appliedMigrations.map((m) => m.version)); |
| 169 | |
| 170 | // Detect filename mismatches — a sign that a migration version was |
| 171 | // claimed by a different file (numbering collision from concurrent PRs). |
| 172 | // Collisions above MISMATCH_BASELINE block startup; older ones are |
| 173 | // historical debt that get logged as warnings. |
| 174 | const MISMATCH_BASELINE = 389; |
| 175 | const appliedByVersion = new Map(appliedMigrations.map((m) => [m.version, m.filename])); |
| 176 | const warnings: string[] = []; |
| 177 | const errors: string[] = []; |
| 178 | for (const m of migrations) { |
| 179 | const appliedFilename = appliedByVersion.get(m.version); |
| 180 | if (appliedFilename && appliedFilename !== m.filename) { |
| 181 | const msg = `Migration ${m.version} on disk is "${m.filename}" but was applied as "${appliedFilename}"`; |
| 182 | if (m.version > MISMATCH_BASELINE) { |
| 183 | errors.push(msg); |
| 184 | } else { |
| 185 | warnings.push(msg); |
| 186 | } |
| 187 | } |
| 188 | } |
| 189 | if (warnings.length > 0) { |
| 190 | console.warn( |
| 191 | `⚠ Historical migration filename mismatches (pre-${MISMATCH_BASELINE}):\n${warnings.join("\n")}` |
| 192 | ); |
| 193 | } |
| 194 | if (errors.length > 0) { |
| 195 | throw new Error( |
| 196 | `Migration filename mismatches detected (possible numbering collision):\n${errors.join("\n")}\n` + |
| 197 | `Renumber the colliding migration(s) and redeploy.` |
| 198 | ); |
| 199 | } |
| 200 | |
| 201 | // Find pending migrations |
| 202 | const pendingMigrations = migrations.filter( |
| 203 | (m) => !appliedVersions.has(m.version) |
| 204 | ); |
| 205 | |
| 206 | if (pendingMigrations.length === 0) { |
| 207 | // Quiet startup - no output when nothing to do |
| 208 | return; |
| 209 | } |
| 210 | |
| 211 | console.log(`Applying ${pendingMigrations.length} pending migrations...`); |
| 212 | |
| 213 | // Apply each pending migration |
no test coverage detected