Skip to content

Instantly share code, notes, and snippets.

@scripting
Created September 13, 2026 14:30
Show Gist options
  • Select an option

  • Save scripting/06b7dc714456e2baf283ab17c3a68af1 to your computer and use it in GitHub Desktop.

Select an option

Save scripting/06b7dc714456e2baf283ab17c3a68af1 to your computer and use it in GitHub Desktop.
A script Claude wrote to find all the parts that changed in my Frontier.root odb
//compareRoots.js -- what in a snapshot of DW's root differs from the shipping root (or another snapshot): node compareRoots.js <his .root> <shipping .db or .root>
const odb = require ("/Users/davewiner/Claude/trigger/frontierodb.js");
const sqlite3 = require ("/Users/davewiner/Claude/trigger/node_modules/better-sqlite3");
function val (v) { return ((v && v.value !== undefined && v.type) ? v.value : v); }
function typeOfNode (v) { return ((v && v.type) ? v.type : (v && typeof v === "object" && !Array.isArray (v) && !(v instanceof Date) ? "table" : typeof v)); }
function textOfLines (lines) { return (lines.map (function (l) { return ("\t".repeat (l.level) + (l.flComment ? "«" : "") + l.text); }).join ("\n")); }
function flatten (t, path, out) { //path -> {type, text}; a node from the root reader is {type, value}
Object.keys (t).forEach (function (name) {
const node = t [name], p = path ? path + "." + name : name;
if ((node === undefined) || (node === null)) { out [p] = {type: "novalue", text: ""}; return; }
const type = node.type || "unknown", v = node.value;
if (type === "table") { out [p] = {type: "table"}; flatten ((v && typeof v === "object") ? v : {}, p, out); return; }
const lines = (v && v.lines) || node.lines;
if (lines) { out [p] = {type, text: textOfLines (lines)}; return; }
out [p] = {type, text: (v instanceof Date) ? v.toISOString () : ((v && typeof v === "object") ? JSON.stringify (v) : String (v))};
});
}
function fromRoot (path) { const out = {}; flatten (odb.readRootFile (path), "", out); return (out); }
function fromDb (path) {
const db = new sqlite3 (path, {readonly: true}); const out = {};
const rows = db.prepare ("select id, parentid, name, type, value from odb").all (); const byParent = {};
rows.forEach (function (r) { (byParent [r.parentid] = byParent [r.parentid] || []).push (r); });
function walk (pid, path) { (byParent [pid] || []).forEach (function (r) { const p = path ? path + "." + r.name : r.name;
if (r.type === "table") { out [p] = {type: "table"}; walk (r.id, p); return; }
if ((r.type === "script" || r.type === "outline" || r.type === "menubar" || r.type === "wptext") && r.value && r.value.charAt (0) === "[") { try { out [p] = {type: r.type, text: textOfLines (JSON.parse (r.value))}; return; } catch (e) {} }
out [p] = {type: r.type, text: String (r.value)}; }); }
walk (0, ""); return (out);
}
const A = fromRoot (process.argv [2]);
const B = process.argv [3].endsWith (".db") ? fromDb (process.argv [3]) : fromRoot (process.argv [3]);
const lowerB = {}; Object.keys (B).forEach (function (p) { lowerB [p.toLowerCase ()] = p; });
const lowerA = {}; Object.keys (A).forEach (function (p) { lowerA [p.toLowerCase ()] = p; });
const added = [], changed = [], missing = [];
Object.keys (A).forEach (function (p) { const a = A [p]; if (a.type === "table") return; const bp = lowerB [p.toLowerCase ()]; if (bp === undefined) { added.push (p + " (" + a.type + ")"); return; } const b = B [bp]; if (b.type === "table") { changed.push (p + " (" + a.type + ", was a table)"); return; } if ((a.text || "").replace (/\r\n|\r/g, "\n").trim () !== (b.text || "").replace (/\r\n|\r/g, "\n").trim ()) changed.push (p + " (" + a.type + ")"); });
Object.keys (B).forEach (function (p) { if (B [p].type !== "table" && lowerA [p.toLowerCase ()] === undefined) missing.push (p + " (" + B [p].type + ")"); });
function group (list) { const g = {}; list.forEach (function (p) { const top = p.split (".").slice (0, 3).join ("."); (g [top] = g [top] || []).push (p); }); return (g); }
console.log ("ADDED " + added.length + " / CHANGED " + changed.length + " / MISSING " + missing.length);
[["ADDED", added], ["CHANGED", changed], ["MISSING (in the second, not in the first)", missing]].forEach (function (pair) { console.log ("\n## " + pair [0]); const g = group (pair [1]); Object.keys (g).sort ().forEach (function (top) { console.log ("- " + top + " (" + g [top].length + ")"); g [top].slice (0, 400).forEach (function (p) { console.log (" " + p); }); }); });
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment