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/dist/types.d.ts CHANGED
@@ -12,6 +12,10 @@ export type RobotsAudience = "robots" | "googlebot" | "bingbot";
12
12
  export interface RobotsSignal extends ElementSignal {
13
13
  readonly audience: RobotsAudience;
14
14
  }
15
+ export type SocialMetadataProperty = "og:title" | "og:type" | "og:url" | "og:image" | "og:description" | "twitter:card" | "twitter:title" | "twitter:description" | "twitter:image";
16
+ export interface SocialMetadataSignal extends ElementSignal {
17
+ readonly property: SocialMetadataProperty;
18
+ }
15
19
  export interface JsonLdSignal extends TimingMark {
16
20
  readonly location: ElementLocation;
17
21
  readonly valid?: boolean;
@@ -26,6 +30,8 @@ export interface DocumentSignals {
26
30
  readonly descriptions: readonly ElementSignal[];
27
31
  readonly canonicals: readonly ElementSignal[];
28
32
  readonly robots: readonly RobotsSignal[];
33
+ /** Present on current probes; optional for low-level callers and older in-memory results. */
34
+ readonly socialMetadata?: readonly SocialMetadataSignal[];
29
35
  readonly h1s: readonly ElementSignal[];
30
36
  readonly firstMainText?: ElementSignal;
31
37
  readonly jsonLd: readonly JsonLdSignal[];
@@ -89,10 +95,16 @@ export interface TargetExpectations {
89
95
  readonly requireCanonical: boolean;
90
96
  readonly requireH1: boolean;
91
97
  readonly requireMainText: boolean;
98
+ /** Require the four Open Graph protocol basic metadata properties. Defaults to false. */
99
+ readonly requireOpenGraph?: boolean;
100
+ /** Require SSRWire's Twitter Card readiness contract. Defaults to false. */
101
+ readonly requireTwitterCard?: boolean;
92
102
  readonly maxFirstByteMs?: number;
93
103
  readonly maxCriticalMs?: number;
94
104
  }
95
105
  export interface AuditTarget {
106
+ /** Stable identity used to match the same target across reports with different origins. */
107
+ readonly id?: string;
96
108
  readonly url: string;
97
109
  readonly expectations: TargetExpectations;
98
110
  }
@@ -160,6 +172,8 @@ export interface AuditSummary {
160
172
  readonly incomplete: number;
161
173
  }
162
174
  export interface AuditResult {
175
+ /** Version of the persisted audit-report contract, independent of the package version. */
176
+ readonly schemaVersion: 1;
163
177
  readonly version: string;
164
178
  readonly generatedAt: string;
165
179
  readonly durationMs: number;
@@ -168,4 +182,82 @@ export interface AuditResult {
168
182
  readonly summary: AuditSummary;
169
183
  }
170
184
  export type ReportFormat = "terminal" | "json" | "sarif";
185
+ export type ComparisonKind = "regression" | "fixed" | "changed";
186
+ export type ComparisonScope = "target" | "agent" | "finding" | "response" | "metadata" | "timing";
187
+ export interface ComparisonChange {
188
+ readonly kind: ComparisonKind;
189
+ readonly scope: ComparisonScope;
190
+ readonly code: string;
191
+ readonly message: string;
192
+ readonly agent?: string;
193
+ readonly field?: string;
194
+ readonly baseline?: string | number | boolean;
195
+ readonly candidate?: string | number | boolean;
196
+ }
197
+ export interface ComparisonTimelineEvent {
198
+ readonly key: string;
199
+ readonly label: string;
200
+ readonly medianMs: number;
201
+ readonly location?: ElementLocation | "mixed";
202
+ readonly observedByByte?: number;
203
+ }
204
+ export interface ComparisonTimelineSnapshot {
205
+ readonly samples: number;
206
+ readonly events: readonly ComparisonTimelineEvent[];
207
+ }
208
+ export interface ComparisonTimelineLane {
209
+ readonly agent: string;
210
+ readonly label: string;
211
+ readonly baseline?: ComparisonTimelineSnapshot;
212
+ readonly candidate?: ComparisonTimelineSnapshot;
213
+ }
214
+ export type TargetComparisonStatus = "matched" | "added" | "removed";
215
+ export interface TargetComparison {
216
+ readonly key: string;
217
+ readonly id?: string;
218
+ readonly status: TargetComparisonStatus;
219
+ readonly baselineUrl?: string;
220
+ readonly candidateUrl?: string;
221
+ readonly changes: readonly ComparisonChange[];
222
+ readonly timelines: readonly ComparisonTimelineLane[];
223
+ }
224
+ export interface AuditReportDescriptor {
225
+ readonly label: string;
226
+ readonly version: string;
227
+ readonly schemaVersion: 1;
228
+ readonly generatedAt: string;
229
+ readonly repeat: number;
230
+ }
231
+ export interface ComparisonThresholds {
232
+ readonly timingRegressionMs: number;
233
+ readonly timingRegressionPercent: number;
234
+ }
235
+ export interface ComparisonSummary {
236
+ readonly targets: number;
237
+ readonly matchedTargets: number;
238
+ readonly addedTargets: number;
239
+ readonly removedTargets: number;
240
+ readonly unchangedTargets: number;
241
+ readonly regressions: number;
242
+ readonly fixed: number;
243
+ readonly changed: number;
244
+ }
245
+ export interface AuditComparison {
246
+ readonly schemaVersion: 1;
247
+ readonly kind: "comparison";
248
+ readonly version: string;
249
+ readonly generatedAt: string;
250
+ readonly baseline: AuditReportDescriptor;
251
+ readonly candidate: AuditReportDescriptor;
252
+ readonly thresholds: ComparisonThresholds;
253
+ readonly results: readonly TargetComparison[];
254
+ readonly summary: ComparisonSummary;
255
+ }
256
+ export interface CompareAuditOptions {
257
+ readonly baselineLabel?: string;
258
+ readonly candidateLabel?: string;
259
+ readonly timingRegressionMs?: number;
260
+ readonly timingRegressionPercent?: number;
261
+ }
262
+ export type ComparisonReportFormat = "terminal" | "json" | "html";
171
263
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;AAEpD,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC;AAE3D,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,aAAc,SAAQ,UAAU;IAC/C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;CACpC;AAED,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,WAAW,GAAG,SAAS,CAAC;AAEhE,MAAM,WAAW,YAAa,SAAQ,aAAa;IACjD,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;CACnC;AAED,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;IAC3C,QAAQ,CAAC,YAAY,EAAE,SAAS,aAAa,EAAE,CAAC;IAChD,QAAQ,CAAC,UAAU,EAAE,SAAS,aAAa,EAAE,CAAC;IAC9C,QAAQ,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,CAAC;IACzC,QAAQ,CAAC,GAAG,EAAE,SAAS,aAAa,EAAE,CAAC;IACvC,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,CAAC;IACzC,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC;IACjC,QAAQ,CAAC,WAAW,CAAC,EAAE,UAAU,CAAC;IAClC,QAAQ,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC;CACtC;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAC;CACxC;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAClD,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;CACpC;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,MAAM,eAAe,GACvB,UAAU,GACV,oBAAoB,GACpB,SAAS,GACT,eAAe,GACf,kBAAkB,CAAC;AAEvB,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACpD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,kGAAkG;IAClG,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;CACvC;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,SAAS,WAAW,EAAE,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAC;IACrC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,kFAAkF;IAClF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;IACrC,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,YAAY,EAAE,kBAAkB,CAAC;CAC3C;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;IACzC,QAAQ,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,CAAC;IACzC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,oFAAoF;IACpF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC;IAC/B,QAAQ,CAAC,SAAS,CAAC,EAAE,WAAW,CAAC;IACjC,QAAQ,CAAC,eAAe,CAAC,EAAE,WAAW,CAAC;IACvC,QAAQ,CAAC,QAAQ,CAAC,EAAE,WAAW,CAAC;CACjC;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;CACtC;AAED,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;CACzE;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,CAAC;IACxC,QAAQ,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;IACtC,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;CAChD;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC/C,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;CAChC;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -27,7 +27,7 @@ jobs:
27
27
  if: github.event_name == 'pull_request'
28
28
  continue-on-error: true
29
29
  run: >-
30
- npx --yes ssrwire@0.2.0 check
30
+ npx --yes ssrwire@0.4.0 check
31
31
  --config ssrwire.config.yml
32
32
  --format sarif
33
33
  --output reports/ssrwire.sarif
@@ -38,7 +38,7 @@ jobs:
38
38
  if: github.event_name != 'pull_request'
39
39
  continue-on-error: true
40
40
  run: >-
41
- npx --yes ssrwire@0.2.0 check
41
+ npx --yes ssrwire@0.4.0 check
42
42
  --config ssrwire.config.yml
43
43
  --format sarif
44
44
  --output reports/ssrwire.sarif
@@ -1,6 +1,7 @@
1
1
  # Replace these URLs with a small set of high-value SSR routes.
2
2
  targets:
3
- - url: https://example.com/
3
+ - id: home
4
+ url: https://example.com/
4
5
  expectedStatus: 200
5
6
  expectedFinalUrl: https://example.com/
6
7
  require:
@@ -9,8 +10,11 @@ targets:
9
10
  canonical: true
10
11
  h1: true
11
12
  mainText: true
13
+ openGraph: true
14
+ twitterCard: true
12
15
 
13
- - url: https://example.com/pricing/
16
+ - id: pricing
17
+ url: https://example.com/pricing/
14
18
  expectedStatus: 200
15
19
  require:
16
20
  title: true
@@ -18,6 +22,8 @@ targets:
18
22
  canonical: true
19
23
  h1: true
20
24
  mainText: true
25
+ openGraph: false
26
+ twitterCard: false
21
27
 
22
28
  # Add maxFirstByteMs or maxCriticalMs per target only when the runner location
23
29
  # and cache state are stable enough for a meaningful threshold.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ssrwire",
3
- "version": "0.2.0",
4
- "description": "Inspect streamed SSR HTML, metadata timing, and crawler-specific delivery from the command line.",
3
+ "version": "0.4.0",
4
+ "description": "Inspect streamed SSR HTML, SEO and social metadata timing, and crawler-specific delivery from the command line.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "ssrwire": "./dist/bin.js"
@@ -52,6 +52,9 @@
52
52
  "cli",
53
53
  "typescript",
54
54
  "seo-tools",
55
+ "open-graph",
56
+ "social-preview",
57
+ "twitter-cards",
55
58
  "github-actions",
56
59
  "sarif"
57
60
  ],
package/src/analyze.ts CHANGED
@@ -1,3 +1,14 @@
1
+ import {
2
+ effectiveTwitterCardSignal,
3
+ firstSocialSignal,
4
+ isAbsoluteHttpSocialUrl,
5
+ normalizeSocialValue,
6
+ OPEN_GRAPH_PROPERTIES,
7
+ OPEN_GRAPH_REQUIRED_PROPERTIES,
8
+ socialSignals,
9
+ TWITTER_CARD_REQUIRED_FIELDS,
10
+ TWITTER_PROPERTIES,
11
+ } from "./social.js";
1
12
  import type {
2
13
  AuditSummary,
3
14
  AuditTarget,
@@ -7,6 +18,8 @@ import type {
7
18
  RobotsAudience,
8
19
  RobotsSignal,
9
20
  Severity,
21
+ SocialMetadataProperty,
22
+ SocialMetadataSignal,
10
23
  TargetAuditResult,
11
24
  } from "./types.js";
12
25
 
@@ -264,6 +277,146 @@ function checkRepeatedRobots(findings: Finding[], targetUrl: string, probe: Prob
264
277
  }
265
278
  }
266
279
 
280
+ function auditedSocialProperties(
281
+ target: AuditTarget,
282
+ probe: ProbeResult,
283
+ ): readonly SocialMetadataProperty[] {
284
+ const properties = new Set<SocialMetadataProperty>();
285
+ if (target.expectations.requireOpenGraph === true) {
286
+ for (const property of OPEN_GRAPH_PROPERTIES) properties.add(property);
287
+ }
288
+ if (target.expectations.requireTwitterCard === true) {
289
+ for (const property of TWITTER_PROPERTIES) properties.add(property);
290
+ if (firstSocialSignal(probe.signals, "twitter:title") === undefined) {
291
+ properties.add("og:title");
292
+ }
293
+ if (firstSocialSignal(probe.signals, "twitter:description") === undefined) {
294
+ properties.add("og:description");
295
+ }
296
+ if (firstSocialSignal(probe.signals, "twitter:image") === undefined) {
297
+ properties.add("og:image");
298
+ }
299
+ }
300
+ return [...properties];
301
+ }
302
+
303
+ function normalizedSocialValues(
304
+ property: SocialMetadataProperty,
305
+ signals: readonly SocialMetadataSignal[],
306
+ baseUrl: string,
307
+ ): readonly string[] {
308
+ return signals
309
+ .map((signal) => normalizeSocialValue(property, signal.value, baseUrl))
310
+ .filter((value) => value.length > 0);
311
+ }
312
+
313
+ function checkRepeatedSocialMetadata(
314
+ findings: Finding[],
315
+ target: AuditTarget,
316
+ probe: ProbeResult,
317
+ ): void {
318
+ for (const property of auditedSocialProperties(target, probe)) {
319
+ // Open Graph explicitly permits multiple images and gives the first one precedence.
320
+ if (property === "og:image") continue;
321
+ const values = normalizedSocialValues(
322
+ property,
323
+ socialSignals(probe.signals, property),
324
+ probe.finalUrl,
325
+ );
326
+ if (values.length < 2) continue;
327
+
328
+ const distinct = new Set(values);
329
+ const conflicting = distinct.size > 1;
330
+ addFinding(findings, target.url, {
331
+ code: conflicting ? "conflicting-social-metadata" : "duplicate-social-metadata",
332
+ severity: "warning",
333
+ message: conflicting
334
+ ? `${probe.agent.label} received conflicting ${property} values.`
335
+ : `${probe.agent.label} received duplicate ${property} values.`,
336
+ agent: probe.agent.key,
337
+ evidence: {
338
+ property,
339
+ count: values.length,
340
+ distinctValues: distinct.size,
341
+ values: [...distinct].join(" | "),
342
+ },
343
+ });
344
+ }
345
+ }
346
+
347
+ function checkSocialUrls(findings: Finding[], target: AuditTarget, probe: ProbeResult): void {
348
+ const candidates = new Set<SocialMetadataSignal>();
349
+ if (target.expectations.requireOpenGraph === true) {
350
+ for (const signal of socialSignals(probe.signals, "og:url")) candidates.add(signal);
351
+ for (const signal of socialSignals(probe.signals, "og:image")) candidates.add(signal);
352
+ }
353
+ if (target.expectations.requireTwitterCard === true) {
354
+ const image = effectiveTwitterCardSignal(probe.signals, "image");
355
+ if (image !== undefined) candidates.add(image);
356
+ }
357
+
358
+ for (const signal of candidates) {
359
+ if (signal.value.trim().length === 0 || isAbsoluteHttpSocialUrl(signal.value)) continue;
360
+ addFinding(findings, target.url, {
361
+ code: "invalid-social-metadata-url",
362
+ severity: "warning",
363
+ message: `${probe.agent.label} received a non-absolute HTTP(S) ${signal.property} URL.`,
364
+ agent: probe.agent.key,
365
+ evidence: { property: signal.property, value: signal.value },
366
+ });
367
+ }
368
+ }
369
+
370
+ function checkRequiredSocialMetadata(
371
+ findings: Finding[],
372
+ target: AuditTarget,
373
+ probe: ProbeResult,
374
+ ): void {
375
+ if (probe.completion !== "complete") return;
376
+
377
+ if (target.expectations.requireOpenGraph === true) {
378
+ const missing = OPEN_GRAPH_REQUIRED_PROPERTIES.filter(
379
+ (property) => firstSocialSignal(probe.signals, property) === undefined,
380
+ );
381
+ if (missing.length > 0) {
382
+ addFinding(findings, target.url, {
383
+ code: "missing-open-graph-metadata",
384
+ severity: "warning",
385
+ message: `${probe.agent.label} received an incomplete Open Graph metadata set.`,
386
+ agent: probe.agent.key,
387
+ evidence: { fields: missing.join(", ") },
388
+ });
389
+ }
390
+ }
391
+
392
+ if (target.expectations.requireTwitterCard === true) {
393
+ const missing = TWITTER_CARD_REQUIRED_FIELDS.filter(
394
+ (field) => effectiveTwitterCardSignal(probe.signals, field) === undefined,
395
+ );
396
+ if (missing.length > 0) {
397
+ addFinding(findings, target.url, {
398
+ code: "missing-twitter-card-metadata",
399
+ severity: "warning",
400
+ message: `${probe.agent.label} received an incomplete Twitter Card metadata set.`,
401
+ agent: probe.agent.key,
402
+ evidence: { fields: missing.join(", ") },
403
+ });
404
+ }
405
+ }
406
+ }
407
+
408
+ function checkSocialMetadata(findings: Finding[], target: AuditTarget, probe: ProbeResult): void {
409
+ if (
410
+ target.expectations.requireOpenGraph !== true &&
411
+ target.expectations.requireTwitterCard !== true
412
+ ) {
413
+ return;
414
+ }
415
+ checkRequiredSocialMetadata(findings, target, probe);
416
+ checkRepeatedSocialMetadata(findings, target, probe);
417
+ checkSocialUrls(findings, target, probe);
418
+ }
419
+
267
420
  function criticalArrivalMs(target: AuditTarget, probe: ProbeResult): number | undefined {
268
421
  const marks: number[] = [];
269
422
  const { expectations } = target;
@@ -299,6 +452,20 @@ function criticalArrivalMs(target: AuditTarget, probe: ProbeResult): number | un
299
452
  ) {
300
453
  marks.push(probe.signals.firstMainText?.atMs ?? 0);
301
454
  }
455
+ if (expectations.requireOpenGraph === true) {
456
+ const signals = OPEN_GRAPH_REQUIRED_PROPERTIES.map((property) =>
457
+ firstSocialSignal(probe.signals, property),
458
+ );
459
+ if (!signals.every((signal) => signal !== undefined)) return undefined;
460
+ marks.push(...signals.map((signal) => signal.atMs));
461
+ }
462
+ if (expectations.requireTwitterCard === true) {
463
+ const signals = TWITTER_CARD_REQUIRED_FIELDS.map((field) =>
464
+ effectiveTwitterCardSignal(probe.signals, field),
465
+ );
466
+ if (!signals.every((signal) => signal !== undefined)) return undefined;
467
+ marks.push(...signals.map((signal) => signal.atMs));
468
+ }
302
469
 
303
470
  return marks.length === 0 ? undefined : Math.max(...marks);
304
471
  }
@@ -365,7 +532,7 @@ function checkRequiredSignals(findings: Finding[], target: AuditTarget, probe: P
365
532
  }
366
533
  }
367
534
 
368
- function checkHeadRequirements(findings: Finding[], targetUrl: string, probe: ProbeResult): void {
535
+ function checkHeadRequirements(findings: Finding[], target: AuditTarget, probe: ProbeResult): void {
369
536
  if (!probe.agent.requiresHeadMetadata) {
370
537
  return;
371
538
  }
@@ -384,12 +551,17 @@ function checkHeadRequirements(findings: Finding[], targetUrl: string, probe: Pr
384
551
  if (effectiveRobotsSignals(probe).some((signal) => signal.location === "body")) {
385
552
  bodyFields.add("robots");
386
553
  }
554
+ for (const property of auditedSocialProperties(target, probe)) {
555
+ if (socialSignals(probe.signals, property).some((signal) => signal.location === "body")) {
556
+ bodyFields.add(property);
557
+ }
558
+ }
387
559
 
388
560
  if (bodyFields.size === 0) {
389
561
  return;
390
562
  }
391
563
 
392
- addFinding(findings, targetUrl, {
564
+ addFinding(findings, target.url, {
393
565
  code: "head-metadata-in-body",
394
566
  severity: "error",
395
567
  message: `${probe.agent.label} requires head metadata but received ${[...bodyFields].join(
@@ -440,9 +612,31 @@ function agentValueSummary(
440
612
  return probes.map((probe) => `${probe.agent.key}=${valueFor(probe)}`).join("; ");
441
613
  }
442
614
 
615
+ function openGraphValue(probe: ProbeResult): string {
616
+ return OPEN_GRAPH_PROPERTIES.map((property) => {
617
+ const values = [
618
+ ...new Set(
619
+ normalizedSocialValues(property, socialSignals(probe.signals, property), probe.finalUrl),
620
+ ),
621
+ ];
622
+ return `${property}=${values.join(" | ") || "<missing>"}`;
623
+ }).join(", ");
624
+ }
625
+
626
+ function twitterCardValue(probe: ProbeResult): string {
627
+ return TWITTER_CARD_REQUIRED_FIELDS.map((field) => {
628
+ const signal = effectiveTwitterCardSignal(probe.signals, field);
629
+ const value =
630
+ signal === undefined
631
+ ? "<missing>"
632
+ : normalizeSocialValue(signal.property, signal.value, probe.finalUrl);
633
+ return `${field}=${value || "<missing>"}`;
634
+ }).join(", ");
635
+ }
636
+
443
637
  function checkAgentDrift(
444
638
  findings: Finding[],
445
- targetUrl: string,
639
+ target: AuditTarget,
446
640
  probes: readonly ProbeResult[],
447
641
  ): void {
448
642
  const complete = probes.filter((probe) => probe.completion === "complete");
@@ -450,7 +644,7 @@ function checkAgentDrift(
450
644
  return;
451
645
  }
452
646
 
453
- const comparisons: readonly {
647
+ const comparisons: {
454
648
  readonly field: string;
455
649
  readonly valueFor: (probe: ProbeResult) => string;
456
650
  }[] = [
@@ -472,6 +666,12 @@ function checkAgentDrift(
472
666
  valueFor: effectiveRobotsValue,
473
667
  },
474
668
  ];
669
+ if (target.expectations.requireOpenGraph === true) {
670
+ comparisons.push({ field: "open-graph", valueFor: openGraphValue });
671
+ }
672
+ if (target.expectations.requireTwitterCard === true) {
673
+ comparisons.push({ field: "twitter-card", valueFor: twitterCardValue });
674
+ }
475
675
 
476
676
  for (const comparison of comparisons) {
477
677
  const values = complete.map(comparison.valueFor);
@@ -479,7 +679,7 @@ function checkAgentDrift(
479
679
  continue;
480
680
  }
481
681
 
482
- addFinding(findings, targetUrl, {
682
+ addFinding(findings, target.url, {
483
683
  code: `agent-${comparison.field}-drift`,
484
684
  severity: "warning",
485
685
  message: `Crawler profiles received different ${comparison.field.replace("-", " ")} values.`,
@@ -556,6 +756,7 @@ export function analyzeTarget(
556
756
  checkRepeatedMetadata(findings, target.url, probe, "description", probe.signals.descriptions);
557
757
  checkRepeatedMetadata(findings, target.url, probe, "canonical", probe.signals.canonicals);
558
758
  checkRepeatedRobots(findings, target.url, probe);
759
+ checkSocialMetadata(findings, target, probe);
559
760
 
560
761
  const invalidJsonLd = probe.signals.jsonLd.filter((signal) => signal.valid === false);
561
762
  if (probe.completion === "complete" && invalidJsonLd.length > 0) {
@@ -587,11 +788,11 @@ export function analyzeTarget(
587
788
  });
588
789
  }
589
790
 
590
- checkHeadRequirements(findings, target.url, probe);
791
+ checkHeadRequirements(findings, target, probe);
591
792
  checkTimings(findings, target, probe);
592
793
  }
593
794
 
594
- checkAgentDrift(findings, target.url, probes);
795
+ checkAgentDrift(findings, target, probes);
595
796
 
596
797
  return findings;
597
798
  }