meld
Version:
Meld: A template language for LLM prompts
119 lines (103 loc) • 3.21 kB
text/typescript
import { MemfsTestFileSystem } from './MemfsTestFileSystem.js';
/**
* Result of comparing two filesystem snapshots
*/
export interface SnapshotDiff {
/**
* Files that were added
*/
added: string[];
/**
* Files that were removed
*/
removed: string[];
/**
* Files that were modified
*/
modified: string[];
/**
* Map of modified files to their new contents
*/
modifiedContents: Map<string, string>;
}
/**
* Utility for taking and comparing filesystem snapshots
*/
export class TestSnapshot {
constructor(private fs: MemfsTestFileSystem) {}
/**
* Take a snapshot of the current filesystem state
*/
async takeSnapshot(dir: string = '/'): Promise<Map<string, string>> {
const snapshot = new Map<string, string>();
await this.snapshotDirectory(dir, snapshot);
return snapshot;
}
/**
* Compare two snapshots and return the differences
*/
compare(before: Map<string, string>, after: Map<string, string>): SnapshotDiff {
const result: SnapshotDiff = {
added: [],
removed: [],
modified: [],
modifiedContents: new Map()
};
// Helper to normalize paths with consistent format
const normalizePath = (p: string) => {
// Ensure path has leading slash for consistency
let normalized = p.replace(/^\/project\//, '/');
if (!normalized.startsWith('/')) {
normalized = '/' + normalized;
}
return normalized;
};
// Find added and modified files
for (const [path, content] of after) {
const normalizedPath = normalizePath(path);
const beforePath = Array.from(before.keys()).find(p => normalizePath(p) === normalizedPath);
if (!beforePath) {
result.added.push(normalizedPath);
} else if (before.get(beforePath) !== content) {
result.modified.push(normalizedPath);
result.modifiedContents.set(normalizedPath, content);
}
}
// Find removed files
for (const path of before.keys()) {
const normalizedPath = normalizePath(path);
const afterPath = Array.from(after.keys()).find(p => normalizePath(p) === normalizedPath);
if (!afterPath) {
result.removed.push(normalizedPath);
}
}
// Sort arrays for consistent results
result.added.sort();
result.removed.sort();
result.modified.sort();
return result;
}
/**
* Take a snapshot of a directory and its contents recursively
*/
private async snapshotDirectory(dir: string, snapshot: Map<string, string>): Promise<void> {
try {
const entries = await this.fs.readDir(dir);
for (const entry of entries) {
const fullPath = dir === '/' ? `/${entry}` : `${dir}/${entry}`;
if (await this.fs.isFile(fullPath)) {
const content = await this.fs.readFile(fullPath);
snapshot.set(fullPath, content);
} else if (await this.fs.isDirectory(fullPath)) {
await this.snapshotDirectory(fullPath, snapshot);
}
}
} catch (error) {
if (error.message.includes('ENOENT: no such directory')) {
// Directory doesn't exist, return empty snapshot
return;
}
throw error;
}
}
}