Skip to content

Instantly share code, notes, and snippets.

@btaillon-coveo
Created September 29, 2023 17:57
Show Gist options
  • Save btaillon-coveo/c356eb606f991a9a0b9adcb8e85e625e to your computer and use it in GitHub Desktop.
Save btaillon-coveo/c356eb606f991a9a0b9adcb8e85e625e to your computer and use it in GitHub Desktop.
compare nx runs
import { resolve, parse } from "node:path";
import { readFile, writeFile } from "node:fs/promises";
/**
* @typedef File
* @property {'file'} type
*/
/**
* @typedef Directory
* @property {'directory'} type
* @property {{ [name: string]: Graph }} contents
*/
/**
* @typedef {File | Directory} Graph
*/
/**
* @typedef DiffAdded
* @property {'added'} type
* @property {string[]} added
*/
/**
* @typedef DiffRemoved
* @property {'removed'} type
* @property {string[]} removed
*/
/**
* @typedef DiffDirectoryContents
* @property {'directory-contents'} type
* @property {{ [name: string]: Differences }} contents
*/
/**
* @typedef DiffTypeChanged
* @property {'type-changed'} type
* @property {Graph['type']} oldType
* @property {Graph['type']} newType
*/
/**
* @typedef {(DiffAdded | DiffRemoved | DiffTypeChanged | DiffDirectoryContents)[]} Differences
*/
/**
* @param {Directory} root
* @param {string} directoryPath
* @returns {Directory}
*/
function getDirectory(root, directoryPath) {
const { dir: parentDirectoryPath } = parse(directoryPath);
const parentDirectory = parentDirectoryPath.trim()
? getDirectory(root, parentDirectoryPath)
: root;
/**
* @type {Graph}
*/
const currentDirectory = parentDirectory.contents[directoryPath];
if (!currentDirectory) {
return (parentDirectory.contents[directoryPath] = {
type: "directory",
contents: {},
});
}
if (currentDirectory.type !== "directory") {
throw "expected directory";
}
return currentDirectory;
}
/**
* @param {string[]} filePaths
* @returns {Directory}
*/
function buildGraph(filePaths) {
/**
* @type {Directory}
*/
const root = { type: "directory", contents: {} };
for (const filePath of filePaths) {
const { dir: directoryPath } = parse(filePath);
getDirectory(root, directoryPath).contents[filePath] = { type: "file" };
}
return root;
}
/**
* @param {Graph} graphA
* @param {Graph} graphB
* @returns {Promise<Differences>}
*/
async function compareGraphs(graphA, graphB) {
/**
* @type {Differences}
*/
const differences = [];
if (graphA.type !== graphB.type) {
differences.push({
type: "type-changed",
oldType: graphA.type,
newType: graphB.type,
});
return differences;
}
if (graphA.type !== "directory") {
return differences;
}
/**
* @type {DiffAdded['added']}
*/
const added = [];
/**
* @type {DiffRemoved['removed']}
*/
const removed = [];
/**
* @type {DiffDirectoryContents['contents']}
*/
const contentsWithDifferences = {};
for (const childName of Array.from(
new Set([...Object.keys(graphA.contents), ...Object.keys(graphB.contents)])
)) {
if (!(childName in graphA.contents)) {
added.push(childName);
continue;
}
if (!(childName in graphB.contents)) {
removed.push(childName);
continue;
}
const childDifferences = await compareGraphs(
graphA.contents[childName],
graphB.contents[childName]
);
if (childDifferences.length) {
contentsWithDifferences[childName] = childDifferences;
}
}
if (added.length) {
differences.push({ type: "added", added });
}
if (removed.length) {
differences.push({ type: "removed", removed });
}
if (Object.keys(contentsWithDifferences).length) {
differences.push({
type: "directory-contents",
contents: contentsWithDifferences,
});
}
return differences;
}
/**
* @param {string} snapshotJsonPath
* @returns {Promise<Record<string, Directory>>}
*/
async function buildGraphsForFile(snapshotJsonPath) {
const contentsBuffer = await readFile(snapshotJsonPath);
/**
* @type {Record<string, string[]>}
*/
const projects = JSON.parse(contentsBuffer.toString());
/**
* @type {Record<string, Directory>}
*/
const newMap = {};
for (const [projectName, projectFileNames] of Object.entries(projects)) {
newMap[projectName] = buildGraph(projectFileNames);
}
return newMap;
}
/**
*
* @param {string} snapshotJsonPathA
* @param {string} snapshotJsonPathB
* @param {string} outputPath
*/
async function compareFiles(snapshotJsonPathA, snapshotJsonPathB, outputPath) {
console.log("Building graph A");
const graphsA = await buildGraphsForFile(snapshotJsonPathA);
console.log("Building graph B");
const graphsB = await buildGraphsForFile(snapshotJsonPathB);
const comparison = {};
for (const key of Array.from(
new Set([...Object.keys(graphsA), ...Object.keys(graphsB)])
)) {
console.log("Comparing", key);
comparison[key] = await compareGraphs(graphsA[key], graphsB[key]);
}
console.log("Writing to", outputPath);
await writeFile(outputPath, JSON.stringify(comparison, undefined, " "));
console.log("Done!");
}
compareFiles(
resolve("before.json"),
resolve("after.json"),
resolve("diff.json")
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment