@rivi-software/agentic-seo-check 0.1.0 → 0.2.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 (144) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +124 -18
  3. package/dist/brand.d.ts +11 -0
  4. package/dist/brand.js +22 -0
  5. package/dist/brand.js.map +1 -0
  6. package/dist/cli.js +174 -86
  7. package/dist/cli.js.map +1 -1
  8. package/dist/core/aeo/audit.d.ts +44 -0
  9. package/dist/core/aeo/audit.js +310 -0
  10. package/dist/core/aeo/audit.js.map +1 -0
  11. package/dist/core/aeo/bots.d.ts +30 -0
  12. package/dist/core/aeo/bots.js +299 -0
  13. package/dist/core/aeo/bots.js.map +1 -0
  14. package/dist/core/aeo/llms-txt.d.ts +8 -0
  15. package/dist/core/aeo/llms-txt.js +88 -0
  16. package/dist/core/aeo/llms-txt.js.map +1 -0
  17. package/dist/core/aeo/page.d.ts +53 -0
  18. package/dist/core/aeo/page.js +312 -0
  19. package/dist/core/aeo/page.js.map +1 -0
  20. package/dist/core/aeo/probe.d.ts +10 -0
  21. package/dist/core/aeo/probe.js +53 -0
  22. package/dist/core/aeo/probe.js.map +1 -0
  23. package/dist/core/aeo/site-rules.d.ts +13 -0
  24. package/dist/core/aeo/site-rules.js +178 -0
  25. package/dist/core/aeo/site-rules.js.map +1 -0
  26. package/dist/core/audit.d.ts +37 -16
  27. package/dist/core/audit.js +389 -249
  28. package/dist/core/audit.js.map +1 -1
  29. package/dist/core/concurrency.d.ts +4 -4
  30. package/dist/core/concurrency.js +33 -23
  31. package/dist/core/concurrency.js.map +1 -1
  32. package/dist/core/crawl.d.ts +9 -5
  33. package/dist/core/crawl.js +271 -123
  34. package/dist/core/crawl.js.map +1 -1
  35. package/dist/core/fetch.d.ts +5 -5
  36. package/dist/core/fetch.js +151 -100
  37. package/dist/core/fetch.js.map +1 -1
  38. package/dist/core/html/answer.d.ts +4 -0
  39. package/dist/core/html/answer.js +198 -0
  40. package/dist/core/html/answer.js.map +1 -0
  41. package/dist/core/html/dom.d.ts +17 -0
  42. package/dist/core/html/dom.js +83 -0
  43. package/dist/core/html/dom.js.map +1 -0
  44. package/dist/core/html/extract.d.ts +2 -2
  45. package/dist/core/html/extract.js +102 -111
  46. package/dist/core/html/extract.js.map +1 -1
  47. package/dist/core/html/structured-data.d.ts +16 -6
  48. package/dist/core/html/structured-data.js +180 -145
  49. package/dist/core/html/structured-data.js.map +1 -1
  50. package/dist/core/html/text.d.ts +14 -14
  51. package/dist/core/html/text.js +110 -107
  52. package/dist/core/html/text.js.map +1 -1
  53. package/dist/core/lighthouse.d.ts +26 -25
  54. package/dist/core/lighthouse.js +181 -105
  55. package/dist/core/lighthouse.js.map +1 -1
  56. package/dist/core/performance-rules.d.ts +2 -2
  57. package/dist/core/performance-rules.js +105 -33
  58. package/dist/core/performance-rules.js.map +1 -1
  59. package/dist/core/render-rules.d.ts +5 -4
  60. package/dist/core/render-rules.js +129 -62
  61. package/dist/core/render-rules.js.map +1 -1
  62. package/dist/core/render.d.ts +2 -2
  63. package/dist/core/render.js +29 -23
  64. package/dist/core/render.js.map +1 -1
  65. package/dist/core/report/markdown.d.ts +4 -2
  66. package/dist/core/report/markdown.js +210 -129
  67. package/dist/core/report/markdown.js.map +1 -1
  68. package/dist/core/robots.d.ts +12 -5
  69. package/dist/core/robots.js +43 -17
  70. package/dist/core/robots.js.map +1 -1
  71. package/dist/core/rules/aeo.d.ts +11 -0
  72. package/dist/core/rules/aeo.js +423 -0
  73. package/dist/core/rules/aeo.js.map +1 -0
  74. package/dist/core/rules/canonical.d.ts +1 -1
  75. package/dist/core/rules/canonical.js +93 -57
  76. package/dist/core/rules/canonical.js.map +1 -1
  77. package/dist/core/rules/content.js +40 -26
  78. package/dist/core/rules/content.js.map +1 -1
  79. package/dist/core/rules/document.js +64 -41
  80. package/dist/core/rules/document.js.map +1 -1
  81. package/dist/core/rules/headings.js +58 -38
  82. package/dist/core/rules/headings.js.map +1 -1
  83. package/dist/core/rules/hreflang.js +70 -38
  84. package/dist/core/rules/hreflang.js.map +1 -1
  85. package/dist/core/rules/images.js +44 -26
  86. package/dist/core/rules/images.js.map +1 -1
  87. package/dist/core/rules/index.d.ts +10 -6
  88. package/dist/core/rules/index.js +43 -36
  89. package/dist/core/rules/index.js.map +1 -1
  90. package/dist/core/rules/indexing.d.ts +2 -2
  91. package/dist/core/rules/indexing.js +48 -26
  92. package/dist/core/rules/indexing.js.map +1 -1
  93. package/dist/core/rules/keyword.js +84 -47
  94. package/dist/core/rules/keyword.js.map +1 -1
  95. package/dist/core/rules/links.js +72 -37
  96. package/dist/core/rules/links.js.map +1 -1
  97. package/dist/core/rules/meta.js +55 -34
  98. package/dist/core/rules/meta.js.map +1 -1
  99. package/dist/core/rules/response.js +75 -41
  100. package/dist/core/rules/response.js.map +1 -1
  101. package/dist/core/rules/rule.d.ts +6 -6
  102. package/dist/core/rules/rule.js +36 -36
  103. package/dist/core/rules/rule.js.map +1 -1
  104. package/dist/core/rules/social.js +86 -45
  105. package/dist/core/rules/social.js.map +1 -1
  106. package/dist/core/rules/structured-data.d.ts +6 -4
  107. package/dist/core/rules/structured-data.js +314 -102
  108. package/dist/core/rules/structured-data.js.map +1 -1
  109. package/dist/core/rules/title.js +48 -33
  110. package/dist/core/rules/title.js.map +1 -1
  111. package/dist/core/score.d.ts +5 -3
  112. package/dist/core/score.js +11 -9
  113. package/dist/core/score.js.map +1 -1
  114. package/dist/core/site-rules.d.ts +3 -3
  115. package/dist/core/site-rules.js +409 -198
  116. package/dist/core/site-rules.js.map +1 -1
  117. package/dist/core/sitemap.d.ts +5 -5
  118. package/dist/core/sitemap.js +109 -64
  119. package/dist/core/sitemap.js.map +1 -1
  120. package/dist/core/static.d.ts +2 -2
  121. package/dist/core/static.js +210 -112
  122. package/dist/core/static.js.map +1 -1
  123. package/dist/core/summary.d.ts +2 -2
  124. package/dist/core/summary.js +11 -6
  125. package/dist/core/summary.js.map +1 -1
  126. package/dist/core/types.d.ts +189 -14
  127. package/dist/core/types.js +15 -8
  128. package/dist/core/types.js.map +1 -1
  129. package/dist/core/url.d.ts +11 -11
  130. package/dist/core/url.js +122 -70
  131. package/dist/core/url.js.map +1 -1
  132. package/dist/index.d.ts +15 -11
  133. package/dist/index.js +11 -9
  134. package/dist/index.js.map +1 -1
  135. package/dist/mcp-bin.js +7 -4
  136. package/dist/mcp-bin.js.map +1 -1
  137. package/dist/mcp.d.ts +3 -3
  138. package/dist/mcp.js +437 -121
  139. package/dist/mcp.js.map +1 -1
  140. package/dist/version.d.ts +0 -1
  141. package/dist/version.js +4 -2
  142. package/dist/version.js.map +1 -1
  143. package/package.json +32 -10
  144. package/skills/seo-audit/SKILL.md +8 -2
@@ -2,10 +2,10 @@
2
2
  * Shared types for the SEO audit core. These shapes are the public contract
3
3
  * of the CLI JSON output and the MCP tool results (`schemaVersion: 1`).
4
4
  */
5
- export type Severity = 'error' | 'warning' | 'info';
5
+ export type Severity = "error" | "warning" | "info";
6
6
  export declare const SEVERITY_RANK: Record<Severity, number>;
7
- export type Category = 'title' | 'meta' | 'headings' | 'canonical' | 'indexing' | 'document' | 'social' | 'images' | 'links' | 'international' | 'structured-data' | 'content' | 'keyword' | 'response' | 'rendering' | 'performance' | 'site' | 'tool';
8
- export type Requirement = 'render' | 'lighthouse' | 'crawl' | 'keyword';
7
+ export type Category = "title" | "meta" | "headings" | "canonical" | "indexing" | "document" | "social" | "images" | "links" | "international" | "structured-data" | "content" | "keyword" | "response" | "rendering" | "performance" | "site" | "aeo" | "tool";
8
+ export type Requirement = "render" | "lighthouse" | "crawl" | "keyword" | "ai-bots";
9
9
  /** Static description of a rule, exposed through `seo_list_rules` / `agentic-seo-check rules`. */
10
10
  export interface RuleMeta {
11
11
  id: string;
@@ -68,16 +68,51 @@ export interface HreflangInfo {
68
68
  url: string | null;
69
69
  }
70
70
  export interface StructuredDataItem {
71
- source: 'json-ld' | 'microdata';
71
+ source: "json-ld" | "microdata";
72
72
  types: string[];
73
73
  context: string | null;
74
74
  data: Record<string, unknown>;
75
75
  }
76
76
  export interface StructuredDataError {
77
- source: 'json-ld';
77
+ source: "json-ld";
78
78
  message: string;
79
79
  snippet: string;
80
80
  }
81
+ /** A heading (or FAQ `<summary>`/`<dt>`) phrased as a question, and the text that follows it. */
82
+ export interface QuestionHeading {
83
+ /** 1-6 for h1-h6, 0 for `<summary>` and `<dt>`. */
84
+ level: number;
85
+ text: string;
86
+ /** Words between the question and the next heading or question. */
87
+ sectionWords: number;
88
+ /** Words in the first paragraph, list item or definition after the question, or null when there is none. */
89
+ firstBlockWords: number | null;
90
+ }
91
+ /** What an answer engine needs from a page beyond classic SEO, read once by the extractor. */
92
+ export interface AnswerSignals {
93
+ /** Content of each robots meta tag by name (robots, googlebot, bingbot, ...), lower-cased. */
94
+ robotsMeta: {
95
+ name: string;
96
+ content: string;
97
+ }[];
98
+ questions: QuestionHeading[];
99
+ /** Lists with two or more items outside navigation, header, footer and aside. */
100
+ lists: number;
101
+ /** Tables with two or more rows outside navigation, header, footer and aside. */
102
+ tables: number;
103
+ /** Elements with `data-nosnippet`, the outermost ones only. */
104
+ nosnippetElements: number;
105
+ nosnippetWords: number;
106
+ nosnippetSelectors: string[];
107
+ /** Selector of an empty JavaScript app root (`#root`, `#__next`, ...), when the page has one. */
108
+ emptyAppRoot: string | null;
109
+ /** A `<noscript>` element mentions JavaScript ("please enable JavaScript"). */
110
+ noscriptAsksForJavaScript: boolean;
111
+ /** `<time>` elements in the body. */
112
+ timeElements: number;
113
+ /** The visible text contains a written date (2026-03-14, 14/03/2026, March 14, 2026, 14 March 2026). */
114
+ mentionsDate: boolean;
115
+ }
81
116
  /** Everything the extractor pulls out of one HTML document. Rules read only this. */
82
117
  export interface ExtractedPage {
83
118
  /** URL used to resolve relative links (the final URL after redirects, or the base URL for files/inline HTML). */
@@ -108,6 +143,8 @@ export interface ExtractedPage {
108
143
  wordCount: number;
109
144
  htmlBytes: number;
110
145
  hasBaseHref: boolean;
146
+ /** Signals the AEO audit reads; the SEO rules do not use them. */
147
+ answer: AnswerSignals;
111
148
  }
112
149
  export interface RedirectHop {
113
150
  url: string;
@@ -177,9 +214,9 @@ export interface LighthouseAuditSummary {
177
214
  displayValue: string | null;
178
215
  }
179
216
  export interface PerformanceResult {
180
- tool: 'lighthouse';
217
+ tool: "lighthouse";
181
218
  lighthouseVersion: string;
182
- formFactor: 'mobile' | 'desktop';
219
+ formFactor: "mobile" | "desktop";
183
220
  requestedUrl: string;
184
221
  finalUrl: string;
185
222
  metrics: LighthouseMetrics;
@@ -188,7 +225,7 @@ export interface PerformanceResult {
188
225
  agenticBrowsingAudits: LighthouseAuditSummary[];
189
226
  }
190
227
  export interface RenderComparison {
191
- tool: 'playwright';
228
+ tool: "playwright";
192
229
  renderedUrl: string;
193
230
  consoleErrors: string[];
194
231
  raw: RenderSnapshot;
@@ -204,10 +241,10 @@ export interface RenderSnapshot {
204
241
  internalLinks: number;
205
242
  structuredDataCount: number;
206
243
  }
207
- export type PageSource = 'url' | 'file' | 'html';
244
+ export type PageSource = "url" | "file" | "html";
208
245
  export interface PageAudit {
209
246
  schemaVersion: 1;
210
- kind: 'page';
247
+ kind: "page";
211
248
  /** What the caller asked for (URL, path, or "inline-html"). */
212
249
  target: string;
213
250
  /** Final URL after redirects, or the file path. */
@@ -264,9 +301,9 @@ export interface IssueRollup {
264
301
  }
265
302
  export interface SiteAudit {
266
303
  schemaVersion: 1;
267
- kind: 'site';
304
+ kind: "site";
268
305
  target: string;
269
- source: 'crawl' | 'directory';
306
+ source: "crawl" | "directory";
270
307
  auditedAt: string;
271
308
  crawl: CrawlStats;
272
309
  robotsTxt: RobotsTxtInfo | null;
@@ -280,6 +317,144 @@ export interface SiteAudit {
280
317
  score: number;
281
318
  }
282
319
  export type Audit = PageAudit | SiteAudit;
320
+ /**
321
+ * What an AI crawler fetches pages for, which decides what blocking it costs: `search` crawlers
322
+ * feed a search index that AI answers draw on (Google AI Overviews, Copilot), `answer` bots fetch
323
+ * pages for an AI answer or an AI search index, `training` bots collect text to train models.
324
+ */
325
+ export type AiBotPurpose = "search" | "answer" | "training";
326
+ /** What the server answered a request with an AI bot's User-Agent (`aiBots` only). */
327
+ export interface BotProbe {
328
+ status: number | null;
329
+ /** The server refused or challenged this User-Agent while the audit's own request got the page. */
330
+ blocked: boolean;
331
+ /** Why it counts as blocked: "HTTP 403", "challenge page", "connection failed: ...". */
332
+ reason: string | null;
333
+ }
334
+ export interface AiBotAccess {
335
+ /** The bot's robots.txt token, e.g. "OAI-SearchBot". */
336
+ name: string;
337
+ operator: string;
338
+ purpose: AiBotPurpose;
339
+ usedFor: string;
340
+ /**
341
+ * The robots.txt group the bot follows: "named" when a group names it, "googlebot" for Applebot
342
+ * following the Googlebot group (Apple's documented fallback), "wildcard" when only
343
+ * `User-agent: *` applies, "none" when nothing does.
344
+ */
345
+ robotsGroup: "named" | "googlebot" | "wildcard" | "none";
346
+ /** robots.txt lets the bot fetch the audited page (the start page of a crawl or directory). */
347
+ allowed: boolean;
348
+ /**
349
+ * Audited pages robots.txt keeps this bot from while `User-agent: *` lets crawlers fetch them.
350
+ * Paths disallowed for every crawler are left to the SEO audit's `site.robots-blocked-pages`.
351
+ */
352
+ blockedPages: number;
353
+ probe?: BotProbe;
354
+ }
355
+ /** One `Content-Signal` line of robots.txt (contentsignals.org). */
356
+ export interface ContentSignal {
357
+ /** User agents of the robots.txt group the line sits in. */
358
+ userAgents: string[];
359
+ /** The path the line is limited to, or null for the whole site. */
360
+ path: string | null;
361
+ /** Each signal and its value as written, lower-cased: { search: "yes", "ai-input": "no" }. */
362
+ signals: Record<string, string>;
363
+ }
364
+ export interface AiCrawlerReport {
365
+ robotsTxt: string;
366
+ status: "found" | "missing" | "unreachable";
367
+ httpStatus: number | null;
368
+ /**
369
+ * The shared AI training preference (`User-Agent: __usage_ai-training`) for the audited page:
370
+ * "disallowed" opts out of training, "none" when robots.txt names no such group.
371
+ */
372
+ trainingPreference: "allowed" | "disallowed" | "none";
373
+ contentSignals: ContentSignal[];
374
+ bots: AiBotAccess[];
375
+ /** With `aiBots`: the User-Agent and status of the audit's own request, which each probe is compared with. */
376
+ probeBaseline?: {
377
+ userAgent: string;
378
+ status: number | null;
379
+ };
380
+ }
381
+ export interface LlmsTxtInfo {
382
+ url: string;
383
+ status: "found" | "missing" | "invalid";
384
+ httpStatus: number | null;
385
+ /** The H1 line, the one part the llms.txt format requires. */
386
+ title: string | null;
387
+ /** The blockquote after the title. */
388
+ summary: string | null;
389
+ /** H2 section names (capped). */
390
+ sections: string[];
391
+ /** Markdown links in the file. */
392
+ links: number;
393
+ bytes: number;
394
+ /** Why the file does not follow the format, when it does not. */
395
+ problems: string[];
396
+ }
397
+ export interface AeoPageSummary {
398
+ indexable: boolean;
399
+ /** "blocked" by nosnippet or max-snippet:0, "limited" by a short max-snippet or data-nosnippet. */
400
+ googleSnippets: "allowed" | "limited" | "blocked";
401
+ /** "excluded" by noarchive or noindex, "snippet-only" by nocache (Bing's rules for Copilot). */
402
+ copilotAnswers: "allowed" | "snippet-only" | "excluded";
403
+ wordCount: number;
404
+ questions: number;
405
+ structuredDataTypes: string[];
406
+ author: string | null;
407
+ datePublished: string | null;
408
+ dateModified: string | null;
409
+ }
410
+ export interface AeoPage {
411
+ url: string;
412
+ depth?: number;
413
+ score: number;
414
+ counts: SeverityCounts;
415
+ summary: AeoPageSummary;
416
+ }
417
+ export interface AeoSummary {
418
+ pages: number;
419
+ /** null when the target has no site to read robots.txt from (an HTML string or a single file). */
420
+ robotsTxt: "found" | "missing" | "unreachable" | null;
421
+ searchCrawlersBlocked: string[];
422
+ answerBotsBlocked: string[];
423
+ trainingBotsBlocked: string[];
424
+ trainingPreference: "allowed" | "disallowed" | "none" | null;
425
+ /** Bots the server refused or challenged, with `aiBots`; null without it. */
426
+ blockedByServer: string[] | null;
427
+ llmsTxt: "found" | "missing" | "invalid" | null;
428
+ /** The audited page, or the start page of a crawl or directory. */
429
+ page: AeoPageSummary | null;
430
+ }
431
+ /**
432
+ * An answer engine optimization audit: can AI crawlers reach the site, may answer engines quote
433
+ * it, and is the content easy to lift into an answer. Its score counts AEO findings only.
434
+ */
435
+ export interface AeoAudit {
436
+ schemaVersion: 1;
437
+ kind: "aeo";
438
+ target: string;
439
+ url: string;
440
+ source: PageSource | "crawl" | "directory";
441
+ /** 0-100. Indicative only: the average page score minus weighted site-wide findings. */
442
+ score: number;
443
+ counts: SeverityCounts;
444
+ auditedAt: string;
445
+ summary: AeoSummary;
446
+ /**
447
+ * Site-wide findings (robots.txt, llms.txt, bot probes), then the page findings: all of them for
448
+ * one page or with `pageDetail: "full"`, none for a site with `pageDetail: "summary"` (see `issues`).
449
+ */
450
+ findings: Finding[];
451
+ crawlers: AiCrawlerReport | null;
452
+ llmsTxt: LlmsTxtInfo | null;
453
+ /** Crawl and directory audits: each page's score and summary. */
454
+ pages?: AeoPage[];
455
+ /** Crawl and directory audits: page findings rolled up by rule. */
456
+ issues?: IssueRollup[];
457
+ }
283
458
  export interface Thresholds {
284
459
  titleMinChars: number;
285
460
  titleMaxChars: number;
@@ -292,5 +467,5 @@ export interface Thresholds {
292
467
  maxKeywordDensityPercent: number;
293
468
  }
294
469
  export declare const DEFAULT_THRESHOLDS: Thresholds;
295
- export declare function emptyCounts(): SeverityCounts;
296
- export declare function countFindings(findings: Finding[]): SeverityCounts;
470
+ export declare const emptyCounts: () => SeverityCounts;
471
+ export declare const countFindings: (findings: Finding[]) => SeverityCounts;
@@ -2,7 +2,11 @@
2
2
  * Shared types for the SEO audit core. These shapes are the public contract
3
3
  * of the CLI JSON output and the MCP tool results (`schemaVersion: 1`).
4
4
  */
5
- export const SEVERITY_RANK = { error: 3, warning: 2, info: 1 };
5
+ export const SEVERITY_RANK = {
6
+ error: 3,
7
+ warning: 2,
8
+ info: 1,
9
+ };
6
10
  export const DEFAULT_THRESHOLDS = {
7
11
  titleMinChars: 30,
8
12
  titleMaxChars: 60,
@@ -14,13 +18,16 @@ export const DEFAULT_THRESHOLDS = {
14
18
  deepPageDepth: 3,
15
19
  maxKeywordDensityPercent: 3,
16
20
  };
17
- export function emptyCounts() {
18
- return { error: 0, warning: 0, info: 0 };
19
- }
20
- export function countFindings(findings) {
21
+ export const emptyCounts = () => ({
22
+ error: 0,
23
+ warning: 0,
24
+ info: 0,
25
+ });
26
+ export const countFindings = (findings) => {
21
27
  const counts = emptyCounts();
22
- for (const f of findings)
23
- counts[f.severity]++;
28
+ for (const f of findings) {
29
+ counts[f.severity] += 1;
30
+ }
24
31
  return counts;
25
- }
32
+ };
26
33
  //# sourceMappingURL=types.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,CAAC,MAAM,aAAa,GAA6B,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AA0UzF,MAAM,CAAC,MAAM,kBAAkB,GAAe;IAC5C,aAAa,EAAE,EAAE;IACjB,aAAa,EAAE,EAAE;IACjB,mBAAmB,EAAE,EAAE;IACvB,mBAAmB,EAAE,GAAG;IACxB,UAAU,EAAE,EAAE;IACd,gBAAgB,EAAE,GAAG;IACrB,cAAc,EAAE,IAAI;IACpB,aAAa,EAAE,CAAC;IAChB,wBAAwB,EAAE,CAAC;CAC5B,CAAC;AAEF,MAAM,UAAU,WAAW;IACzB,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,QAAmB;IAC/C,MAAM,MAAM,GAAG,WAAW,EAAE,CAAC;IAC7B,KAAK,MAAM,CAAC,IAAI,QAAQ;QAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;IAC/C,OAAO,MAAM,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,CAAC,MAAM,aAAa,GAA6B;IACrD,KAAK,EAAE,CAAC;IACR,OAAO,EAAE,CAAC;IACV,IAAI,EAAE,CAAC;CACR,CAAC;AAugBF,MAAM,CAAC,MAAM,kBAAkB,GAAe;IAC5C,aAAa,EAAE,EAAE;IACjB,aAAa,EAAE,EAAE;IACjB,mBAAmB,EAAE,EAAE;IACvB,mBAAmB,EAAE,GAAG;IACxB,UAAU,EAAE,EAAE;IACd,gBAAgB,EAAE,GAAG;IACrB,cAAc,EAAE,IAAI;IACpB,aAAa,EAAE,CAAC;IAChB,wBAAwB,EAAE,CAAC;CAC5B,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG,GAAmB,EAAE,CAAC,CAAC;IAChD,KAAK,EAAE,CAAC;IACR,OAAO,EAAE,CAAC;IACV,IAAI,EAAE,CAAC;CACR,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,QAAmB,EAAkB,EAAE;IACnE,MAAM,MAAM,GAAG,WAAW,EAAE,CAAC;IAE7B,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC"}
@@ -1,20 +1,20 @@
1
- export declare function isHttpUrl(value: string): boolean;
1
+ export declare const isHttpUrl: (value: string) => boolean;
2
2
  /** Resolve `href` against `base`, returning null for non-http(s) or unparsable values. */
3
- export declare function resolveUrl(href: string, base: string): string | null;
3
+ export declare const resolveUrl: (href: string, base: string) => string | null;
4
4
  /**
5
5
  * Canonical form used for visited-sets and comparisons: lower-cased scheme/host,
6
6
  * default ports dropped, fragment removed, tracking parameters removed.
7
7
  * Path and remaining query are kept verbatim (trailing slashes matter to servers).
8
8
  */
9
- export declare function normalizeUrl(input: string): string | null;
10
- export declare function hostOf(url: string): string | null;
11
- export declare function stripWww(host: string): string;
9
+ export declare const normalizeUrl: (input: string) => string | null;
10
+ export declare const hostOf: (url: string) => string | null;
11
+ export declare const stripWww: (host: string) => string;
12
12
  /** Same site: identical host ignoring `www.`; optionally any subdomain of the start host. */
13
- export declare function isSameSite(candidate: string, startHost: string, includeSubdomains?: boolean): boolean;
13
+ export declare const isSameSite: (candidate: string, startHost: string, includeSubdomains?: boolean) => boolean;
14
14
  /** Heuristic: does the path look like it points at an HTML document (not an asset)? */
15
- export declare function looksLikeHtml(url: string): boolean;
16
- /** Compare two URLs by path only (used for static-directory audits where the host is synthetic). */
17
- export declare function samePath(a: string, b: string): boolean;
15
+ export declare const looksLikeHtml: (url: string) => boolean;
18
16
  /** `/dir/index.html` -> `/dir/`, `/page.html` -> `/page`, trailing slash dropped except root. */
19
- export declare function normalizePath(pathname: string): string;
20
- export declare function truncate(value: string, max?: number): string;
17
+ export declare const normalizePath: (pathname: string) => string;
18
+ /** Compare two URLs by path only (used for static-directory audits where the host is synthetic). */
19
+ export declare const samePath: (a: string, b: string) => boolean;
20
+ export declare const truncate: (value: string, max?: number) => string;
package/dist/core/url.js CHANGED
@@ -1,125 +1,169 @@
1
- import path from 'node:path';
1
+ import path from "node:path";
2
2
  const TRACKING_PARAMS = new Set([
3
- 'utm_source',
4
- 'utm_medium',
5
- 'utm_campaign',
6
- 'utm_term',
7
- 'utm_content',
8
- 'utm_id',
9
- 'fbclid',
10
- 'gclid',
11
- 'dclid',
12
- 'msclkid',
13
- 'mc_cid',
14
- 'mc_eid',
15
- '_hsenc',
16
- '_hsmi',
17
- 'ref',
3
+ "utm_source",
4
+ "utm_medium",
5
+ "utm_campaign",
6
+ "utm_term",
7
+ "utm_content",
8
+ "utm_id",
9
+ "fbclid",
10
+ "gclid",
11
+ "dclid",
12
+ "msclkid",
13
+ "mc_cid",
14
+ "mc_eid",
15
+ "_hsenc",
16
+ "_hsmi",
17
+ "ref",
18
18
  ]);
19
19
  const NON_HTML_EXTENSIONS = new Set([
20
- '.pdf', '.jpg', '.jpeg', '.png', '.gif', '.webp', '.avif', '.svg', '.ico', '.bmp', '.tiff',
21
- '.mp4', '.webm', '.mp3', '.wav', '.ogg', '.mov', '.avi',
22
- '.zip', '.gz', '.tar', '.rar', '.7z', '.dmg', '.exe', '.msi',
23
- '.css', '.js', '.mjs', '.map', '.json', '.xml', '.txt', '.rss', '.atom',
24
- '.woff', '.woff2', '.ttf', '.otf', '.eot',
25
- '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.csv',
20
+ ".pdf",
21
+ ".jpg",
22
+ ".jpeg",
23
+ ".png",
24
+ ".gif",
25
+ ".webp",
26
+ ".avif",
27
+ ".svg",
28
+ ".ico",
29
+ ".bmp",
30
+ ".tiff",
31
+ ".mp4",
32
+ ".webm",
33
+ ".mp3",
34
+ ".wav",
35
+ ".ogg",
36
+ ".mov",
37
+ ".avi",
38
+ ".zip",
39
+ ".gz",
40
+ ".tar",
41
+ ".rar",
42
+ ".7z",
43
+ ".dmg",
44
+ ".exe",
45
+ ".msi",
46
+ ".css",
47
+ ".js",
48
+ ".mjs",
49
+ ".map",
50
+ ".json",
51
+ ".xml",
52
+ ".txt",
53
+ ".rss",
54
+ ".atom",
55
+ ".woff",
56
+ ".woff2",
57
+ ".ttf",
58
+ ".otf",
59
+ ".eot",
60
+ ".doc",
61
+ ".docx",
62
+ ".xls",
63
+ ".xlsx",
64
+ ".ppt",
65
+ ".pptx",
66
+ ".csv",
26
67
  ]);
27
- export function isHttpUrl(value) {
28
- return /^https?:\/\//i.test(value.trim());
29
- }
68
+ export const isHttpUrl = (value) => /^https?:\/\//iu.test(value.trim());
30
69
  /** Resolve `href` against `base`, returning null for non-http(s) or unparsable values. */
31
- export function resolveUrl(href, base) {
70
+ export const resolveUrl = (href, base) => {
32
71
  const trimmed = href.trim();
33
- if (!trimmed)
72
+ if (!trimmed) {
34
73
  return null;
35
- if (/^(mailto|tel|sms|javascript|data|blob|file|ftp|whatsapp|skype):/i.test(trimmed))
74
+ }
75
+ if (/^(?:mailto|tel|sms|javascript|data|blob|file|ftp|whatsapp|skype):/iu.test(trimmed)) {
36
76
  return null;
77
+ }
37
78
  try {
38
79
  const url = new URL(trimmed, base);
39
- if (url.protocol !== 'http:' && url.protocol !== 'https:')
80
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
40
81
  return null;
82
+ }
41
83
  return url.href;
42
84
  }
43
85
  catch {
44
86
  return null;
45
87
  }
46
- }
88
+ };
47
89
  /**
48
90
  * Canonical form used for visited-sets and comparisons: lower-cased scheme/host,
49
91
  * default ports dropped, fragment removed, tracking parameters removed.
50
92
  * Path and remaining query are kept verbatim (trailing slashes matter to servers).
51
93
  */
52
- export function normalizeUrl(input) {
94
+ export const normalizeUrl = (input) => {
53
95
  try {
54
96
  const url = new URL(input);
55
- if (url.protocol !== 'http:' && url.protocol !== 'https:')
97
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
56
98
  return null;
57
- url.hash = '';
99
+ }
100
+ url.hash = "";
58
101
  url.hostname = url.hostname.toLowerCase();
59
- if ((url.protocol === 'http:' && url.port === '80') || (url.protocol === 'https:' && url.port === '443')) {
60
- url.port = '';
102
+ if ((url.protocol === "http:" && url.port === "80") ||
103
+ (url.protocol === "https:" && url.port === "443")) {
104
+ url.port = "";
61
105
  }
62
106
  const params = [...url.searchParams.keys()];
63
107
  for (const key of params) {
64
- if (TRACKING_PARAMS.has(key.toLowerCase()))
108
+ if (TRACKING_PARAMS.has(key.toLowerCase())) {
65
109
  url.searchParams.delete(key);
110
+ }
111
+ }
112
+ if ([...url.searchParams.keys()].length === 0) {
113
+ url.search = "";
66
114
  }
67
- if ([...url.searchParams.keys()].length === 0)
68
- url.search = '';
69
115
  return url.href;
70
116
  }
71
117
  catch {
72
118
  return null;
73
119
  }
74
- }
75
- export function hostOf(url) {
120
+ };
121
+ export const hostOf = (url) => {
76
122
  try {
77
123
  return new URL(url).hostname.toLowerCase();
78
124
  }
79
125
  catch {
80
126
  return null;
81
127
  }
82
- }
83
- export function stripWww(host) {
84
- return host.replace(/^www\./i, '');
85
- }
128
+ };
129
+ export const stripWww = (host) => host.replace(/^www\./iu, "");
86
130
  /** Same site: identical host ignoring `www.`; optionally any subdomain of the start host. */
87
- export function isSameSite(candidate, startHost, includeSubdomains = false) {
131
+ export const isSameSite = (candidate, startHost, includeSubdomains = false) => {
88
132
  const host = hostOf(candidate);
89
- if (!host)
133
+ if (!host) {
90
134
  return false;
135
+ }
91
136
  const a = stripWww(host);
92
137
  const b = stripWww(startHost.toLowerCase());
93
- if (a === b)
138
+ if (a === b) {
94
139
  return true;
140
+ }
95
141
  return includeSubdomains && a.endsWith(`.${b}`);
96
- }
142
+ };
97
143
  /** Heuristic: does the path look like it points at an HTML document (not an asset)? */
98
- export function looksLikeHtml(url) {
144
+ export const looksLikeHtml = (url) => {
99
145
  try {
100
146
  const { pathname } = new URL(url);
101
147
  const ext = path.posix.extname(pathname).toLowerCase();
102
- if (!ext)
148
+ if (!ext) {
103
149
  return true;
104
- if (ext === '.html' || ext === '.htm' || ext === '.php' || ext === '.asp' || ext === '.aspx' || ext === '.jsp')
150
+ }
151
+ if (ext === ".html" ||
152
+ ext === ".htm" ||
153
+ ext === ".php" ||
154
+ ext === ".asp" ||
155
+ ext === ".aspx" ||
156
+ ext === ".jsp") {
105
157
  return true;
158
+ }
106
159
  return !NON_HTML_EXTENSIONS.has(ext);
107
160
  }
108
161
  catch {
109
162
  return false;
110
163
  }
111
- }
112
- /** Compare two URLs by path only (used for static-directory audits where the host is synthetic). */
113
- export function samePath(a, b) {
114
- try {
115
- return normalizePath(new URL(a).pathname) === normalizePath(new URL(b).pathname);
116
- }
117
- catch {
118
- return false;
119
- }
120
- }
164
+ };
121
165
  /** `/dir/index.html` -> `/dir/`, `/page.html` -> `/page`, trailing slash dropped except root. */
122
- export function normalizePath(pathname) {
166
+ export const normalizePath = (pathname) => {
123
167
  let p = pathname;
124
168
  try {
125
169
  p = decodeURIComponent(p);
@@ -127,13 +171,21 @@ export function normalizePath(pathname) {
127
171
  catch {
128
172
  /* keep as-is */
129
173
  }
130
- p = p.replace(/\/index\.html?$/i, '/');
131
- p = p.replace(/\.html?$/i, '');
132
- if (p.length > 1 && p.endsWith('/'))
174
+ p = p.replace(/\/index\.html?$/iu, "/");
175
+ p = p.replace(/\.html?$/iu, "");
176
+ if (p.length > 1 && p.endsWith("/")) {
133
177
  p = p.slice(0, -1);
134
- return p === '' ? '/' : p;
135
- }
136
- export function truncate(value, max = 120) {
137
- return value.length > max ? `${value.slice(0, max - 1)}…` : value;
138
- }
178
+ }
179
+ return p === "" ? "/" : p;
180
+ };
181
+ /** Compare two URLs by path only (used for static-directory audits where the host is synthetic). */
182
+ export const samePath = (a, b) => {
183
+ try {
184
+ return (normalizePath(new URL(a).pathname) === normalizePath(new URL(b).pathname));
185
+ }
186
+ catch {
187
+ return false;
188
+ }
189
+ };
190
+ export const truncate = (value, max = 120) => value.length > max ? `${value.slice(0, max - 1)}…` : value;
139
191
  //# sourceMappingURL=url.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"url.js","sourceRoot":"","sources":["../../src/core/url.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"url.js","sourceRoot":"","sources":["../../src/core/url.ts"],"names":[],"mappings":"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"}