@monoes/monobrowse 1.0.12 → 1.0.13

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.
Files changed (175) hide show
  1. package/dist/src/__tests__/profiler-heap.test.d.ts +2 -0
  2. package/dist/src/__tests__/profiler-heap.test.d.ts.map +1 -0
  3. package/dist/src/__tests__/profiler-heap.test.js +154 -0
  4. package/dist/src/__tests__/profiler-heap.test.js.map +1 -0
  5. package/dist/src/__tests__/report-a11y.test.d.ts +2 -0
  6. package/dist/src/__tests__/report-a11y.test.d.ts.map +1 -0
  7. package/dist/src/__tests__/report-a11y.test.js +147 -0
  8. package/dist/src/__tests__/report-a11y.test.js.map +1 -0
  9. package/dist/src/__tests__/report-analyze.test.d.ts +2 -0
  10. package/dist/src/__tests__/report-analyze.test.d.ts.map +1 -0
  11. package/dist/src/__tests__/report-analyze.test.js +157 -0
  12. package/dist/src/__tests__/report-analyze.test.js.map +1 -0
  13. package/dist/src/__tests__/report-budget.test.d.ts +2 -0
  14. package/dist/src/__tests__/report-budget.test.d.ts.map +1 -0
  15. package/dist/src/__tests__/report-budget.test.js +80 -0
  16. package/dist/src/__tests__/report-budget.test.js.map +1 -0
  17. package/dist/src/__tests__/report-diff.test.d.ts +2 -0
  18. package/dist/src/__tests__/report-diff.test.d.ts.map +1 -0
  19. package/dist/src/__tests__/report-diff.test.js +231 -0
  20. package/dist/src/__tests__/report-diff.test.js.map +1 -0
  21. package/dist/src/__tests__/report-evidence.test.d.ts +2 -0
  22. package/dist/src/__tests__/report-evidence.test.d.ts.map +1 -0
  23. package/dist/src/__tests__/report-evidence.test.js +214 -0
  24. package/dist/src/__tests__/report-evidence.test.js.map +1 -0
  25. package/dist/src/__tests__/report-flake.test.d.ts +2 -0
  26. package/dist/src/__tests__/report-flake.test.d.ts.map +1 -0
  27. package/dist/src/__tests__/report-flake.test.js +182 -0
  28. package/dist/src/__tests__/report-flake.test.js.map +1 -0
  29. package/dist/src/__tests__/report-history.test.d.ts +2 -0
  30. package/dist/src/__tests__/report-history.test.d.ts.map +1 -0
  31. package/dist/src/__tests__/report-history.test.js +229 -0
  32. package/dist/src/__tests__/report-history.test.js.map +1 -0
  33. package/dist/src/__tests__/report-output.test.d.ts +2 -0
  34. package/dist/src/__tests__/report-output.test.d.ts.map +1 -0
  35. package/dist/src/__tests__/report-output.test.js +169 -0
  36. package/dist/src/__tests__/report-output.test.js.map +1 -0
  37. package/dist/src/__tests__/report-png.test.d.ts +2 -0
  38. package/dist/src/__tests__/report-png.test.d.ts.map +1 -0
  39. package/dist/src/__tests__/report-png.test.js +84 -0
  40. package/dist/src/__tests__/report-png.test.js.map +1 -0
  41. package/dist/src/__tests__/report-render-extras.test.d.ts +2 -0
  42. package/dist/src/__tests__/report-render-extras.test.d.ts.map +1 -0
  43. package/dist/src/__tests__/report-render-extras.test.js +285 -0
  44. package/dist/src/__tests__/report-render-extras.test.js.map +1 -0
  45. package/dist/src/__tests__/report-render.test.d.ts +2 -0
  46. package/dist/src/__tests__/report-render.test.d.ts.map +1 -0
  47. package/dist/src/__tests__/report-render.test.js +143 -0
  48. package/dist/src/__tests__/report-render.test.js.map +1 -0
  49. package/dist/src/__tests__/report-run.test.d.ts +2 -0
  50. package/dist/src/__tests__/report-run.test.d.ts.map +1 -0
  51. package/dist/src/__tests__/report-run.test.js +234 -0
  52. package/dist/src/__tests__/report-run.test.js.map +1 -0
  53. package/dist/src/browser/index.d.ts +1 -0
  54. package/dist/src/browser/index.d.ts.map +1 -1
  55. package/dist/src/browser/index.js +1 -0
  56. package/dist/src/browser/index.js.map +1 -1
  57. package/dist/src/browser/profiler.d.ts.map +1 -1
  58. package/dist/src/browser/profiler.js +19 -22
  59. package/dist/src/browser/profiler.js.map +1 -1
  60. package/dist/src/cli/commands.d.ts.map +1 -1
  61. package/dist/src/cli/commands.js +254 -0
  62. package/dist/src/cli/commands.js.map +1 -1
  63. package/dist/src/report/a11y.d.ts +52 -0
  64. package/dist/src/report/a11y.d.ts.map +1 -0
  65. package/dist/src/report/a11y.js +231 -0
  66. package/dist/src/report/a11y.js.map +1 -0
  67. package/dist/src/report/analyze.d.ts +20 -0
  68. package/dist/src/report/analyze.d.ts.map +1 -0
  69. package/dist/src/report/analyze.js +136 -0
  70. package/dist/src/report/analyze.js.map +1 -0
  71. package/dist/src/report/budget.d.ts +27 -0
  72. package/dist/src/report/budget.d.ts.map +1 -0
  73. package/dist/src/report/budget.js +143 -0
  74. package/dist/src/report/budget.js.map +1 -0
  75. package/dist/src/report/collect-a11y.d.ts +25 -0
  76. package/dist/src/report/collect-a11y.d.ts.map +1 -0
  77. package/dist/src/report/collect-a11y.js +154 -0
  78. package/dist/src/report/collect-a11y.js.map +1 -0
  79. package/dist/src/report/collect.d.ts +45 -0
  80. package/dist/src/report/collect.d.ts.map +1 -0
  81. package/dist/src/report/collect.js +275 -0
  82. package/dist/src/report/collect.js.map +1 -0
  83. package/dist/src/report/evidence.d.ts +80 -0
  84. package/dist/src/report/evidence.d.ts.map +1 -0
  85. package/dist/src/report/evidence.js +279 -0
  86. package/dist/src/report/evidence.js.map +1 -0
  87. package/dist/src/report/flake.d.ts +88 -0
  88. package/dist/src/report/flake.d.ts.map +1 -0
  89. package/dist/src/report/flake.js +232 -0
  90. package/dist/src/report/flake.js.map +1 -0
  91. package/dist/src/report/history.d.ts +85 -0
  92. package/dist/src/report/history.d.ts.map +1 -0
  93. package/dist/src/report/history.js +176 -0
  94. package/dist/src/report/history.js.map +1 -0
  95. package/dist/src/report/index.d.ts +102 -0
  96. package/dist/src/report/index.d.ts.map +1 -0
  97. package/dist/src/report/index.js +216 -0
  98. package/dist/src/report/index.js.map +1 -0
  99. package/dist/src/report/pixel-diff.d.ts +30 -0
  100. package/dist/src/report/pixel-diff.d.ts.map +1 -0
  101. package/dist/src/report/pixel-diff.js +175 -0
  102. package/dist/src/report/pixel-diff.js.map +1 -0
  103. package/dist/src/report/png.d.ts +28 -0
  104. package/dist/src/report/png.d.ts.map +1 -0
  105. package/dist/src/report/png.js +259 -0
  106. package/dist/src/report/png.js.map +1 -0
  107. package/dist/src/report/render-evidence.d.ts +16 -0
  108. package/dist/src/report/render-evidence.d.ts.map +1 -0
  109. package/dist/src/report/render-evidence.js +174 -0
  110. package/dist/src/report/render-evidence.js.map +1 -0
  111. package/dist/src/report/render-trend.d.ts +16 -0
  112. package/dist/src/report/render-trend.d.ts.map +1 -0
  113. package/dist/src/report/render-trend.js +194 -0
  114. package/dist/src/report/render-trend.js.map +1 -0
  115. package/dist/src/report/render.d.ts +21 -0
  116. package/dist/src/report/render.d.ts.map +1 -0
  117. package/dist/src/report/render.js +286 -0
  118. package/dist/src/report/render.js.map +1 -0
  119. package/dist/src/report/run-diff.d.ts +18 -0
  120. package/dist/src/report/run-diff.d.ts.map +1 -0
  121. package/dist/src/report/run-diff.js +51 -0
  122. package/dist/src/report/run-diff.js.map +1 -0
  123. package/dist/src/report/structure.d.ts +41 -0
  124. package/dist/src/report/structure.d.ts.map +1 -0
  125. package/dist/src/report/structure.js +210 -0
  126. package/dist/src/report/structure.js.map +1 -0
  127. package/dist/src/report/trend.d.ts +26 -0
  128. package/dist/src/report/trend.d.ts.map +1 -0
  129. package/dist/src/report/trend.js +164 -0
  130. package/dist/src/report/trend.js.map +1 -0
  131. package/dist/src/report/types.d.ts +320 -0
  132. package/dist/src/report/types.d.ts.map +1 -0
  133. package/dist/src/report/types.js +11 -0
  134. package/dist/src/report/types.js.map +1 -0
  135. package/dist/src/report/util.d.ts +17 -0
  136. package/dist/src/report/util.d.ts.map +1 -0
  137. package/dist/src/report/util.js +45 -0
  138. package/dist/src/report/util.js.map +1 -0
  139. package/dist/tsconfig.tsbuildinfo +1 -1
  140. package/package.json +1 -1
  141. package/src/__tests__/profiler-heap.test.ts +191 -0
  142. package/src/__tests__/report-a11y.test.ts +180 -0
  143. package/src/__tests__/report-analyze.test.ts +202 -0
  144. package/src/__tests__/report-budget.test.ts +92 -0
  145. package/src/__tests__/report-diff.test.ts +272 -0
  146. package/src/__tests__/report-evidence.test.ts +276 -0
  147. package/src/__tests__/report-flake.test.ts +214 -0
  148. package/src/__tests__/report-history.test.ts +293 -0
  149. package/src/__tests__/report-output.test.ts +210 -0
  150. package/src/__tests__/report-png.test.ts +95 -0
  151. package/src/__tests__/report-render-extras.test.ts +315 -0
  152. package/src/__tests__/report-render.test.ts +175 -0
  153. package/src/__tests__/report-run.test.ts +264 -0
  154. package/src/browser/index.ts +1 -0
  155. package/src/browser/profiler.ts +28 -21
  156. package/src/cli/commands.ts +272 -0
  157. package/src/report/a11y.ts +242 -0
  158. package/src/report/analyze.ts +181 -0
  159. package/src/report/budget.ts +155 -0
  160. package/src/report/collect-a11y.ts +200 -0
  161. package/src/report/collect.ts +408 -0
  162. package/src/report/evidence.ts +379 -0
  163. package/src/report/flake.ts +303 -0
  164. package/src/report/history.ts +219 -0
  165. package/src/report/index.ts +307 -0
  166. package/src/report/pixel-diff.ts +197 -0
  167. package/src/report/png.ts +290 -0
  168. package/src/report/render-evidence.ts +194 -0
  169. package/src/report/render-trend.ts +225 -0
  170. package/src/report/render.ts +314 -0
  171. package/src/report/run-diff.ts +63 -0
  172. package/src/report/structure.ts +212 -0
  173. package/src/report/trend.ts +189 -0
  174. package/src/report/types.ts +356 -0
  175. package/src/report/util.ts +48 -0
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Persistent run history for `monobrowse report` (RIG-13).
3
+ *
4
+ * Every run is appended to a per-URL directory under the monomind data dir,
5
+ * so the next run can say "LCP has crept up 400ms over nine runs" instead of
6
+ * reporting a number with no context. Deliberately not a database: one JSON
7
+ * file per run plus its screenshot is greppable, diffable, rsync-able, and
8
+ * survives this tool being uninstalled.
9
+ *
10
+ * ~/.monomind/browser-reports/
11
+ * shop.test-checkout-1f4c9a2b/
12
+ * url.txt <- the full URL, for humans
13
+ * 20260921T100000Z-8f2a1c4d.json <- HistoryRun
14
+ * 20260921T100000Z-8f2a1c4d.png <- main screenshot, for the diff
15
+ *
16
+ * Set `MONOBROWSE_HISTORY_DIR` to relocate the root (the tests do).
17
+ */
18
+ import type { ImageSize, Report, StructureNode, VerdictValue, VitalsData } from './types.js';
19
+ /** Runs kept per URL before the oldest are pruned. Override with --history-max. */
20
+ export declare const DEFAULT_HISTORY_MAX = 20;
21
+ export interface HistoryRun {
22
+ /** `<compact ISO timestamp>-<8 hex>`, also the basename of its files. */
23
+ id: string;
24
+ url: string;
25
+ finalUrl: string;
26
+ title: string;
27
+ capturedAt: string;
28
+ durationMs: number;
29
+ verdict: VerdictValue;
30
+ counts: Report['counts'];
31
+ vitals: VitalsData;
32
+ failures: Array<{
33
+ budget: string;
34
+ expected: string;
35
+ actual: string;
36
+ }>;
37
+ /** `<rule>@<locator>` per finding, so a11y regressions are attributable. */
38
+ a11ySignatures: string[];
39
+ structure: StructureNode[];
40
+ /** Basename of the sibling PNG, when one was archived. */
41
+ screenshotFile?: string;
42
+ screenshotSize?: ImageSize;
43
+ }
44
+ export declare function historyRoot(): string;
45
+ /**
46
+ * Directory name for a URL: readable prefix plus a hash of the full URL.
47
+ *
48
+ * The hash is what makes it correct — `?page=2` and `?page=3` must not share
49
+ * a history — and the prefix is what makes `ls` useful.
50
+ */
51
+ export declare function historyKey(url: string): string;
52
+ export declare function historyDirFor(url: string, root?: string): string;
53
+ export declare function compactTimestamp(iso: string): string;
54
+ export declare function newRunId(capturedAt: string): string;
55
+ /** The record we persist — a Report minus the megabytes of inline base64. */
56
+ export declare function toHistoryRun(report: Report, id: string): HistoryRun;
57
+ /**
58
+ * Every run for a URL, oldest first.
59
+ *
60
+ * A file that will not parse is skipped rather than thrown: a half-written
61
+ * record from a killed process must not permanently break the next report.
62
+ */
63
+ export declare function listRuns(dir: string): Promise<HistoryRun[]>;
64
+ /** Delete all but the newest `max` runs, taking each one's PNG with it. */
65
+ export declare function pruneHistory(dir: string, max: number): Promise<string[]>;
66
+ export interface SaveRunOptions {
67
+ root?: string;
68
+ max?: number;
69
+ /** Fixed id, for deterministic tests. */
70
+ id?: string;
71
+ }
72
+ export interface SaveRunResult {
73
+ dir: string;
74
+ file: string;
75
+ run: HistoryRun;
76
+ pruned: string[];
77
+ }
78
+ /**
79
+ * Append this run to its URL's history and prune the tail. Callers treat a
80
+ * rejection as non-fatal — a read-only home directory should cost you the
81
+ * trend section, not the report.
82
+ */
83
+ export declare function saveRun(report: Report, options?: SaveRunOptions): Promise<SaveRunResult>;
84
+ export declare function loadRunScreenshot(dir: string, run: HistoryRun): Promise<Uint8Array | null>;
85
+ //# sourceMappingURL=history.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../../../src/report/history.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAOH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7F,mFAAmF;AACnF,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAStC,MAAM,WAAW,UAAU;IACzB,yEAAyE;IACzE,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,YAAY,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IACzB,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtE,4EAA4E;IAC5E,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,0DAA0D;IAC1D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,SAAS,CAAC;CAC5B;AAED,wBAAgB,WAAW,IAAI,MAAM,CAIpC;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAY9C;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,SAAgB,GAAG,MAAM,CAEvE;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wBAAgB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,6EAA6E;AAC7E,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,UAAU,CAmBnE;AAID;;;;;GAKG;AACH,wBAAsB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAiBjE;AAED,2EAA2E;AAC3E,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAc9E;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,UAAU,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAsB,OAAO,CAC3B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,aAAa,CAAC,CA4BxB;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAOhG"}
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Persistent run history for `monobrowse report` (RIG-13).
3
+ *
4
+ * Every run is appended to a per-URL directory under the monomind data dir,
5
+ * so the next run can say "LCP has crept up 400ms over nine runs" instead of
6
+ * reporting a number with no context. Deliberately not a database: one JSON
7
+ * file per run plus its screenshot is greppable, diffable, rsync-able, and
8
+ * survives this tool being uninstalled.
9
+ *
10
+ * ~/.monomind/browser-reports/
11
+ * shop.test-checkout-1f4c9a2b/
12
+ * url.txt <- the full URL, for humans
13
+ * 20260921T100000Z-8f2a1c4d.json <- HistoryRun
14
+ * 20260921T100000Z-8f2a1c4d.png <- main screenshot, for the diff
15
+ *
16
+ * Set `MONOBROWSE_HISTORY_DIR` to relocate the root (the tests do).
17
+ */
18
+ import { createHash, randomBytes } from 'node:crypto';
19
+ import { mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';
20
+ import { homedir } from 'node:os';
21
+ import { join } from 'node:path';
22
+ import { pngFromDataUrl } from './png.js';
23
+ /** Runs kept per URL before the oldest are pruned. Override with --history-max. */
24
+ export const DEFAULT_HISTORY_MAX = 20;
25
+ /**
26
+ * Screenshots above this are not archived for the diff. A 30MB full-page PNG
27
+ * per run would turn a 20-run history into 600MB on someone's laptop, and the
28
+ * pixel diff is the only consumer.
29
+ */
30
+ const MAX_ARCHIVED_SCREENSHOT_BYTES = 8 * 1024 * 1024;
31
+ export function historyRoot() {
32
+ const override = process.env.MONOBROWSE_HISTORY_DIR;
33
+ if (override?.trim())
34
+ return override.trim();
35
+ return join(homedir(), '.monomind', 'browser-reports');
36
+ }
37
+ /**
38
+ * Directory name for a URL: readable prefix plus a hash of the full URL.
39
+ *
40
+ * The hash is what makes it correct — `?page=2` and `?page=3` must not share
41
+ * a history — and the prefix is what makes `ls` useful.
42
+ */
43
+ export function historyKey(url) {
44
+ const hash = createHash('sha256').update(url).digest('hex').slice(0, 8);
45
+ let readable;
46
+ try {
47
+ const parsed = new URL(url);
48
+ const path = parsed.pathname.replace(/\/+$/, '').replace(/[^a-z0-9]+/gi, '-');
49
+ readable = `${parsed.hostname}${path}`;
50
+ }
51
+ catch {
52
+ readable = url.replace(/[^a-z0-9]+/gi, '-');
53
+ }
54
+ readable = readable.replace(/-+/g, '-').replace(/^-|-$/g, '').toLowerCase().slice(0, 60);
55
+ return `${readable || 'page'}-${hash}`;
56
+ }
57
+ export function historyDirFor(url, root = historyRoot()) {
58
+ return join(root, historyKey(url));
59
+ }
60
+ export function compactTimestamp(iso) {
61
+ return iso.replace(/[-:]/g, '').replace(/\.\d+Z$/, 'Z');
62
+ }
63
+ export function newRunId(capturedAt) {
64
+ return `${compactTimestamp(capturedAt)}-${randomBytes(4).toString('hex')}`;
65
+ }
66
+ /** The record we persist — a Report minus the megabytes of inline base64. */
67
+ export function toHistoryRun(report, id) {
68
+ return {
69
+ id,
70
+ url: report.url,
71
+ finalUrl: report.finalUrl,
72
+ title: report.title,
73
+ capturedAt: report.capturedAt,
74
+ durationMs: report.durationMs,
75
+ verdict: report.verdict,
76
+ counts: report.counts,
77
+ vitals: report.vitals,
78
+ failures: report.failures.map((f) => ({
79
+ budget: String(f.budget),
80
+ expected: f.expected,
81
+ actual: f.actual,
82
+ })),
83
+ a11ySignatures: report.a11y.map((f) => `${f.rule}@${f.locator}`),
84
+ structure: report.structure ?? [],
85
+ };
86
+ }
87
+ const RUN_FILE = /^(\d{8}T\d{6}Z)-([0-9a-f]{8})\.json$/;
88
+ /**
89
+ * Every run for a URL, oldest first.
90
+ *
91
+ * A file that will not parse is skipped rather than thrown: a half-written
92
+ * record from a killed process must not permanently break the next report.
93
+ */
94
+ export async function listRuns(dir) {
95
+ let names;
96
+ try {
97
+ names = await readdir(dir);
98
+ }
99
+ catch {
100
+ return [];
101
+ }
102
+ const runs = [];
103
+ for (const name of names.filter((n) => RUN_FILE.test(n)).sort()) {
104
+ try {
105
+ const parsed = JSON.parse(await readFile(join(dir, name), 'utf8'));
106
+ if (parsed && typeof parsed.capturedAt === 'string')
107
+ runs.push(parsed);
108
+ }
109
+ catch {
110
+ // Unreadable record — ignore it and carry on.
111
+ }
112
+ }
113
+ return runs.sort((a, b) => a.capturedAt.localeCompare(b.capturedAt));
114
+ }
115
+ /** Delete all but the newest `max` runs, taking each one's PNG with it. */
116
+ export async function pruneHistory(dir, max) {
117
+ if (!Number.isFinite(max) || max <= 0)
118
+ return [];
119
+ const runs = await listRuns(dir);
120
+ if (runs.length <= max)
121
+ return [];
122
+ const doomed = runs.slice(0, runs.length - max);
123
+ const removed = [];
124
+ for (const run of doomed) {
125
+ for (const file of [`${run.id}.json`, run.screenshotFile]) {
126
+ if (!file)
127
+ continue;
128
+ await rm(join(dir, file), { force: true }).catch(() => { });
129
+ removed.push(file);
130
+ }
131
+ }
132
+ return removed;
133
+ }
134
+ /**
135
+ * Append this run to its URL's history and prune the tail. Callers treat a
136
+ * rejection as non-fatal — a read-only home directory should cost you the
137
+ * trend section, not the report.
138
+ */
139
+ export async function saveRun(report, options = {}) {
140
+ const root = options.root ?? historyRoot();
141
+ const dir = join(root, historyKey(report.url));
142
+ await mkdir(dir, { recursive: true });
143
+ await writeFile(join(dir, 'url.txt'), `${report.url}\n`, 'utf8').catch(() => { });
144
+ const id = options.id ?? newRunId(report.capturedAt);
145
+ const run = toHistoryRun(report, id);
146
+ const main = report.screenshots.find((s) => s.label === 'page') ?? report.screenshots[0];
147
+ if (main?.dataUrl) {
148
+ try {
149
+ const bytes = pngFromDataUrl(main.dataUrl);
150
+ if (bytes.length <= MAX_ARCHIVED_SCREENSHOT_BYTES) {
151
+ const file = `${id}.png`;
152
+ await writeFile(join(dir, file), bytes);
153
+ run.screenshotFile = file;
154
+ run.screenshotSize = { width: main.width, height: main.height };
155
+ }
156
+ }
157
+ catch {
158
+ // No archived screenshot means no pixel diff next run. Not fatal.
159
+ }
160
+ }
161
+ const file = join(dir, `${id}.json`);
162
+ await writeFile(file, `${JSON.stringify(run, null, 2)}\n`, 'utf8');
163
+ const pruned = await pruneHistory(dir, options.max ?? DEFAULT_HISTORY_MAX);
164
+ return { dir, file, run, pruned };
165
+ }
166
+ export async function loadRunScreenshot(dir, run) {
167
+ if (!run.screenshotFile)
168
+ return null;
169
+ try {
170
+ return new Uint8Array(await readFile(join(dir, run.screenshotFile)));
171
+ }
172
+ catch {
173
+ return null;
174
+ }
175
+ }
176
+ //# sourceMappingURL=history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history.js","sourceRoot":"","sources":["../../../src/report/history.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * `monobrowse report <url>` — open a page, measure it, judge it against a
3
+ * budget, and leave behind one self-contained HTML file plus a sibling JSON.
4
+ *
5
+ * collect.ts (drive the browser) -> analyze.ts (a11y + budgets -> verdict)
6
+ * -> history/trend/diff (what changed since last time) -> render.ts (HTML).
7
+ * This module is the seam that wires them together and writes the two files.
8
+ */
9
+ import type { CdpClient } from '../browser/cdp.js';
10
+ import type { CollectOptions } from './collect.js';
11
+ import { type FlakeReport } from './flake.js';
12
+ import { type HistoryRun } from './history.js';
13
+ import type { Budget, Report } from './types.js';
14
+ export * from './a11y.js';
15
+ export * from './analyze.js';
16
+ export * from './budget.js';
17
+ export * from './collect.js';
18
+ export * from './collect-a11y.js';
19
+ export * from './evidence.js';
20
+ export * from './flake.js';
21
+ export * from './history.js';
22
+ export * from './pixel-diff.js';
23
+ export * from './png.js';
24
+ export * from './render.js';
25
+ export * from './run-diff.js';
26
+ export * from './structure.js';
27
+ export * from './trend.js';
28
+ export * from './types.js';
29
+ /** Prior runs a trend is charted over. Older runs stay on disk, off the chart. */
30
+ export declare const DEFAULT_TREND_WINDOW = 10;
31
+ export interface RunReportOptions extends CollectOptions {
32
+ /** Output path: a `.html` file, or a directory to drop the default name in. */
33
+ out?: string;
34
+ /** Budget file path or inline JSON. Omitted means DEFAULT_BUDGET. */
35
+ budget?: string | Budget;
36
+ cwd?: string;
37
+ /** Persist this run and compare against previous ones. Default true. */
38
+ history?: boolean;
39
+ /** Runs kept per URL. Default DEFAULT_HISTORY_MAX. */
40
+ historyMax?: number;
41
+ /** Root of the history store. Default `~/.monomind/browser-reports`. */
42
+ historyDir?: string;
43
+ /** Prior runs charted. Default DEFAULT_TREND_WINDOW. */
44
+ trendWindow?: number;
45
+ }
46
+ export interface ReportResult {
47
+ report: Report;
48
+ htmlPath: string;
49
+ jsonPath: string;
50
+ summary: string;
51
+ /** Present when the run was saved to the history store. */
52
+ historyDir?: string;
53
+ /** Present only for a `--repeat` run (RIG-14); see RepeatResult. */
54
+ flake?: FlakeReport;
55
+ }
56
+ /**
57
+ * Resolve `--out` into an absolute .html path. A path that is an existing
58
+ * directory, or that ends in a separator, gets the generated filename
59
+ * appended — `--out ./reports` should not silently produce a file called
60
+ * `reports`.
61
+ */
62
+ export declare function resolveOutPath(out: string | undefined, url: string, capturedAt: string, cwd: string): Promise<string>;
63
+ /**
64
+ * The JSON sibling drops the base64 image payloads — they are megabytes of
65
+ * noise for anything reading this programmatically, and the HTML already
66
+ * carries them inline. `path` still points at the PNG on disk. The pixel
67
+ * diff's highlight image and the evidence frames go the same way: every
68
+ * measurement survives, only the pixels are dropped.
69
+ */
70
+ export declare function toJsonReport(report: Report): Record<string, unknown>;
71
+ export declare function writeReportFiles(report: Report, htmlPath: string, extras?: {
72
+ flake?: FlakeReport;
73
+ }): Promise<{
74
+ htmlPath: string;
75
+ jsonPath: string;
76
+ }>;
77
+ /** One run: collect, judge, compare against history, write the two files. */
78
+ export declare function runReport(client: CdpClient, sessionId: string, options: RunReportOptions): Promise<ReportResult>;
79
+ export interface RepeatResult extends ReportResult {
80
+ flake: FlakeReport;
81
+ /** Every run, in order. `report` is the last of them. */
82
+ reports: Report[];
83
+ }
84
+ /**
85
+ * `--repeat N`: run the same URL N times and judge the set (RIG-14).
86
+ *
87
+ * Only the last run is written out in full — N runs' worth of inlined
88
+ * screenshots is not something anyone wants on disk — but the flake analysis
89
+ * covers all of them, and it is the flake verdict that decides the exit code.
90
+ * A check that passed three times and failed twice must not exit 0.
91
+ */
92
+ export declare function runReportRepeated(client: CdpClient, sessionId: string, options: RunReportOptions & {
93
+ repeat: number;
94
+ }): Promise<RepeatResult>;
95
+ /** `--history <url>`: the stored runs for a URL, oldest first. */
96
+ export declare function readHistory(url: string, options?: {
97
+ historyDir?: string;
98
+ }): Promise<{
99
+ dir: string;
100
+ runs: HistoryRun[];
101
+ }>;
102
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/report/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,216 @@
1
+ /**
2
+ * `monobrowse report <url>` — open a page, measure it, judge it against a
3
+ * budget, and leave behind one self-contained HTML file plus a sibling JSON.
4
+ *
5
+ * collect.ts (drive the browser) -> analyze.ts (a11y + budgets -> verdict)
6
+ * -> history/trend/diff (what changed since last time) -> render.ts (HTML).
7
+ * This module is the seam that wires them together and writes the two files.
8
+ */
9
+ import { mkdir, stat, writeFile } from 'node:fs/promises';
10
+ import { dirname, isAbsolute, join, resolve } from 'node:path';
11
+ import { buildReport, summarizeVerdict } from './analyze.js';
12
+ import { loadBudget } from './budget.js';
13
+ import { collect } from './collect.js';
14
+ import { buildEvidence } from './evidence.js';
15
+ import { analyzeFlake, summarizeFlake } from './flake.js';
16
+ import { DEFAULT_HISTORY_MAX, historyDirFor, historyRoot, listRuns, saveRun, } from './history.js';
17
+ import { renderHtml } from './render.js';
18
+ import { buildRunDiff } from './run-diff.js';
19
+ import { buildTrend } from './trend.js';
20
+ export * from './a11y.js';
21
+ export * from './analyze.js';
22
+ export * from './budget.js';
23
+ export * from './collect.js';
24
+ export * from './collect-a11y.js';
25
+ export * from './evidence.js';
26
+ export * from './flake.js';
27
+ export * from './history.js';
28
+ export * from './pixel-diff.js';
29
+ export * from './png.js';
30
+ export * from './render.js';
31
+ export * from './run-diff.js';
32
+ export * from './structure.js';
33
+ export * from './trend.js';
34
+ export * from './types.js';
35
+ /** Prior runs a trend is charted over. Older runs stay on disk, off the chart. */
36
+ export const DEFAULT_TREND_WINDOW = 10;
37
+ function slugFor(url) {
38
+ try {
39
+ const parsed = new URL(url);
40
+ const path = parsed.pathname.replace(/\/+$/, '').replace(/[^a-z0-9]+/gi, '-');
41
+ return `${parsed.hostname}${path}`.replace(/-+/g, '-').replace(/^-|-$/g, '').toLowerCase();
42
+ }
43
+ catch {
44
+ return (url
45
+ .replace(/[^a-z0-9]+/gi, '-')
46
+ .replace(/^-|-$/g, '')
47
+ .toLowerCase() || 'page');
48
+ }
49
+ }
50
+ function timestamp(iso) {
51
+ return iso.replace(/[-:]/g, '').replace(/\.\d+Z$/, 'Z');
52
+ }
53
+ /**
54
+ * Resolve `--out` into an absolute .html path. A path that is an existing
55
+ * directory, or that ends in a separator, gets the generated filename
56
+ * appended — `--out ./reports` should not silently produce a file called
57
+ * `reports`.
58
+ */
59
+ export async function resolveOutPath(out, url, capturedAt, cwd) {
60
+ const defaultName = `monobrowse-report-${slugFor(url)}-${timestamp(capturedAt)}.html`;
61
+ if (!out)
62
+ return join(cwd, defaultName);
63
+ const abs = isAbsolute(out) ? out : resolve(cwd, out);
64
+ if (/[\\/]$/.test(out))
65
+ return join(abs, defaultName);
66
+ const isDir = await stat(abs)
67
+ .then((s) => s.isDirectory())
68
+ .catch(() => false);
69
+ if (isDir)
70
+ return join(abs, defaultName);
71
+ return abs.toLowerCase().endsWith('.html') || abs.toLowerCase().endsWith('.htm')
72
+ ? abs
73
+ : `${abs}.html`;
74
+ }
75
+ /**
76
+ * The JSON sibling drops the base64 image payloads — they are megabytes of
77
+ * noise for anything reading this programmatically, and the HTML already
78
+ * carries them inline. `path` still points at the PNG on disk. The pixel
79
+ * diff's highlight image and the evidence frames go the same way: every
80
+ * measurement survives, only the pixels are dropped.
81
+ */
82
+ export function toJsonReport(report) {
83
+ const { screenshots, recording, diff, evidence, ...rest } = report;
84
+ void recording;
85
+ return {
86
+ ...rest,
87
+ screenshots: screenshots.map(({ label, width, height, path }) => ({
88
+ label,
89
+ width,
90
+ height,
91
+ path,
92
+ })),
93
+ ...(diff
94
+ ? {
95
+ diff: {
96
+ ...diff,
97
+ pixels: diff.pixels ? { ...diff.pixels, diffDataUrl: undefined } : undefined,
98
+ },
99
+ }
100
+ : {}),
101
+ ...(evidence
102
+ ? {
103
+ evidence: {
104
+ ...evidence,
105
+ frames: evidence.frames.map(({ offsetMs, bytes }) => ({ offsetMs, bytes })),
106
+ },
107
+ }
108
+ : {}),
109
+ };
110
+ }
111
+ export async function writeReportFiles(report, htmlPath, extras = {}) {
112
+ const jsonPath = `${htmlPath.replace(/\.html?$/i, '')}.json`;
113
+ await mkdir(dirname(htmlPath), { recursive: true });
114
+ await writeFile(htmlPath, renderHtml(report, extras), 'utf8');
115
+ const json = extras.flake
116
+ ? { ...toJsonReport(report), flake: extras.flake }
117
+ : toJsonReport(report);
118
+ await writeFile(jsonPath, `${JSON.stringify(json, null, 2)}\n`, 'utf8');
119
+ return { htmlPath, jsonPath };
120
+ }
121
+ /**
122
+ * Attach the trend and the diff, then persist the run.
123
+ *
124
+ * Order matters: history is read BEFORE the run is saved, so a run never
125
+ * trends or diffs against itself. A failure anywhere in here becomes a note
126
+ * on the report rather than an error — an unwritable home directory should
127
+ * cost you the trend section, not the report.
128
+ */
129
+ async function applyHistory(report, options) {
130
+ if (options.history === false)
131
+ return undefined;
132
+ const root = options.historyDir ?? historyRoot();
133
+ const dir = historyDirFor(report.url, root);
134
+ try {
135
+ const previous = await listRuns(dir);
136
+ const window = options.trendWindow ?? DEFAULT_TREND_WINDOW;
137
+ report.trend = buildTrend(report, previous.slice(-window));
138
+ report.diff = await buildRunDiff(dir, previous.at(-1), report);
139
+ await saveRun(report, { root, max: options.historyMax ?? DEFAULT_HISTORY_MAX });
140
+ return dir;
141
+ }
142
+ catch (err) {
143
+ report.notes.push(`Run history unavailable (${err.message}) — no trend or diff for this run.`);
144
+ return undefined;
145
+ }
146
+ }
147
+ /** Turn the recorder's raw frames into the evidence block, when warranted. */
148
+ function applyEvidence(report, data) {
149
+ const recording = data.recording;
150
+ if (!recording)
151
+ return;
152
+ if (report.verdict !== 'fail' && !recording.requested)
153
+ return;
154
+ report.evidence = buildEvidence({
155
+ reason: report.verdict === 'fail' ? 'budget-failure' : 'requested',
156
+ startedAtMs: recording.startedAtMs,
157
+ frames: recording.frames,
158
+ bufferDropped: recording.droppedFrames,
159
+ console: report.console,
160
+ pageErrors: report.pageErrors,
161
+ requests: report.requests,
162
+ failures: report.failures,
163
+ });
164
+ }
165
+ async function resolveBudget(options) {
166
+ return options.budget && typeof options.budget === 'object'
167
+ ? options.budget
168
+ : await loadBudget(options.budget);
169
+ }
170
+ /** One run: collect, judge, compare against history, write the two files. */
171
+ export async function runReport(client, sessionId, options) {
172
+ const budget = await resolveBudget(options);
173
+ const data = await collect(client, sessionId, options);
174
+ const report = buildReport(data, budget);
175
+ applyEvidence(report, data);
176
+ const historyDir = await applyHistory(report, options);
177
+ // Working material, not report content — and megabytes of it.
178
+ report.recording = undefined;
179
+ const htmlPath = await resolveOutPath(options.out, options.url, data.capturedAt, options.cwd ?? process.cwd());
180
+ const paths = await writeReportFiles(report, htmlPath);
181
+ return { report, ...paths, summary: summarizeVerdict(report), historyDir };
182
+ }
183
+ /**
184
+ * `--repeat N`: run the same URL N times and judge the set (RIG-14).
185
+ *
186
+ * Only the last run is written out in full — N runs' worth of inlined
187
+ * screenshots is not something anyone wants on disk — but the flake analysis
188
+ * covers all of them, and it is the flake verdict that decides the exit code.
189
+ * A check that passed three times and failed twice must not exit 0.
190
+ */
191
+ export async function runReportRepeated(client, sessionId, options) {
192
+ const times = Math.max(1, Math.floor(options.repeat));
193
+ const budget = await resolveBudget(options);
194
+ const reports = [];
195
+ for (let i = 0; i < times; i++) {
196
+ const data = await collect(client, sessionId, options);
197
+ const report = buildReport(data, budget);
198
+ applyEvidence(report, data);
199
+ report.recording = undefined;
200
+ reports.push(report);
201
+ }
202
+ const flake = analyzeFlake(reports);
203
+ const report = reports[reports.length - 1];
204
+ // Only the run we keep is persisted: writing N history entries for one
205
+ // command would let a single `--repeat 20` evict the whole trend window.
206
+ const historyDir = await applyHistory(report, options);
207
+ const htmlPath = await resolveOutPath(options.out, options.url, report.capturedAt, options.cwd ?? process.cwd());
208
+ const paths = await writeReportFiles(report, htmlPath, { flake });
209
+ return { report, reports, flake, ...paths, summary: summarizeFlake(flake), historyDir };
210
+ }
211
+ /** `--history <url>`: the stored runs for a URL, oldest first. */
212
+ export async function readHistory(url, options = {}) {
213
+ const dir = historyDirFor(url, options.historyDir ?? historyRoot());
214
+ return { dir, runs: await listRuns(dir) };
215
+ }
216
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/report/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Pixel diff between two screenshots (RIG-10), with no image dependency —
3
+ * png.ts decodes and re-encodes using node's own zlib.
4
+ *
5
+ * Two design choices worth stating:
6
+ *
7
+ * - A per-channel tolerance, not exact equality. Chrome's rasteriser is not
8
+ * bit-deterministic across runs (font hinting, GPU vs. software paths), so
9
+ * an exact compare reports 4% of every screenshot as "changed" and the
10
+ * number becomes noise nobody reads.
11
+ * - Differing dimensions are a result, not an error. A page that grew 200px
12
+ * taller is exactly the regression this is for, so we compare the
13
+ * overlapping region, count the rest as changed, and say so.
14
+ */
15
+ import type { PixelDiff } from './types.js';
16
+ export interface PixelDiffOptions {
17
+ threshold?: number;
18
+ maxPixels?: number;
19
+ /** Set false to skip building the highlight image (counts only). */
20
+ renderImage?: boolean;
21
+ }
22
+ /**
23
+ * Compare two PNGs. Never throws for image-shaped reasons — an undecodable
24
+ * screenshot produces a result carrying `note` and no counts, because a
25
+ * report that renders minus one section beats a report that fails to build.
26
+ */
27
+ export declare function pixelDiff(previousPng: Uint8Array, currentPng: Uint8Array, options?: PixelDiffOptions): PixelDiff;
28
+ /** One line for a CI log. */
29
+ export declare function summarizePixelDiff(diff: PixelDiff): string;
30
+ //# sourceMappingURL=pixel-diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pixel-diff.d.ts","sourceRoot":"","sources":["../../../src/report/pixel-diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAW5C,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oEAAoE;IACpE,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAwED;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,WAAW,EAAE,UAAU,EACvB,UAAU,EAAE,UAAU,EACtB,OAAO,GAAE,gBAAqB,GAC7B,SAAS,CAwEX;AAED,6BAA6B;AAC7B,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAQ1D"}