chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Detects migration files that share the same numeric prefix.
|
||||
*
|
||||
* node-pg-migrate orders migrations by their leading number, so two files with
|
||||
* the same prefix (e.g. `047_a.sql` and `047_b.sql`) have an ambiguous order and
|
||||
* can apply inconsistently across environments. This guard runs in CI (and as a
|
||||
* unit test) to stop new duplicates from landing on main.
|
||||
*
|
||||
* A small set of historical duplicates already exists on main and is grandfathered
|
||||
* in via ALLOWED_DUPLICATES — those are tolerated, anything new is rejected.
|
||||
*
|
||||
* Exports `findDuplicateMigrations()` for the test suite; runs as a CLI (exit 1 on
|
||||
* new duplicates) when executed directly.
|
||||
*/
|
||||
/* global console, process */
|
||||
import { readdirSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join } from 'node:path';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
export const MIGRATIONS_DIR = join(__dirname, '..', 'src', 'infra', 'database', 'migrations');
|
||||
|
||||
// Pre-existing duplicate prefixes on main. Do not add to this list — fix the
|
||||
// duplicate instead. These remain only because the migrations already shipped.
|
||||
export const ALLOWED_DUPLICATES = new Set(['033', '047']);
|
||||
|
||||
const MIGRATION_FILE = /^(\d+)_.*\.sql$/;
|
||||
|
||||
/**
|
||||
* @param {string} [dir] migrations directory to scan
|
||||
* @returns {{ count: number, newDuplicates: Array<{prefix:string, files:string[]}>,
|
||||
* grandfathered: Array<{prefix:string, files:string[]}>, nextPrefix: string }}
|
||||
*/
|
||||
export function findDuplicateMigrations(dir = MIGRATIONS_DIR) {
|
||||
const byPrefix = new Map();
|
||||
|
||||
for (const name of readdirSync(dir)) {
|
||||
const match = MIGRATION_FILE.exec(name);
|
||||
if (!match) continue;
|
||||
const prefix = match[1];
|
||||
if (!byPrefix.has(prefix)) byPrefix.set(prefix, []);
|
||||
byPrefix.get(prefix).push(name);
|
||||
}
|
||||
|
||||
const newDuplicates = [];
|
||||
const grandfathered = [];
|
||||
|
||||
for (const [prefix, files] of byPrefix) {
|
||||
if (files.length < 2) continue;
|
||||
files.sort();
|
||||
(ALLOWED_DUPLICATES.has(prefix) ? grandfathered : newDuplicates).push({ prefix, files });
|
||||
}
|
||||
|
||||
const maxPrefix = byPrefix.size
|
||||
? Math.max(...[...byPrefix.keys()].map((p) => parseInt(p, 10)))
|
||||
: -1;
|
||||
|
||||
return {
|
||||
count: byPrefix.size,
|
||||
newDuplicates,
|
||||
grandfathered,
|
||||
nextPrefix: String(maxPrefix + 1).padStart(3, '0'),
|
||||
};
|
||||
}
|
||||
|
||||
function main() {
|
||||
const { count, newDuplicates, grandfathered, nextPrefix } = findDuplicateMigrations();
|
||||
|
||||
if (grandfathered.length > 0) {
|
||||
console.log('Known (grandfathered) duplicate migration prefixes:');
|
||||
for (const { prefix, files } of grandfathered) {
|
||||
console.log(` ${prefix}: ${files.join(', ')}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (newDuplicates.length > 0) {
|
||||
console.error('\nERROR: duplicate migration number(s) detected:');
|
||||
for (const { prefix, files } of newDuplicates) {
|
||||
console.error(` ${prefix}: ${files.join(', ')}`);
|
||||
}
|
||||
console.error(
|
||||
`\nEach migration needs a unique number. Renumber the new file to the next ` +
|
||||
`available prefix (currently ${nextPrefix}_).`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`\nOK: ${count} unique migration number(s), no new duplicates.`);
|
||||
}
|
||||
|
||||
// Run as a CLI only when executed directly, not when imported by tests.
|
||||
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
||||
main();
|
||||
}
|
||||
Reference in New Issue
Block a user