node-gamedig/tools/find-id-changes.js
Tom 68c8423c84
tools: Add find ID changes to list changes to game IDs (#435)
This tool iterates through the commit history finding commits where
lib/games.js has been modified. Whenever it has it loads the
before/after state and compares them to generate a list of IDs that were
removed/added/changed (changed is based on comparing the name of the
game).

The output of this script can then be converted into an object mapping
between old IDs and new IDs using a jq one-liner.
2023-12-30 16:38:32 +02:00

125 lines
3.6 KiB
JavaScript

#!/usr/bin/env node
import { spawnSync } from "node:child_process";
import assert from "node:assert";
import { mkdirSync, copyFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import process from "node:process";
// Generate a list of changes to "lib/games.js" where game IDs have been changed via the git history.
// Requires git to be installed.
// Make sure you don't have any local un-committed changes to lib/games.js
// Usage: node tools/find-id-changes.js > id-changes.json
// Output is an array of
// {
// "hash": "git commit hash",
// "changes": [ ["oldid", "newid"], ... ],
// "removed": [ "removedid", ... ],
// "added": [ "addedid", ... ]
// }
// The output can be converted to a map of { "oldid": "newid" } using a jq command:
// cat id-changes.json | jq ".[].changes | map({ (.[0]): .[1] } ) | add" | jq -s "add"
const main = async (rootDir) => {
// Make sure CWD is the root of the repo
process.chdir(rootDir);
// Get list of commits that have modified lib/games.js
const gitLog = spawnSync(
"git",
[
"log",
"--follow",
"--format=%H",
"--diff-filter=M",
"--reverse",
"--",
"lib/games.js",
],
{ encoding: "utf-8" }
);
// Make a directory to store files in
mkdirSync("game_changes", { recursive: true });
const output = [];
for (const commitHash of gitLog.stdout.split("\n")) {
if (commitHash.length === 0) continue;
// Checkout lib/games.js before the commit that changed it
assert(
spawnSync("git", ["checkout", `${commitHash}^1`, "--", "lib/games.js"])
.status === 0
);
// We have to copy each state of the file to its own file because node caches imports
const beforeName = `game_changes/${commitHash}-before.js`;
copyFileSync("lib/games.js", beforeName);
const before = await import(path.join("../", beforeName));
// Checkout lib/games.js after the commit that changed it
assert(
spawnSync("git", ["checkout", `${commitHash}`, "--", "lib/games.js"])
.status === 0
);
const afterName = `game_changes/${commitHash}-after.js`;
copyFileSync("lib/games.js", afterName);
const after = await import(path.join("../", afterName));
// Find game IDs that were removed and added
let removed = Object.keys(before.games).filter(
(key) => !(key in after.games)
);
let added = Object.keys(after.games).filter(
(key) => !(key in before.games)
);
const changes = [];
for (const rm of removed) {
for (const add of added) {
const beforeGame = before.games[rm];
const afterGame = after.games[add];
// Modify game names to ignore case, spaces, and punctuation
const beforeName = beforeGame.name.toLowerCase().replace(/[^a-z]/g, "");
const afterName = afterGame.name.toLowerCase().replace(/[^a-z]/g, "");
if (
beforeGame.options.protocol === afterGame.options.protocol &&
(beforeName.includes(afterName) || afterName.includes(beforeName))
) {
changes.push([rm, add]);
removed = removed.filter((r) => r !== rm);
added = added.filter((a) => a !== add);
break;
}
}
}
output.push({
hash: commitHash,
changes,
removed,
added,
});
}
// Reset the contents of lib/games.js
spawnSync("git", ["checkout", "--", "lib/games.js"]);
return output;
};
main(
// Get the root of the repo:
// dir of bin/find-id-changes.js -> /../
path.join(path.dirname(fileURLToPath(import.meta.url)), "..")
).then((o) => console.log(JSON.stringify(o)), console.error);