@geonosis/visual-diff 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
  // Committed, so `pnpm install` can link the bin on a fresh clone — before `pnpm build` has
3
3
  // produced dist/. A bin that only exists after a build is a bin that is missing when you need it.
4
- import '../dist/cli.js'
4
+ //
5
+ // In the repo, `src/` sits beside `dist/`, and a dist older than src answered for a fix it did not
6
+ // carry once (#62). The published package ships no src, so there the check is skipped.
7
+ import { existsSync, readdirSync, statSync } from 'node:fs'
8
+ import { dirname, join } from 'node:path'
9
+ import { fileURLToPath } from 'node:url'
10
+
11
+ const here = dirname(fileURLToPath(import.meta.url))
12
+ const newest = (dir) =>
13
+ existsSync(dir)
14
+ ? readdirSync(dir, { withFileTypes: true }).reduce((most, entry) => {
15
+ const at = join(dir, entry.name)
16
+ return Math.max(most, entry.isDirectory() ? newest(at) : statSync(at).mtimeMs)
17
+ }, 0)
18
+ : 0
19
+ const src = join(here, '..', 'src')
20
+ if (existsSync(src) && newest(src) > newest(join(here, '..', 'dist'))) {
21
+ process.stderr.write(
22
+ 'geonosis-visual-diff: dist is older than src — run pnpm build before trusting this bin.\n',
23
+ )
24
+ process.exit(2)
25
+ }
26
+
27
+ await import('../dist/cli.js')
@@ -0,0 +1,149 @@
1
+ /**
2
+ * The per-pixel colour threshold handed to pixelmatch, 0–1, smaller is stricter.
3
+ *
4
+ * `0` is strict per-pixel equality: any channel that moved by one unit is a difference. That is
5
+ * what a parity claim ("these two renders are the same") has to be measured at.
6
+ * `0.1` — pixelmatch's own default, and this one — absorbs the sub-pixel noise two renders of the
7
+ * same layout produce on different machines.
8
+ */
9
+ declare const DEFAULT_THRESHOLD = 0.1;
10
+ /**
11
+ * The fraction of a frame allowed to differ before the frame FAILS. A SECOND, orthogonal knob:
12
+ * `threshold` decides whether ONE pixel counts, `mismatchRatio` decides how many counted pixels a
13
+ * frame may carry. Folding them into one number loses the distinction.
14
+ */
15
+ declare const DEFAULT_MISMATCH_RATIO = 0.005;
16
+ interface CompareOptions {
17
+ /** Per-pixel colour threshold, 0–1. Defaults to {@link DEFAULT_THRESHOLD}. */
18
+ readonly threshold?: number;
19
+ /** Count anti-aliased pixels as differences. Off by default, as in pixelmatch. */
20
+ readonly includeAA?: boolean;
21
+ }
22
+ interface CompareResult {
23
+ /** The height of the actual frame. */
24
+ readonly actualHeight: number;
25
+ /** The width of the actual frame. */
26
+ readonly actualWidth: number;
27
+ /** True when both frames decode to the same width × height. */
28
+ readonly dimensionsMatch: boolean;
29
+ /** The rendered diff PNG — null when the dimensions differ and there is nothing to render. */
30
+ readonly diff: Buffer | null;
31
+ /** The height of the baseline frame. */
32
+ readonly height: number;
33
+ /** Pixels counted as different. 0 when the dimensions differ — read `dimensionsMatch` first. */
34
+ readonly mismatch: number;
35
+ /** mismatch / total, as a fraction 0–1. 1 when the dimensions differ. */
36
+ readonly ratio: number;
37
+ /** The baseline frame's pixel count. */
38
+ readonly total: number;
39
+ /** The width of the baseline frame. */
40
+ readonly width: number;
41
+ }
42
+ /**
43
+ * Decode and pixel-diff two PNG buffers.
44
+ *
45
+ * A dimension mismatch is a hard fail — `dimensionsMatch: false`, `ratio: 1`, no diff image. It is
46
+ * reported, not thrown, because the frame-by-frame caller wants an outcome per frame rather than an
47
+ * exception that ends the run.
48
+ */
49
+ declare function comparePng(baseline: Buffer, actual: Buffer, options?: CompareOptions): CompareResult;
50
+ /** Decide pass/fail from a compare result against the frame budget. A dimension mismatch fails at
51
+ * every budget, including 1 — a frame that changed shape is not a frame that drifted a little. */
52
+ declare function isWithinBudget(result: CompareResult, mismatchRatio?: number): boolean;
53
+
54
+ type FrameStatus = 'created' | 'fail' | 'missing-baseline' | 'pass' | 'updated';
55
+ interface BaselineDirs {
56
+ /** Where the failing actual and diff PNGs are written. */
57
+ readonly artifactsDir: string;
58
+ /** Where the committed baseline PNGs live. */
59
+ readonly baselineDir: string;
60
+ }
61
+ interface ReconcileOptions extends CompareOptions {
62
+ /** The fraction of the frame allowed to differ. Defaults to {@link DEFAULT_MISMATCH_RATIO}. */
63
+ readonly mismatchRatio?: number;
64
+ /** Write the baseline instead of checking against it. */
65
+ readonly update?: boolean;
66
+ }
67
+ interface FrameOutcome {
68
+ readonly baselinePath: string;
69
+ readonly name: string;
70
+ /** The comparison, when one was run — null in update mode and when no baseline exists. */
71
+ readonly result: CompareResult | null;
72
+ readonly status: FrameStatus;
73
+ }
74
+ /**
75
+ * Reconcile one captured frame against the baseline stored under its name.
76
+ *
77
+ * `update` writes the baseline — `created` when there was none, `updated` when there was. Without
78
+ * it the frame is compared: `missing-baseline` when nothing is stored (never a silent pass), then
79
+ * `pass` or `fail`. A failure leaves `<name>.actual.png` and `<name>.diff.png` in `artifactsDir`.
80
+ */
81
+ declare function reconcileFrame(name: string, actual: Buffer, dirs: BaselineDirs, options?: ReconcileOptions): FrameOutcome;
82
+ /** One line saying what happened to a frame, and what to do about it. */
83
+ declare function describeOutcome(outcome: FrameOutcome): string;
84
+
85
+ /** A named viewport. `height` matters only for above-the-fold captures; a full-page screenshot
86
+ * ignores it, but a fixed value keeps a partial capture deterministic. */
87
+ interface Breakpoint {
88
+ readonly height: number;
89
+ readonly name: string;
90
+ readonly width: number;
91
+ }
92
+ /** A surface to capture. `waitFor` and `mask` are consumer-defined selectors — the kit passes them
93
+ * through and never interprets them. */
94
+ interface VisualTarget {
95
+ /** An optional per-target grid. Omitted → the fallback grid. */
96
+ readonly breakpoints?: readonly Breakpoint[];
97
+ /** A stable id used to build the baseline filename. */
98
+ readonly id: string;
99
+ /** Selectors for regions to blank before capture, so a volatile region cannot redden a frame. */
100
+ readonly mask?: readonly string[];
101
+ /** Where the surface lives, in whatever form the consumer's capture step understands. */
102
+ readonly path: string;
103
+ /** A selector proving the surface finished rendering. */
104
+ readonly waitFor: string;
105
+ }
106
+ /** A five-width fallback grid, at the widths CSS frameworks converge on. A DEFAULT, not a fact
107
+ * about any repo — pass your own grid to `breakpointsFor` when yours differs. */
108
+ declare const DEFAULT_BREAKPOINTS: readonly Breakpoint[];
109
+ /** The breakpoints a target commits baselines for: its own, or the fallback grid. */
110
+ declare function breakpointsFor(target: VisualTarget, fallback?: readonly Breakpoint[]): readonly Breakpoint[];
111
+ /** The baseline filename for a surface at a breakpoint. */
112
+ declare function baselineName(surface: string, breakpoint: string): string;
113
+
114
+ type Verdict = 'IDENTICAL' | 'MINOR_DIFF' | 'NEAR_IDENTICAL' | 'SIGNIFICANT_DIFF' | 'SIMILAR';
115
+ interface DiffOptions extends CompareOptions {
116
+ /** Where to write the rendered diff PNG. Omitted → no diff image is written. */
117
+ readonly outputPath?: string;
118
+ }
119
+ interface CompareInput {
120
+ readonly image1: string;
121
+ readonly image2: string;
122
+ readonly options?: DiffOptions;
123
+ }
124
+ interface DiffResult {
125
+ readonly analysis: string;
126
+ readonly diffImagePath: string | null;
127
+ /** The difference as a PERCENTAGE, 0–100 — not the 0–1 ratio `comparePng` returns. */
128
+ readonly diffPercent: number;
129
+ readonly diffPercentFormatted: string;
130
+ readonly differentPixels: number;
131
+ readonly dimensions: {
132
+ readonly height: number;
133
+ readonly width: number;
134
+ };
135
+ readonly identical: boolean;
136
+ readonly totalPixels: number;
137
+ readonly verdict: Verdict;
138
+ }
139
+ /**
140
+ * Compare two PNG FILES.
141
+ *
142
+ * Throws when an input is missing and when the two differ in size — the caller asked about two
143
+ * frames of the same thing, and two different sizes means the question was wrong, not the answer.
144
+ */
145
+ declare function compare({ image1, image2, options }: CompareInput): Promise<DiffResult>;
146
+ /** The printable block a human reads in a terminal or a CI log. */
147
+ declare function formatReport(result: DiffResult): string;
148
+
149
+ export { type BaselineDirs, type Breakpoint, type CompareInput, type CompareOptions, type CompareResult, DEFAULT_BREAKPOINTS, DEFAULT_MISMATCH_RATIO, DEFAULT_THRESHOLD, type DiffOptions, type DiffResult, type FrameOutcome, type FrameStatus, type ReconcileOptions, type Verdict, type VisualTarget, baselineName, breakpointsFor, compare, comparePng, describeOutcome, formatReport, isWithinBudget, reconcileFrame };
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@geonosis/visual-diff",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
+ "types": "./dist/index.d.ts",
4
5
  "description": "Pixel comparison, reporting and baseline management for visual regression — the union of dielime's and during.day's forks. Capture stays with the consumer; nothing here launches a browser.",
5
6
  "keywords": [
6
7
  "visual-regression",
@@ -23,7 +24,10 @@
23
24
  "geonosis-visual-diff": "bin/geonosis-visual-diff.mjs"
24
25
  },
25
26
  "exports": {
26
- ".": "./dist/index.js"
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "default": "./dist/index.js"
30
+ }
27
31
  },
28
32
  "files": [
29
33
  "bin",