Skip to content

Commit 3f0adc5

Browse files
dmealingclaude
andcommitted
feat(cli): snapshot path + diff helpers [FR-004]
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 7304761 commit 3f0adc5

2 files changed

Lines changed: 78 additions & 0 deletions

File tree

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Path + diff helpers for `meta prompt-snapshot`. Pure (no I/O, no metadata) so
2+
// they unit-test trivially; the command does the filesystem work.
3+
4+
import { join } from "node:path";
5+
6+
export interface SnapshotPaths {
7+
/** The per-template snapshot directory: <cwd>/.metaobjects/snapshots/<name>. */
8+
dir: string;
9+
/** The committed fixture payload (author-owned input). */
10+
payloadPath: string;
11+
/** The golden rendered output (tool-managed, byte-exact). */
12+
snapPath: string;
13+
}
14+
15+
export function snapshotPaths(cwd: string, templateName: string): SnapshotPaths {
16+
const dir = join(cwd, ".metaobjects", "snapshots", templateName);
17+
return {
18+
dir,
19+
payloadPath: join(dir, "payload.json"),
20+
snapPath: join(dir, "output.snap"),
21+
};
22+
}
23+
24+
// A compact line diff: trim the common leading/trailing lines, then show the
25+
// differing middle as `- <expected>` followed by `+ <actual>`. Enough to make
26+
// drift reviewable in CI output without pulling in a diff dependency.
27+
export function unifiedDiff(expected: string, actual: string): string {
28+
const e = expected.split("\n");
29+
const a = actual.split("\n");
30+
31+
let pre = 0;
32+
while (pre < e.length && pre < a.length && e[pre] === a[pre]) pre++;
33+
34+
let suf = 0;
35+
while (
36+
suf < e.length - pre &&
37+
suf < a.length - pre &&
38+
e[e.length - 1 - suf] === a[a.length - 1 - suf]
39+
) {
40+
suf++;
41+
}
42+
43+
const eMid = e.slice(pre, e.length - suf);
44+
const aMid = a.slice(pre, a.length - suf);
45+
46+
const out: string[] = [`@@ line ${pre + 1} @@`];
47+
for (const line of eMid) out.push(`- ${line}`);
48+
for (const line of aMid) out.push(`+ ${line}`);
49+
return out.join("\n");
50+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { describe, test, expect } from "bun:test";
2+
import { join } from "node:path";
3+
import { snapshotPaths, unifiedDiff } from "../../src/lib/snapshot.js";
4+
5+
describe("snapshotPaths", () => {
6+
test("resolves the per-template dir + payload + golden under .metaobjects/snapshots", () => {
7+
const p = snapshotPaths("/proj", "Greeting");
8+
expect(p.dir).toBe(join("/proj", ".metaobjects", "snapshots", "Greeting"));
9+
expect(p.payloadPath).toBe(join("/proj", ".metaobjects", "snapshots", "Greeting", "payload.json"));
10+
expect(p.snapPath).toBe(join("/proj", ".metaobjects", "snapshots", "Greeting", "output.snap"));
11+
});
12+
});
13+
14+
describe("unifiedDiff", () => {
15+
test("identical strings produce no -/+ lines", () => {
16+
const d = unifiedDiff("a\nb\nc", "a\nb\nc");
17+
expect(d).not.toContain("\n- ");
18+
expect(d).not.toContain("\n+ ");
19+
});
20+
test("shows the differing region with - (expected) then + (actual)", () => {
21+
const d = unifiedDiff("a\nOLD\nc", "a\nNEW\nc");
22+
expect(d).toContain("- OLD");
23+
expect(d).toContain("+ NEW");
24+
// common leading/trailing lines are trimmed from the diff body
25+
expect(d).not.toContain("- a");
26+
expect(d).not.toContain("+ c");
27+
});
28+
});

0 commit comments

Comments
 (0)