Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save anthonyjoeseph/6b99beb34d494acd1dfc83a192ed9388 to your computer and use it in GitHub Desktop.
Save anthonyjoeseph/6b99beb34d494acd1dfc83a192ed9388 to your computer and use it in GitHub Desktop.
// MIT licensed (© 2024)
// https://opensource.org/license/mit
//
// Source: https://gist.github.com/anthonyjoeseph/6b99beb34d494acd1dfc83a192ed9388/edit
// Original (with bug): https://gist.github.com/gburtini/7e34842c567dd80ee834de74e7b79edd
import fs from "fs";
import config from "../../drizzle.config";
import path from "path";
import { execSync } from "child_process";
if (!config.out || !fs.existsSync(config.out)) {
console.error(`Directory ${config.out} does not exist`);
console.error("Maybe your drizzle.config.ts can't be found.");
process.exit(1);
}
const files = fs.readdirSync(config.out).sort();
const rawJournal = fs.readFileSync(
path.join(config.out, "meta", "_journal.json"),
"utf8",
);
// a conflict is determined when two files have the same migration number
function findAllConflict(files: string[]) {
const conflicts = [];
let foundConflict = false;
let lastMigration = null;
for (let i = 0; i < files.length; i++) {
const file = files[i] as string;
const match = file.match(/^(\d+)_/);
if (!match?.[1]) continue;
const migration = parseInt(match[1]);
if (foundConflict) {
conflicts.push(file);
} else if (migration === lastMigration) {
// everything after the first conflict is a conflict
foundConflict = true;
conflicts.push(files[i - 1] as string);
conflicts.push(file);
}
lastMigration = migration;
}
return conflicts;
}
const conflicts = findAllConflict(files);
const firstConflict = conflicts[0];
if (!firstConflict) {
console.log("No migration conflicts found.");
process.exit(0);
}
console.error(`Migration conflict found starting at ${pad(firstConflict)}`);
// if we have a journal, it may have git conflict markers. don't touch it if it doesn't, so we don't accidentally throw anything away.
if (rawJournal.includes("<<<<<<<")) {
if (process.env.FORCE_FIX) {
console.log("Resetting journal file to the state of the current branch.");
execSync(`git checkout --theirs ${path.join(config.out, "meta")}`);
} else {
console.error("Journal file contains git conflict markers.");
console.error("Restore the journal to the state _before_ this change.");
console.error(
"That is, return to the state that was in main or the parent branch.",
);
console.error("As a rule, never merge anything in the meta directory.");
process.exit(1);
}
}
// read the journal to identify which migrations to keep
const reloadedJournal = fs.readFileSync(
path.join(config.out, "meta", "_journal.json"),
"utf8",
);
const journal = JSON.parse(reloadedJournal) as {
entries: { idx: number; tag: string }[];
};
for (const conflict of conflicts) {
const [idx] = conflict.split("_");
const tag = conflict.replace(".sql", "");
if (!idx || !tag) {
console.error(`Could not parse migration number and tag from ${conflict}`);
process.exit(1);
}
const journalEntry = journal.entries.find(
(entry) => entry.idx === parseInt(idx),
) as {
idx: number;
tag: string;
};
if (!journalEntry) {
console.error(`Migration ${pad(idx)} not found in journal.`);
if (process.env.FORCE_FIX) {
execSync("rm -f " + path.join(config.out, conflict));
console.log(`Removed migration ${pad(idx)}: ${conflict}`);
execSync(
"rm -f " + path.join(config.out, "meta", `${pad(idx)}_snapshot.json`),
);
console.log(`Removed snapshot ${pad(idx)}: ${pad(idx)}_snapshot.json`);
continue;
} else {
process.exit(1);
}
} else if (journalEntry.tag === tag) {
console.log(`Keeping migration ${pad(idx)}: ${journalEntry.tag}`);
continue;
} else {
if (process.env.FORCE_FIX) {
execSync("rm -f " + path.join(config.out, conflict));
console.log(`Removed migration ${pad(idx)}: ${conflict}`);
continue;
} else {
console.error(
`Migration ${pad(idx)} is tagged ${journalEntry.tag} in journal.`,
);
console.error(`Expected tag ${tag} for migration ${pad(idx)}.`);
process.exit(1);
}
}
}
if (process.env.FORCE_FIX) {
execSync("pnpm run db:generate");
console.log("All conflicts resolved. Migrations regenerated.");
} else {
console.log("All conflicts resolved. Regenerate migrations for your changes.");
}
console.log(
"When you open the PR, ensure there are no changes to historical entries in the journal or meta snapshot files.",
);
function pad(num: number | string, length = 4) {
return num.toString().padStart(length, "0");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment