ssrwire 0.2.0 → 0.4.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.
Files changed (71) hide show
  1. package/CHANGELOG.md +47 -1
  2. package/CONTRIBUTING.md +3 -1
  3. package/PUBLISHING.md +15 -15
  4. package/README.md +179 -29
  5. package/dist/analyze.d.ts.map +1 -1
  6. package/dist/analyze.js +162 -6
  7. package/dist/analyze.js.map +1 -1
  8. package/dist/audit-report.d.ts +8 -0
  9. package/dist/audit-report.d.ts.map +1 -0
  10. package/dist/audit-report.js +243 -0
  11. package/dist/audit-report.js.map +1 -0
  12. package/dist/audit.d.ts.map +1 -1
  13. package/dist/audit.js +2 -0
  14. package/dist/audit.js.map +1 -1
  15. package/dist/cli.d.ts.map +1 -1
  16. package/dist/cli.js +77 -10
  17. package/dist/cli.js.map +1 -1
  18. package/dist/compare.d.ts +6 -0
  19. package/dist/compare.d.ts.map +1 -0
  20. package/dist/compare.js +720 -0
  21. package/dist/compare.js.map +1 -0
  22. package/dist/comparison-reporters.d.ts +9 -0
  23. package/dist/comparison-reporters.d.ts.map +1 -0
  24. package/dist/comparison-reporters.js +223 -0
  25. package/dist/comparison-reporters.js.map +1 -0
  26. package/dist/config.d.ts.map +1 -1
  27. package/dist/config.js +20 -1
  28. package/dist/config.js.map +1 -1
  29. package/dist/http-probe.d.ts.map +1 -1
  30. package/dist/http-probe.js +4 -0
  31. package/dist/http-probe.js.map +1 -1
  32. package/dist/index.d.ts +4 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +3 -0
  35. package/dist/index.js.map +1 -1
  36. package/dist/redact.d.ts.map +1 -1
  37. package/dist/redact.js +6 -0
  38. package/dist/redact.js.map +1 -1
  39. package/dist/reporters.d.ts.map +1 -1
  40. package/dist/reporters.js +27 -0
  41. package/dist/reporters.js.map +1 -1
  42. package/dist/social.d.ts +14 -0
  43. package/dist/social.d.ts.map +1 -0
  44. package/dist/social.js +88 -0
  45. package/dist/social.js.map +1 -0
  46. package/dist/stability.d.ts.map +1 -1
  47. package/dist/stability.js +84 -4
  48. package/dist/stability.js.map +1 -1
  49. package/dist/stream-parser.d.ts.map +1 -1
  50. package/dist/stream-parser.js +21 -1
  51. package/dist/stream-parser.js.map +1 -1
  52. package/dist/types.d.ts +92 -0
  53. package/dist/types.d.ts.map +1 -1
  54. package/examples/github-actions.yml +2 -2
  55. package/examples/ssrwire.config.yml +8 -2
  56. package/package.json +5 -2
  57. package/src/analyze.ts +208 -7
  58. package/src/audit-report.ts +269 -0
  59. package/src/audit.ts +2 -0
  60. package/src/cli.ts +106 -12
  61. package/src/compare.ts +949 -0
  62. package/src/comparison-reporters.ts +276 -0
  63. package/src/config.ts +19 -1
  64. package/src/http-probe.ts +4 -0
  65. package/src/index.ts +29 -0
  66. package/src/redact.ts +6 -0
  67. package/src/reporters.ts +46 -0
  68. package/src/social.ts +116 -0
  69. package/src/stability.ts +123 -4
  70. package/src/stream-parser.ts +31 -1
  71. package/src/types.ts +117 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,50 @@ All notable changes to SSRWire are documented here. The project follows
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.4.0] - 2026-09-01
9
+
10
+ ### Added
11
+
12
+ - Offline `ssrwire compare` for two redacted JSON audits, with stable target IDs
13
+ for matching the same route across production and preview origins.
14
+ - Regression, fixed, and neutral-change classification for policy findings,
15
+ completion, HTTP delivery, metadata values and locations, agent coverage, and
16
+ redirect evidence.
17
+ - Dual-floor median timing comparisons with configurable absolute and relative
18
+ thresholds.
19
+ - Terminal and deterministic JSON comparison reports plus a self-contained,
20
+ script-free HTML wire waterfall.
21
+ - Public comparison, report-validation, and comparison-reporter APIs.
22
+
23
+ ### Changed
24
+
25
+ - JSON audits now include an explicit `schemaVersion: 1` persisted-report
26
+ contract independent of the SSRWire package version.
27
+ - Target objects accept an optional unique `id`; exact URL matching remains the
28
+ fallback when an ID is absent.
29
+
30
+ ## [0.3.0] - 2026-08-28
31
+
32
+ ### Added
33
+
34
+ - Streamed capture for core Open Graph and Twitter Card metadata with arrival
35
+ time, observed byte position, document location, bounded repeated values, and
36
+ report redaction.
37
+ - Opt-in `require.openGraph` and `require.twitterCard` target contracts with
38
+ missing-field, invalid-URL, duplicate/conflict, and head/body delivery
39
+ findings.
40
+ - A terminal social-preview readiness table and public social metadata signal
41
+ types in JSON and the programmatic API.
42
+
43
+ ### Changed
44
+
45
+ - Enabled social contracts now participate in required-signal timing,
46
+ cross-agent comparison, and repeated-sample stability analysis.
47
+ - Twitter Card readiness prefers native Twitter metadata and falls back to the
48
+ corresponding Open Graph title, description, and image.
49
+ - Multiple `og:image` values are retained in document order without being
50
+ treated as conflicting scalar metadata.
51
+
8
52
  ## [0.2.0] - 2026-08-24
9
53
 
10
54
  ### Added
@@ -36,6 +80,8 @@ All notable changes to SSRWire are documented here. The project follows
36
80
  - Terminal, JSON, and SARIF reports with CI-safe exit codes.
37
81
  - YAML configuration, one-off URL checks, Docker support, and GitHub Actions examples.
38
82
 
39
- [Unreleased]: https://github.com/lame13/ssrwire/compare/v0.2.0...HEAD
83
+ [Unreleased]: https://github.com/lame13/ssrwire/compare/v0.4.0...HEAD
84
+ [0.4.0]: https://github.com/lame13/ssrwire/compare/v0.3.0...v0.4.0
85
+ [0.3.0]: https://github.com/lame13/ssrwire/compare/v0.2.0...v0.3.0
40
86
  [0.2.0]: https://github.com/lame13/ssrwire/compare/v0.1.0...v0.2.0
41
87
  [0.1.0]: https://github.com/lame13/ssrwire/releases/tag/v0.1.0
package/CONTRIBUTING.md CHANGED
@@ -26,12 +26,14 @@ before opening a pull request, then run `npm run check` again.
26
26
  Keep each change narrow. Include tests for behavior changes, especially for:
27
27
 
28
28
  - response chunk boundaries and elements split across chunks;
29
+ - persisted audit-report validation, target matching, and comparison classification;
29
30
  - redirects, timeouts, aborted bodies, and size limits;
30
31
  - malformed, duplicated, late, or body-located metadata;
32
+ - Open Graph and Twitter Card requirements, fallbacks, arrays, and URL values;
31
33
  - user-agent differences and comparison findings;
32
34
  - repeated-sample ordering, aggregation, and instability classification;
33
35
  - redaction of configured header values;
34
- - terminal, JSON, SARIF, and exit-code behavior.
36
+ - terminal, JSON, SARIF, comparison HTML, and exit-code behavior.
35
37
 
36
38
  Do not make timing tests depend on exact millisecond values. Shared runners and
37
39
  local machines have unavoidable scheduling variance. Test ordering,
package/PUBLISHING.md CHANGED
@@ -1,4 +1,4 @@
1
- # Publish SSRWire 0.2.0 from a local machine
1
+ # Publish SSRWire 0.4.0 from a local machine
2
2
 
3
3
  This repository intentionally includes no npm publishing workflow. Publish from
4
4
  a foreground local terminal only after the GitHub `CI` workflow passes. Do not
@@ -10,7 +10,7 @@ request, and do not use an administrator bypass or a direct push to `main`.
10
10
 
11
11
  ## 1. Update the existing repository
12
12
 
13
- Work from a clean clone of the existing public repository. Copy the 0.2.0
13
+ Work from a clean clone of the existing public repository. Copy the 0.4.0
14
14
  source files into that clone while preserving its `.git` directory.
15
15
 
16
16
  ```bash
@@ -18,7 +18,7 @@ cd ssrwire
18
18
  git switch main
19
19
  git pull --ff-only origin main
20
20
  git status --short
21
- git switch -c release/0.2.0
21
+ git switch -c release/0.4.0
22
22
  ```
23
23
 
24
24
  `git status --short` must be empty before creating the release branch and
@@ -36,7 +36,7 @@ npm pack --dry-run
36
36
  node dist/bin.js --version
37
37
  ```
38
38
 
39
- The final command must print `0.2.0`. Inspect the dry-run file list. It must not
39
+ The final command must print `0.4.0`. Inspect the dry-run file list. It must not
40
40
  contain `.env`, `.github`, `node_modules`, `test`, ZIP files, or tarballs.
41
41
 
42
42
  Review the release diff and version references:
@@ -45,7 +45,7 @@ Review the release diff and version references:
45
45
  git diff --check
46
46
  git diff --stat
47
47
  git diff -- package.json package-lock.json CHANGELOG.md README.md PUBLISHING.md
48
- rg '0\.1\.0' README.md examples package.json package-lock.json src test
48
+ rg '0\.3\.0' README.md examples package.json package-lock.json scripts src test
49
49
  ```
50
50
 
51
51
  The final search should return nothing. Historical entries in `CHANGELOG.md`
@@ -58,10 +58,10 @@ gh auth status -h github.com || gh auth login -h github.com --web
58
58
  git add --all
59
59
  git diff --cached --check
60
60
  git diff --cached --stat
61
- git commit -m "feat: release SSRWire 0.2.0"
62
- git push --set-upstream origin release/0.2.0
63
- gh pr create --base main --head release/0.2.0 --fill
64
- PR_NUMBER="$(gh pr view release/0.2.0 --json number --jq .number)"
61
+ git commit -m "feat: release SSRWire 0.4.0"
62
+ git push --set-upstream origin release/0.4.0
63
+ gh pr create --base main --head release/0.4.0 --fill
64
+ PR_NUMBER="$(gh pr view release/0.4.0 --json number --jq .number)"
65
65
  test -n "$PR_NUMBER"
66
66
  gh pr checks "$PR_NUMBER" --watch --fail-fast
67
67
  ```
@@ -102,8 +102,8 @@ npm config get registry
102
102
  npm config get provenance
103
103
  ```
104
104
 
105
- The published version and `latest` tag must still be `0.1.0`. If npm already
106
- reports `0.2.0`, stop: never reuse a version that npm accepted.
105
+ The published version and `latest` tag must still be `0.3.0`. If npm already
106
+ reports `0.4.0`, stop: never reuse a version that npm accepted.
107
107
 
108
108
  In the npm package settings, select **Require two-factor authentication and
109
109
  disallow tokens**. npm documents this as the strongest package publishing
@@ -132,7 +132,7 @@ or add an npm credential to the repository or GitHub Actions. The package-level
132
132
  "disallow tokens" setting ensures that publication remains interactive.
133
133
 
134
134
  If npm's publish-time scanning delays package visibility, wait. Do not publish
135
- `0.2.0` again or change the tag to work around propagation.
135
+ `0.4.0` again or change the tag to work around propagation.
136
136
 
137
137
  ## 6. Tag the exact published commit
138
138
 
@@ -140,9 +140,9 @@ If npm's publish-time scanning delays package visibility, wait. Do not publish
140
140
  node scripts/clean.mjs
141
141
  rm -rf node_modules/.vite
142
142
  git status --short
143
- git tag -a v0.2.0 -m "SSRWire v0.2.0"
144
- git push origin v0.2.0
145
- gh release create v0.2.0 --generate-notes --title "SSRWire v0.2.0"
143
+ git tag -a v0.4.0 -m "SSRWire v0.4.0"
144
+ git push origin v0.4.0
145
+ gh release create v0.4.0 --generate-notes --title "SSRWire v0.4.0"
146
146
  ```
147
147
 
148
148
  `git status --short` must print nothing before tagging.
package/README.md CHANGED
@@ -4,14 +4,14 @@
4
4
  [![npm version](https://img.shields.io/npm/v/ssrwire.svg)](https://www.npmjs.com/package/ssrwire)
5
5
  [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
6
 
7
- Inspect streamed SSR HTML, metadata timing, and crawler-specific delivery from
8
- the command line.
7
+ Inspect streamed SSR HTML, SEO and social metadata timing, and crawler-specific
8
+ delivery from the command line.
9
9
 
10
10
  SSRWire makes a real HTTP request for each selected user-agent profile, reads
11
- the response incrementally, and records when important SEO signals become
12
- observable to its parser. It reports their elapsed time, observed byte
13
- position, and document location without launching a browser or executing
14
- JavaScript.
11
+ the response incrementally, and records when important SEO and social-preview
12
+ signals become observable to its parser. It reports their elapsed time,
13
+ observed byte position, and document location without launching a browser or
14
+ executing JavaScript.
15
15
 
16
16
  ```bash
17
17
  npx ssrwire https://example.com/product
@@ -25,7 +25,8 @@ Modern SSR output is not always one complete HTML document delivered at once:
25
25
  - a framework may intentionally stream metadata into `<body>` for a capable
26
26
  crawler while blocking for an HTML-limited bot;
27
27
  - browser, search-crawler, and social-crawler user agents may receive different
28
- titles, canonicals, robots directives, redirects, or statuses;
28
+ titles, canonicals, social-preview metadata, robots directives, redirects, or
29
+ statuses;
29
30
  - the same URL and user agent may receive inconsistent SSR output between requests;
30
31
  - an interrupted or oversized stream may never deliver the expected elements;
31
32
  - a working hydrated page can hide thin or incomplete source HTML.
@@ -84,6 +85,65 @@ npx ssrwire check \
84
85
  The root command and `check` are equivalent, so `npx ssrwire URL` is the short
85
86
  form of `npx ssrwire check URL`.
86
87
 
88
+ ## Compare deployments
89
+
90
+ SSRWire can compare two JSON audits without making more network requests. Give
91
+ the same logical target a stable `id` in each environment so reports can match
92
+ across different origins:
93
+
94
+ ```yaml
95
+ # production.yml
96
+ targets:
97
+ - id: home
98
+ url: https://www.example.com/
99
+ - id: pricing
100
+ url: https://www.example.com/pricing/
101
+ ```
102
+
103
+ ```yaml
104
+ # preview.yml
105
+ targets:
106
+ - id: home
107
+ url: https://preview.example.net/
108
+ - id: pricing
109
+ url: https://preview.example.net/pricing/
110
+ ```
111
+
112
+ Capture and compare the already-redacted reports:
113
+
114
+ ```bash
115
+ npx ssrwire check --config production.yml --format json --output production.json
116
+ npx ssrwire check --config preview.yml --format json --output preview.json
117
+
118
+ npx ssrwire compare production.json preview.json
119
+ npx ssrwire compare production.json preview.json \
120
+ --format html \
121
+ --output ssrwire-diff.html
122
+ ```
123
+
124
+ The comparison classifies candidate-only warning/error findings and newly
125
+ incomplete probes as regressions, resolved findings and material timing
126
+ improvements as fixed, and response or metadata differences as neutral changes.
127
+ A metadata change becomes a regression when it causes a candidate policy
128
+ finding, such as required metadata disappearing or head-only crawler metadata
129
+ moving into the body.
130
+
131
+ Timing regressions use per-agent medians and require both an absolute increase
132
+ over 250 ms and a relative increase over 25% by default. Both floors are
133
+ configurable on `compare`; small timing differences remain visible in the HTML
134
+ waterfall without making CI noisy. `--fail-on regression` is the comparison
135
+ default, while `--fail-on never` always exits successfully after valid reports
136
+ are compared.
137
+
138
+ The HTML report is one self-contained, script-free file with synchronized
139
+ baseline and candidate milestones. It never embeds raw response HTML. SSRWire
140
+ does not create or update baseline files automatically.
141
+
142
+ Reports without target IDs match by exact target URL. IDs must be unique within
143
+ one report, so an ID mismatch is shown as one removed and one added target
144
+ instead of being guessed. Comparison requires the explicit `schemaVersion: 1`
145
+ audit contract emitted by SSRWire 0.4.0.
146
+
87
147
  ## What it observes
88
148
 
89
149
  For each target, agent, and configured sample, SSRWire captures:
@@ -91,8 +151,8 @@ For each target, agent, and configured sample, SSRWire captures:
91
151
  - response status, final URL, redirect chain, and an allowlisted response-header snapshot;
92
152
  - time to response headers, first response-body bytes, and completed body;
93
153
  - total bytes delivered to the stream parser and a body fingerprint;
94
- - title, meta description, canonical, meta robots, H1, first main-content
95
- text, and JSON-LD blocks;
154
+ - title, meta description, canonical, meta robots, Open Graph, Twitter Card,
155
+ H1, first main-content text, and JSON-LD blocks;
96
156
  - elapsed arrival time, observed byte position, and `head`/`body` location for
97
157
  each signal;
98
158
  - clean completion, timeout, network failure, invalid response, or configured
@@ -107,10 +167,12 @@ It then checks:
107
167
  | Missing title | Error |
108
168
  | Missing description, canonical, H1, or main text | Warning |
109
169
  | Duplicate or conflicting title, description, canonical, or robots values | Warning |
170
+ | Missing an enabled Open Graph or Twitter Card contract | Warning |
171
+ | Invalid social metadata URL or conflicting scalar social metadata | Warning |
110
172
  | Invalid JSON-LD | Warning |
111
173
  | JSON-LD block/count exceeds the bounded analysis budget | Warning |
112
- | Critical metadata in `<body>` for a profile that requires head metadata | Error |
113
- | Status, final URL, title, canonical, or robots drift between profiles | Warning |
174
+ | Critical or enabled social metadata in `<body>` for a profile that requires head metadata | Error |
175
+ | Status, final URL, title, canonical, robots, or enabled social metadata drift between profiles | Warning |
114
176
  | Completion, status, final URL, or redirect-chain drift between samples | Warning |
115
177
  | Metadata value or document-location drift between complete samples | Warning |
116
178
  | Exact body fingerprint drift without metadata drift | Information |
@@ -162,6 +224,41 @@ agents:
162
224
  requiresHeadMetadata: true
163
225
  ```
164
226
 
227
+ ## Social preview metadata
228
+
229
+ Every probe captures these bounded, ordered metadata signals when they arrive
230
+ before completion or termination:
231
+
232
+ - Open Graph: `og:title`, `og:type`, `og:url`, `og:image`, and
233
+ `og:description`;
234
+ - Twitter Card: `twitter:card`, `twitter:title`, `twitter:description`, and
235
+ `twitter:image`.
236
+
237
+ SSRWire accepts either the conventional `property` attribute or a `name`
238
+ attribute for those keys. Each captured value carries the same arrival time,
239
+ observed byte position, and document location as the existing metadata
240
+ signals. The terminal report shows a compact readiness table whenever social
241
+ metadata is observed or required, while JSON retains every captured value.
242
+
243
+ Social policy is opt-in per target. `openGraph: true` requires the four basic
244
+ [Open Graph protocol](https://ogp.me/) properties: title, type, URL, and image.
245
+ The `twitterCard: true` option requires `twitter:card` plus a usable title,
246
+ description, and image; SSRWire prefers the corresponding `twitter:*` value
247
+ and falls back to `og:title`, `og:description`, or `og:image`. These are
248
+ explicit SSRWire audit contracts, not a claim that a social platform will
249
+ render a particular preview.
250
+
251
+ Enabled contracts also participate in critical-signal timing, head/body
252
+ policy, cross-agent drift, and repeated-sample stability checks. URL-valued
253
+ fields must be absolute HTTP or HTTPS URLs. Open Graph permits multiple images,
254
+ so SSRWire retains them in order without treating the array as a scalar
255
+ conflict; the first non-empty image satisfies readiness.
256
+
257
+ When both options are false, SSRWire still records and reports raw social
258
+ signals but emits no social-policy or social-drift findings. It does not fetch
259
+ images, verify dimensions or media types, execute JavaScript, or simulate a
260
+ platform's rendered preview.
261
+
165
262
  ## Configuration
166
263
 
167
264
  SSRWire automatically looks for `ssrwire.config.yml`,
@@ -170,7 +267,8 @@ takes precedence.
170
267
 
171
268
  ```yaml
172
269
  targets:
173
- - url: https://example.com/
270
+ - id: home
271
+ url: https://example.com/
174
272
  expectedStatus: 200
175
273
  expectedFinalUrl: https://example.com/
176
274
  require:
@@ -179,10 +277,13 @@ targets:
179
277
  canonical: true
180
278
  h1: true
181
279
  mainText: true
280
+ openGraph: true
281
+ twitterCard: true
182
282
  maxFirstByteMs: 1200
183
283
  maxCriticalMs: 2500
184
284
 
185
- - url: https://example.com/not-found/
285
+ - id: not-found
286
+ url: https://example.com/not-found/
186
287
  expectedStatus: [404]
187
288
  require:
188
289
  title: true
@@ -190,6 +291,8 @@ targets:
190
291
  canonical: false
191
292
  h1: true
192
293
  mainText: true
294
+ openGraph: false
295
+ twitterCard: false
193
296
 
194
297
  agents:
195
298
  - browser
@@ -214,10 +317,15 @@ targets:
214
317
  - https://example.com/pricing/
215
318
  ```
216
319
 
320
+ Use the object form with a stable `id` when reports from different origins will
321
+ be compared. IDs are 1–64 ASCII letters, digits, dots, underscores, or hyphens,
322
+ and must start with a letter or digit.
323
+
217
324
  Defaults:
218
325
 
219
326
  - expected status: `200`;
220
327
  - title, description, canonical, H1, and main text: required;
328
+ - Open Graph and Twitter Card contracts: disabled;
221
329
  - agents: `browser`, `googlebot`, `bingbot`, and `twitterbot`;
222
330
  - timeout: 15 seconds per probe;
223
331
  - response limit: 10 MiB;
@@ -262,11 +370,11 @@ The final response must declare `text/html` or `application/xhtml+xml` as its
262
370
  sniff headerless, JSON, text, or binary responses for HTML-looking fragments.
263
371
 
264
372
  To keep hostile or accidentally huge pages bounded, SSRWire retains at most
265
- 256 signals of each repeated metadata kind, analyzes at most 64 JSON-LD blocks,
266
- and captures at most 1,048,576 characters from one JSON-LD block. Exceeding a
267
- JSON-LD analysis budget produces a dedicated warning rather than being
268
- mislabeled as invalid JSON. The configured response-byte limit remains the
269
- outer bound.
373
+ 256 signals of each repeated metadata kind, including each supported social
374
+ property, analyzes at most 64 JSON-LD blocks, and captures at most 1,048,576
375
+ characters from one JSON-LD block. Exceeding a JSON-LD analysis budget produces
376
+ a dedicated warning rather than being mislabeled as invalid JSON. The
377
+ configured response-byte limit remains the outer bound.
270
378
 
271
379
  ## Timing interpretation
272
380
 
@@ -311,15 +419,18 @@ the maximum.
311
419
 
312
420
  Timing spread alone is evidence, not a failure. Network and cache variation can
313
421
  change timings without changing the response contract. SSRWire warns when HTTP
314
- response evidence or streamed metadata changes across samples. Exact body-hash
315
- variation by itself is informational because timestamps, nonces, and other
316
- legitimate dynamic values commonly change source HTML.
422
+ response evidence or streamed metadata changes across samples. Enabled social
423
+ contracts are included in metadata stability; observed social tags remain
424
+ evidence-only when their contracts are disabled. Exact body-hash variation by
425
+ itself is informational because timestamps, nonces, and other legitimate
426
+ dynamic values commonly change source HTML.
317
427
 
318
428
  ## CLI reference
319
429
 
320
430
  ```text
321
431
  ssrwire [urls...] [options]
322
432
  ssrwire check [urls...] [options]
433
+ ssrwire compare <baseline.json> <candidate.json> [options]
323
434
  ssrwire init [path] [--force]
324
435
  ```
325
436
 
@@ -342,13 +453,27 @@ Check options:
342
453
  Config-file targets and CLI URLs are combined, with exact duplicate URLs
343
454
  removed.
344
455
 
456
+ Comparison options:
457
+
458
+ | Option | Purpose |
459
+ |---|---|
460
+ | `-f, --format <format>` | `terminal`, `json`, or self-contained `html` |
461
+ | `-o, --output <path>` | Write the comparison to a file |
462
+ | `--fail-on <level>` | `regression` or `never` |
463
+ | `--timing-regression-ms <ms>` | Absolute median slowdown floor; default `250` |
464
+ | `--timing-regression-percent <percent>` | Relative median slowdown floor; default `25` |
465
+ | `--no-color` | Disable terminal color |
466
+
345
467
  ## Reports and exit codes
346
468
 
347
- - `terminal`: compact sample, aggregate-timing, and finding tables for local use.
469
+ - `terminal`: compact sample, social-readiness, aggregate-timing, and finding
470
+ tables for local use.
348
471
  - `json`: structured machine-readable evidence, including every probe and timing
349
472
  signal plus repeated-run stability summaries.
350
473
  - `sarif`: findings suitable for GitHub Code Scanning and other SARIF 2.1.0
351
474
  consumers.
475
+ - comparison `html`: a script-free deployment summary and per-agent wire
476
+ waterfall suitable for a CI artifact.
352
477
 
353
478
  Exit codes are stable:
354
479
 
@@ -356,7 +481,11 @@ Exit codes are stable:
356
481
  - `1`: the run completed but crossed the `--fail-on` threshold;
357
482
  - `2`: configuration/setup failure or incomplete probe evidence.
358
483
 
359
- `--fail-on never` suppresses policy failures, but it never converts an
484
+ For `compare`, exit `1` means at least one regression was introduced under the
485
+ default policy, while invalid or unreadable reports use exit `2`. Neutral
486
+ changes and fixed findings do not fail comparison.
487
+
488
+ `check --fail-on never` suppresses policy failures, but it never converts an
360
489
  incomplete probe into a pass.
361
490
 
362
491
  ## GitHub Actions
@@ -414,15 +543,36 @@ const audit = await runAudit(config);
414
543
  process.stdout.write(renderJson(audit));
415
544
  ```
416
545
 
417
- Treat the JSON report's top-level `version` as the SSRWire software version,
418
- not a promise that every nested field will remain unchanged across major
419
- versions.
546
+ Comparisons use the same primitives as the CLI:
547
+
548
+ ```ts
549
+ import { readFile } from "node:fs/promises";
550
+ import {
551
+ compareAudits,
552
+ parseAuditReportText,
553
+ renderComparisonHtml,
554
+ } from "ssrwire";
555
+
556
+ const [baselineJson, candidateJson] = await Promise.all([
557
+ readFile("production.json", "utf8"),
558
+ readFile("preview.json", "utf8"),
559
+ ]);
560
+ const baseline = parseAuditReportText(baselineJson, "baseline audit report");
561
+ const candidate = parseAuditReportText(candidateJson, "candidate audit report");
562
+ const comparison = compareAudits(baseline, candidate);
563
+ const html = renderComparisonHtml(comparison);
564
+ ```
565
+
566
+ The JSON report's top-level `version` is the SSRWire software version.
567
+ `schemaVersion` separately identifies the persisted report contract used by
568
+ offline comparison.
420
569
 
421
570
  ## Scope
422
571
 
423
- SSRWire does not execute JavaScript, inspect a hydrated DOM, measure Core Web
424
- Vitals, discover URLs, validate indexing, bypass access controls, perform load
425
- testing, or emulate a crawler's rendering pipeline. Use
572
+ SSRWire does not execute JavaScript, inspect a hydrated DOM, render social
573
+ previews, fetch social images, measure Core Web Vitals, discover URLs, validate
574
+ indexing, bypass access controls, perform load testing, or emulate a crawler's
575
+ rendering pipeline. Use
426
576
  [RoutePlay](https://github.com/lame13/routeplay) for server HTML versus a cold
427
577
  browser versus real client-side navigation. Use
428
578
  [RouteLint](https://github.com/lame13/routelint) for route discovery,
@@ -1 +1 @@
1
- {"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../src/analyze.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EAEX,OAAO,EACP,WAAW,EAIX,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAgepB,wBAAgB,aAAa,CAC3B,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,SAAS,WAAW,EAAE,GAC7B,SAAS,OAAO,EAAE,CAuGpB;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,SAAS,iBAAiB,EAAE,GAAG,YAAY,CA6BlF"}
1
+ {"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../src/analyze.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EAEX,OAAO,EACP,WAAW,EAMX,iBAAiB,EAClB,MAAM,YAAY,CAAC;AA2pBpB,wBAAgB,aAAa,CAC3B,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,SAAS,WAAW,EAAE,GAC7B,SAAS,OAAO,EAAE,CAwGpB;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,SAAS,iBAAiB,EAAE,GAAG,YAAY,CA6BlF"}