@rivi-software/agentic-seo-check 0.0.0-stage → 0.1.1

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 (133) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +210 -2
  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.d.ts +2 -0
  7. package/dist/cli.js +194 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/core/audit.d.ts +72 -0
  10. package/dist/core/audit.js +452 -0
  11. package/dist/core/audit.js.map +1 -0
  12. package/dist/core/concurrency.d.ts +4 -0
  13. package/dist/core/concurrency.js +39 -0
  14. package/dist/core/concurrency.js.map +1 -0
  15. package/dist/core/crawl.d.ts +40 -0
  16. package/dist/core/crawl.js +324 -0
  17. package/dist/core/crawl.js.map +1 -0
  18. package/dist/core/fetch.d.ts +27 -0
  19. package/dist/core/fetch.js +186 -0
  20. package/dist/core/fetch.js.map +1 -0
  21. package/dist/core/html/dom.d.ts +13 -0
  22. package/dist/core/html/dom.js +17 -0
  23. package/dist/core/html/dom.js.map +1 -0
  24. package/dist/core/html/extract.d.ts +10 -0
  25. package/dist/core/html/extract.js +207 -0
  26. package/dist/core/html/extract.js.map +1 -0
  27. package/dist/core/html/structured-data.d.ts +21 -0
  28. package/dist/core/html/structured-data.js +220 -0
  29. package/dist/core/html/structured-data.js.map +1 -0
  30. package/dist/core/html/text.d.ts +23 -0
  31. package/dist/core/html/text.js +152 -0
  32. package/dist/core/html/text.js.map +1 -0
  33. package/dist/core/lighthouse.d.ts +50 -0
  34. package/dist/core/lighthouse.js +216 -0
  35. package/dist/core/lighthouse.js.map +1 -0
  36. package/dist/core/performance-rules.d.ts +3 -0
  37. package/dist/core/performance-rules.js +125 -0
  38. package/dist/core/performance-rules.js.map +1 -0
  39. package/dist/core/render-rules.d.ts +8 -0
  40. package/dist/core/render-rules.js +144 -0
  41. package/dist/core/render-rules.js.map +1 -0
  42. package/dist/core/render.d.ts +22 -0
  43. package/dist/core/render.js +79 -0
  44. package/dist/core/render.js.map +1 -0
  45. package/dist/core/report/markdown.d.ts +2 -0
  46. package/dist/core/report/markdown.js +131 -0
  47. package/dist/core/report/markdown.js.map +1 -0
  48. package/dist/core/robots.d.ts +11 -0
  49. package/dist/core/robots.js +48 -0
  50. package/dist/core/robots.js.map +1 -0
  51. package/dist/core/rules/canonical.d.ts +2 -0
  52. package/dist/core/rules/canonical.js +141 -0
  53. package/dist/core/rules/canonical.js.map +1 -0
  54. package/dist/core/rules/content.d.ts +1 -0
  55. package/dist/core/rules/content.js +55 -0
  56. package/dist/core/rules/content.js.map +1 -0
  57. package/dist/core/rules/document.d.ts +1 -0
  58. package/dist/core/rules/document.js +83 -0
  59. package/dist/core/rules/document.js.map +1 -0
  60. package/dist/core/rules/headings.d.ts +1 -0
  61. package/dist/core/rules/headings.js +81 -0
  62. package/dist/core/rules/headings.js.map +1 -0
  63. package/dist/core/rules/hreflang.d.ts +1 -0
  64. package/dist/core/rules/hreflang.js +100 -0
  65. package/dist/core/rules/hreflang.js.map +1 -0
  66. package/dist/core/rules/images.d.ts +1 -0
  67. package/dist/core/rules/images.js +77 -0
  68. package/dist/core/rules/images.js.map +1 -0
  69. package/dist/core/rules/index.d.ts +14 -0
  70. package/dist/core/rules/index.js +77 -0
  71. package/dist/core/rules/index.js.map +1 -0
  72. package/dist/core/rules/indexing.d.ts +10 -0
  73. package/dist/core/rules/indexing.js +75 -0
  74. package/dist/core/rules/indexing.js.map +1 -0
  75. package/dist/core/rules/keyword.d.ts +1 -0
  76. package/dist/core/rules/keyword.js +111 -0
  77. package/dist/core/rules/keyword.js.map +1 -0
  78. package/dist/core/rules/links.d.ts +1 -0
  79. package/dist/core/rules/links.js +107 -0
  80. package/dist/core/rules/links.js.map +1 -0
  81. package/dist/core/rules/meta.d.ts +1 -0
  82. package/dist/core/rules/meta.js +81 -0
  83. package/dist/core/rules/meta.js.map +1 -0
  84. package/dist/core/rules/response.d.ts +1 -0
  85. package/dist/core/rules/response.js +93 -0
  86. package/dist/core/rules/response.js.map +1 -0
  87. package/dist/core/rules/rule.d.ts +42 -0
  88. package/dist/core/rules/rule.js +51 -0
  89. package/dist/core/rules/rule.js.map +1 -0
  90. package/dist/core/rules/social.d.ts +1 -0
  91. package/dist/core/rules/social.js +115 -0
  92. package/dist/core/rules/social.js.map +1 -0
  93. package/dist/core/rules/structured-data.d.ts +25 -0
  94. package/dist/core/rules/structured-data.js +381 -0
  95. package/dist/core/rules/structured-data.js.map +1 -0
  96. package/dist/core/rules/title.d.ts +1 -0
  97. package/dist/core/rules/title.js +74 -0
  98. package/dist/core/rules/title.js.map +1 -0
  99. package/dist/core/score.d.ts +3 -0
  100. package/dist/core/score.js +17 -0
  101. package/dist/core/score.js.map +1 -0
  102. package/dist/core/site-rules.d.ts +64 -0
  103. package/dist/core/site-rules.js +462 -0
  104. package/dist/core/site-rules.js.map +1 -0
  105. package/dist/core/sitemap.d.ts +13 -0
  106. package/dist/core/sitemap.js +140 -0
  107. package/dist/core/sitemap.js.map +1 -0
  108. package/dist/core/static.d.ts +9 -0
  109. package/dist/core/static.js +281 -0
  110. package/dist/core/static.js.map +1 -0
  111. package/dist/core/summary.d.ts +2 -0
  112. package/dist/core/summary.js +33 -0
  113. package/dist/core/summary.js.map +1 -0
  114. package/dist/core/types.d.ts +296 -0
  115. package/dist/core/types.js +33 -0
  116. package/dist/core/types.js.map +1 -0
  117. package/dist/core/url.d.ts +20 -0
  118. package/dist/core/url.js +191 -0
  119. package/dist/core/url.js.map +1 -0
  120. package/dist/index.d.ts +11 -0
  121. package/dist/index.js +10 -0
  122. package/dist/index.js.map +1 -0
  123. package/dist/mcp-bin.d.ts +2 -0
  124. package/dist/mcp-bin.js +10 -0
  125. package/dist/mcp-bin.js.map +1 -0
  126. package/dist/mcp.d.ts +5 -0
  127. package/dist/mcp.js +380 -0
  128. package/dist/mcp.js.map +1 -0
  129. package/dist/version.d.ts +1 -0
  130. package/dist/version.js +6 -0
  131. package/dist/version.js.map +1 -0
  132. package/package.json +107 -3
  133. package/skills/seo-audit/SKILL.md +53 -0
@@ -0,0 +1,79 @@
1
+ import { DEFAULT_USER_AGENT } from "./fetch.js";
2
+ export class RenderUnavailableError extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = "RenderUnavailableError";
6
+ }
7
+ }
8
+ export const RENDER_INSTALL_HINT = "Install Playwright to enable rendering: `pnpm add -D playwright && pnpm exec playwright install chromium` (or npm/npx equivalents).";
9
+ const launchSystemBrowser = async (playwright) => {
10
+ for (const channel of ["chrome", "msedge"]) {
11
+ try {
12
+ // oxlint-disable-next-line eslint/no-await-in-loop -- one browser at a time, in order of preference
13
+ return await playwright.chromium.launch({ headless: true, channel });
14
+ }
15
+ catch {
16
+ /* try next */
17
+ }
18
+ }
19
+ return null;
20
+ };
21
+ const firstLine = (message) => message.split("\n")[0] ?? message;
22
+ /** Load the page in headless Chromium and return the rendered DOM. */
23
+ export const renderPage = async (url, options = {}) => {
24
+ let playwright;
25
+ try {
26
+ playwright = await import("playwright");
27
+ }
28
+ catch {
29
+ throw new RenderUnavailableError(`Playwright is not installed. ${RENDER_INSTALL_HINT}`);
30
+ }
31
+ let browser;
32
+ try {
33
+ browser = await playwright.chromium.launch({ headless: true });
34
+ }
35
+ catch (error) {
36
+ // No downloaded Playwright browser: fall back to an installed Google Chrome / Edge.
37
+ const fallback = await launchSystemBrowser(playwright);
38
+ if (!fallback) {
39
+ throw new RenderUnavailableError(`Could not launch Chromium: ${firstLine(error instanceof Error ? error.message : String(error))}. ${RENDER_INSTALL_HINT}`);
40
+ }
41
+ browser = fallback;
42
+ }
43
+ const timeout = options.timeoutMs ?? 30_000;
44
+ const consoleErrors = [];
45
+ try {
46
+ const context = await browser.newContext({
47
+ userAgent: options.userAgent ?? DEFAULT_USER_AGENT,
48
+ viewport: options.viewport ?? { width: 1366, height: 900 },
49
+ extraHTTPHeaders: options.headers,
50
+ });
51
+ const page = await context.newPage();
52
+ page.on("console", (msg) => {
53
+ if (msg.type() === "error") {
54
+ consoleErrors.push(msg.text().slice(0, 300));
55
+ }
56
+ });
57
+ page.on("pageerror", (err) => consoleErrors.push(err.message.slice(0, 300)));
58
+ try {
59
+ await page.goto(url, {
60
+ waitUntil: options.waitUntil ?? "networkidle",
61
+ timeout,
62
+ });
63
+ }
64
+ catch (error) {
65
+ // networkidle can time out on pages with long-polling; fall back to whatever has loaded.
66
+ if (!(error instanceof Error) || !/Timeout/iu.test(error.message)) {
67
+ throw error;
68
+ }
69
+ }
70
+ await page.waitForTimeout(250);
71
+ const html = await page.content();
72
+ return { html, finalUrl: page.url(), consoleErrors };
73
+ }
74
+ finally {
75
+ // A browser that fails to close must not replace the page's result or its error.
76
+ await browser.close().catch(() => null);
77
+ }
78
+ };
79
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/core/render.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAC/C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACvC,CAAC;CACF;AAiBD,MAAM,CAAC,MAAM,mBAAmB,GAC9B,qIAAqI,CAAC;AAExI,MAAM,mBAAmB,GAAG,KAAK,EAC/B,UAA6B,EACJ,EAAE;IAC3B,KAAK,MAAM,OAAO,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAU,EAAE,CAAC;QACpD,IAAI,CAAC;YACH,oGAAoG;YACpG,OAAO,MAAM,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QACvE,CAAC;QAAC,MAAM,CAAC;YACP,cAAc;QAChB,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF,MAAM,SAAS,GAAG,CAAC,OAAe,EAAU,EAAE,CAC5C,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC;AAEpC,sEAAsE;AACtE,MAAM,CAAC,MAAM,UAAU,GAAG,KAAK,EAC7B,GAAW,EACX,OAAO,GAAkB,EAAE,EACJ,EAAE;IACzB,IAAI,UAA6B,CAAC;IAElC,IAAI,CAAC;QACH,UAAU,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,sBAAsB,CAC9B,gCAAgC,mBAAmB,EAAE,CACtD,CAAC;IACJ,CAAC;IAED,IAAI,OAAgB,CAAC;IAErB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IACjE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,oFAAoF;QACpF,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,UAAU,CAAC,CAAC;QAEvD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,sBAAsB,CAC9B,8BAA8B,SAAS,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,mBAAmB,EAAE,CAC1H,CAAC;QACJ,CAAC;QAED,OAAO,GAAG,QAAQ,CAAC;IACrB,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,SAAS,IAAI,MAAM,CAAC;IAC5C,MAAM,aAAa,GAAa,EAAE,CAAC;IAEnC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC;YACvC,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,kBAAkB;YAClD,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;YAC1D,gBAAgB,EAAE,OAAO,CAAC,OAAO;SAClC,CAAC,CAAC;QAEH,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;QACrC,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE;YACzB,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,OAAO,EAAE,CAAC;gBAC3B,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;YAC/C,CAAC;QACH,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,GAAG,EAAE,EAAE,CAC3B,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAC9C,CAAC;QAEF,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE;gBACnB,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,aAAa;gBAC7C,OAAO;aACR,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,yFAAyF;YACzF,IAAI,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;gBAClE,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;QAED,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;QAC/B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QAElC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,CAAC;IACvD,CAAC;YAAS,CAAC;QACT,iFAAiF;QACjF,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAC1C,CAAC;AACH,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { Audit } from "../types.js";
2
+ export declare const renderMarkdown: (audit: Audit) => string;
@@ -0,0 +1,131 @@
1
+ import { MAKER, MAKER_URL, REPOSITORY_URL } from "../../brand.js";
2
+ import { VERSION } from "../../version.js";
3
+ const cell = (value) => {
4
+ if (!value) {
5
+ return "—";
6
+ }
7
+ return value.replaceAll("|", "\\|").replaceAll("\n", " ").slice(0, 120);
8
+ };
9
+ const ms = (value) => value === null ? "—" : `${Math.round(value)} ms`;
10
+ const countsLine = (c) => `${c.error} error(s), ${c.warning} warning(s), ${c.info} info`;
11
+ const findingLines = (f) => {
12
+ const out = [
13
+ `- **\`${f.rule}\`** ${f.message}`,
14
+ ` - Fix: ${f.recommendation}`,
15
+ ];
16
+ if (f.selector) {
17
+ out.push(` - Where: \`${f.selector}\``);
18
+ }
19
+ if (f.evidence && Object.keys(f.evidence).length) {
20
+ out.push(` - Evidence: \`${JSON.stringify(f.evidence).slice(0, 400)}\``);
21
+ }
22
+ return out;
23
+ };
24
+ const SEVERITY_HEADINGS = {
25
+ error: "Errors",
26
+ warning: "Warnings",
27
+ info: "Info",
28
+ };
29
+ const findingsSection = (findings, heading) => {
30
+ const lines = [`${heading}# Findings`, ""];
31
+ if (!findings.length) {
32
+ lines.push("No issues found.");
33
+ return lines;
34
+ }
35
+ for (const severity of ["error", "warning", "info"]) {
36
+ const group = findings.filter((f) => f.severity === severity);
37
+ if (!group.length) {
38
+ continue;
39
+ }
40
+ lines.push(`${heading}## ${SEVERITY_HEADINGS[severity]} (${group.length})`, "");
41
+ for (const f of group) {
42
+ lines.push(...findingLines(f));
43
+ }
44
+ lines.push("");
45
+ }
46
+ return lines;
47
+ };
48
+ const pageMarkdown = (audit, heading = "#") => {
49
+ const lines = [
50
+ `${heading} SEO audit: ${audit.url}`,
51
+ "",
52
+ `Score **${audit.score}/100** · ${countsLine(audit.counts)}${audit.response?.status ? ` · HTTP ${audit.response.status}` : ""}${audit.response?.timeMs ? ` · ${audit.response.timeMs} ms` : ""}`,
53
+ "",
54
+ ];
55
+ const s = audit.summary;
56
+ lines.push(`${heading}# Summary`, "", "| Field | Value |", "| --- | --- |", `| Title | ${cell(s.title)} (${s.titleLength} chars) |`, `| Meta description | ${cell(s.metaDescription)} (${s.metaDescriptionLength} chars) |`, `| Canonical | ${cell(s.canonical)} |`, `| Indexable | ${s.indexable ? "yes" : "no"}${s.robots.length ? ` (${s.robots.join(", ")})` : ""} |`, `| Language | ${cell(s.lang)} |`, `| H1 | ${s.h1.length ? s.h1.map(cell).join(" / ") : "—"} |`, `| Words | ${s.wordCount} |`, `| Images | ${s.images.total} (${s.images.missingAlt} without alt) |`, `| Links | ${s.links.internal} internal, ${s.links.external} external |`, `| Structured data | ${s.structuredDataTypes.length ? s.structuredDataTypes.join(", ") : "—"} |`, `| Open Graph | ${Object.keys(s.openGraph).length
57
+ ? Object.keys(s.openGraph)
58
+ .map((k) => `og:${k}`)
59
+ .join(", ")
60
+ : "—"} |`, "");
61
+ if (audit.performance) {
62
+ const m = audit.performance.metrics;
63
+ lines.push(`${heading}# Lighthouse (${audit.performance.formFactor})`, "", "| Metric | Value |", "| --- | --- |", `| Performance score | ${m.performanceScore ?? "—"} |`, `| SEO score | ${m.seoScore ?? "—"} |`, `| LCP | ${ms(m.lcpMs)} |`, `| CLS | ${m.cls === null ? "—" : m.cls.toFixed(3)} |`, `| TBT | ${ms(m.tbtMs)} |`, `| FCP | ${ms(m.fcpMs)} |`, `| Speed Index | ${ms(m.speedIndexMs)} |`, `| TTFB | ${ms(m.ttfbMs)} |`, "");
64
+ }
65
+ if (audit.rendering) {
66
+ const r = audit.rendering;
67
+ lines.push(`${heading}# Rendered vs server HTML`, "", "| Field | Server HTML | Rendered DOM |", "| --- | --- | --- |", `| Title | ${cell(r.raw.title)} | ${cell(r.rendered.title)} |`, `| Meta description | ${cell(r.raw.metaDescription)} | ${cell(r.rendered.metaDescription)} |`, `| Canonical | ${cell(r.raw.canonical)} | ${cell(r.rendered.canonical)} |`, `| Robots | ${r.raw.robots.join(", ") || "—"} | ${r.rendered.robots.join(", ") || "—"} |`, `| H1 count | ${r.raw.h1Count} | ${r.rendered.h1Count} |`, `| Words | ${r.raw.wordCount} | ${r.rendered.wordCount} |`, `| Internal links | ${r.raw.internalLinks} | ${r.rendered.internalLinks} |`, `| Structured data items | ${r.raw.structuredDataCount} | ${r.rendered.structuredDataCount} |`, "");
68
+ }
69
+ lines.push(...findingsSection(audit.findings, heading));
70
+ return lines.join("\n");
71
+ };
72
+ const robotsTxtResult = (audit) => {
73
+ if (!audit.robotsTxt) {
74
+ return "not checked";
75
+ }
76
+ if (audit.robotsTxt.found) {
77
+ return `found (${audit.robotsTxt.sitemaps.length} sitemap(s) declared)`;
78
+ }
79
+ return `not found (HTTP ${audit.robotsTxt.status ?? "n/a"})`;
80
+ };
81
+ const siteMarkdown = (audit) => {
82
+ const lines = [
83
+ `# SEO site audit: ${audit.target}`,
84
+ "",
85
+ `Score **${audit.score}/100** · ${countsLine(audit.counts)} · ${audit.crawl.pagesCrawled} pages audited, ${audit.crawl.pagesDiscovered} discovered${audit.crawl.hitPageLimit ? ` (page limit ${audit.crawl.maxPages} reached)` : ""} · ${(audit.crawl.durationMs / 1000).toFixed(1)} s`,
86
+ "",
87
+ "| Check | Result |",
88
+ "| --- | --- |",
89
+ `| robots.txt | ${robotsTxtResult(audit)} |`,
90
+ `| Sitemaps | ${audit.sitemaps.sources.length ? `${audit.sitemaps.sources.length} file(s), ${audit.sitemaps.urlCount} URLs` : "none found"}${audit.sitemaps.errors.length ? `, ${audit.sitemaps.errors.length} error(s)` : ""} |`,
91
+ "",
92
+ ];
93
+ if (audit.siteFindings.length) {
94
+ lines.push("## Site-wide findings", "");
95
+ for (const f of audit.siteFindings) {
96
+ lines.push(...findingLines(f));
97
+ }
98
+ lines.push("");
99
+ }
100
+ if (audit.issues.length) {
101
+ lines.push("## Page issues by rule", "", "| Severity | Rule | Pages | Example |", "| --- | --- | --- | --- |");
102
+ for (const i of audit.issues) {
103
+ lines.push(`| ${i.severity} | \`${i.rule}\` | ${i.pages} | ${i.examples[0] ?? ""} |`);
104
+ }
105
+ lines.push("");
106
+ }
107
+ lines.push("## Pages", "", "| Score | E/W/I | URL | Title |", "| --- | --- | --- | --- |");
108
+ for (const p of audit.pages) {
109
+ lines.push(`| ${p.score} | ${p.counts.error}/${p.counts.warning}/${p.counts.info} | ${p.url} | ${cell(p.summary.title)} |`);
110
+ }
111
+ lines.push("");
112
+ const detailed = audit.pages.filter((p) => p.findings.length > 0);
113
+ if (detailed.length) {
114
+ lines.push("## Page details", "");
115
+ for (const p of detailed) {
116
+ lines.push(`### ${p.url}`, "", `Score ${p.score}/100 · ${countsLine(p.counts)}`, "");
117
+ for (const f of p.findings) {
118
+ lines.push(...findingLines(f));
119
+ }
120
+ lines.push("");
121
+ }
122
+ }
123
+ return lines.join("\n");
124
+ };
125
+ /** One line under every report: which tool and version wrote it, and who makes it. */
126
+ const footer = `_Audited with [agentic-seo-check](${REPOSITORY_URL}) ${VERSION}, made by [${MAKER}](${MAKER_URL})._`;
127
+ export const renderMarkdown = (audit) => {
128
+ const body = audit.kind === "page" ? pageMarkdown(audit) : siteMarkdown(audit);
129
+ return `${body.trimEnd()}\n\n---\n\n${footer}\n`;
130
+ };
131
+ //# sourceMappingURL=markdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../../src/core/report/markdown.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { FetchOptions } from "./fetch.js";
2
+ import type { RobotsTxtInfo } from "./types.js";
3
+ export interface RobotsResult {
4
+ info: RobotsTxtInfo;
5
+ /** Returns true when crawling the URL is allowed (unknown/missing robots.txt allows everything). */
6
+ allows: (url: string) => boolean;
7
+ }
8
+ export declare const buildRobots: (url: string, status: number | null, error: string | undefined, text: string | null, userAgent: string) => RobotsResult;
9
+ export declare const loadRobotsTxt: (origin: string, userAgent: string, options?: FetchOptions) => Promise<RobotsResult>;
10
+ /** Short bot token used for robots.txt matching, derived from the full UA string. */
11
+ export declare const robotsUserAgentToken: (userAgent: string) => string;
@@ -0,0 +1,48 @@
1
+ import { createRequire } from "node:module";
2
+ import { fetchPage } from "./fetch.js";
3
+ // robots-parser is CommonJS; require() avoids ESM default-export interop mismatches.
4
+ // SAFETY: require() returns the package's module.exports, which is the parser function that its
5
+ // index.d.ts declares as the default export.
6
+ const robotsParser = createRequire(import.meta.url)("robots-parser");
7
+ export const buildRobots = (url, status, error, text, userAgent) => {
8
+ const info = {
9
+ url,
10
+ status,
11
+ found: text !== null,
12
+ sitemaps: [],
13
+ text: text ? text.slice(0, 20_000) : null,
14
+ };
15
+ if (error) {
16
+ info.error = error;
17
+ }
18
+ if (text === null) {
19
+ return { info, allows: () => true };
20
+ }
21
+ const robot = robotsParser(url, text);
22
+ info.sitemaps = robot.getSitemaps();
23
+ return {
24
+ info,
25
+ allows: (target) => {
26
+ const allowed = robot.isAllowed(target, userAgent);
27
+ return allowed === undefined ? true : allowed;
28
+ },
29
+ };
30
+ };
31
+ export const loadRobotsTxt = async (origin, userAgent, options = {}) => {
32
+ const url = new URL("/robots.txt", origin).href;
33
+ const res = await fetchPage(url, {
34
+ ...options,
35
+ timeoutMs: Math.min(options.timeoutMs ?? 10_000, 10_000),
36
+ });
37
+ const found = res.status === 200 &&
38
+ res.body !== null &&
39
+ !res.error &&
40
+ !/<html/iu.test(res.body.slice(0, 500));
41
+ return buildRobots(url, res.status, res.error, found ? res.body : null, userAgent);
42
+ };
43
+ /** Short bot token used for robots.txt matching, derived from the full UA string. */
44
+ export const robotsUserAgentToken = (userAgent) => {
45
+ const match = /agentic-seo-check|Googlebot|bingbot/iu.exec(userAgent);
46
+ return match ? match[0] : userAgent;
47
+ };
48
+ //# sourceMappingURL=robots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"robots.js","sourceRoot":"","sources":["../../src/core/robots.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAI5C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAIvC,qFAAqF;AACrF,gGAAgG;AAChG,6CAA6C;AAC7C,MAAM,YAAY,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CACjD,eAAe,CAC0B,CAAC;AAQ5C,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,GAAW,EACX,MAAqB,EACrB,KAAyB,EACzB,IAAmB,EACnB,SAAiB,EACH,EAAE;IAChB,MAAM,IAAI,GAAkB;QAC1B,GAAG;QACH,MAAM;QACN,KAAK,EAAE,IAAI,KAAK,IAAI;QACpB,QAAQ,EAAE,EAAE;QACZ,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI;KAC1C,CAAC;IAEF,IAAI,KAAK,EAAE,CAAC;QACV,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAED,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC;IACtC,CAAC;IAED,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACtC,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;IAEpC,OAAO;QACL,IAAI;QACJ,MAAM,EAAE,CAAC,MAAc,EAAE,EAAE;YACzB,MAAM,OAAO,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;YAEnD,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;QAChD,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,EAChC,MAAc,EACd,SAAiB,EACjB,OAAO,GAAiB,EAAE,EACH,EAAE;IACzB,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC;IAEhD,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE;QAC/B,GAAG,OAAO;QACV,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,IAAI,MAAM,EAAE,MAAM,CAAC;KACzD,CAAC,CAAC;IAEH,MAAM,KAAK,GACT,GAAG,CAAC,MAAM,KAAK,GAAG;QAClB,GAAG,CAAC,IAAI,KAAK,IAAI;QACjB,CAAC,GAAG,CAAC,KAAK;QACV,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAE1C,OAAO,WAAW,CAChB,GAAG,EACH,GAAG,CAAC,MAAM,EACV,GAAG,CAAC,KAAK,EACT,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EACvB,SAAS,CACV,CAAC;AACJ,CAAC,CAAC;AAEF,qFAAqF;AACrF,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,SAAiB,EAAU,EAAE;IAChE,MAAM,KAAK,GAAG,uCAAuC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAEtE,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACtC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const isLocalHost: (url: string) => boolean;
2
+ export declare const canonicalRules: import("./rule.js").Rule[];
@@ -0,0 +1,141 @@
1
+ import { hostOf, normalizeUrl, samePath } from "../url.js";
2
+ import { cap, defineRule, GOOGLE_DOCS } from "./rule.js";
3
+ const SELECTOR = 'head > link[rel="canonical"]';
4
+ const LOCAL_HOSTS = new Set([
5
+ "localhost",
6
+ "127.0.0.1",
7
+ "0.0.0.0",
8
+ "::1",
9
+ "[::1]",
10
+ ]);
11
+ export const isLocalHost = (url) => {
12
+ const host = hostOf(url);
13
+ if (!host) {
14
+ return false;
15
+ }
16
+ return (LOCAL_HOSTS.has(host) ||
17
+ host.endsWith(".local") ||
18
+ host.endsWith(".localhost") ||
19
+ host.endsWith(".test"));
20
+ };
21
+ const stripTrailingSlash = (url) => url.endsWith("/") && new URL(url).pathname !== "/" ? url.slice(0, -1) : url;
22
+ export const canonicalRules = [
23
+ defineRule({
24
+ id: "canonical.missing",
25
+ category: "canonical",
26
+ severity: "warning",
27
+ description: 'No <link rel="canonical"> is present.',
28
+ recommendation: "Add a self-referencing canonical with the absolute preferred URL (scheme, host, trailing slash) so duplicate URLs (query strings, www/non-www) consolidate to it.",
29
+ docs: GOOGLE_DOCS.canonical,
30
+ }, ({ page }, make) => page.canonicals.length === 0
31
+ ? make("Page has no canonical link.", { selector: SELECTOR })
32
+ : null),
33
+ defineRule({
34
+ id: "canonical.multiple",
35
+ category: "canonical",
36
+ severity: "error",
37
+ description: "More than one canonical link is present. When they disagree, search engines ignore all of them.",
38
+ recommendation: "Emit exactly one canonical link per page. Check for a framework default plus a hand-written one.",
39
+ docs: GOOGLE_DOCS.canonical,
40
+ }, ({ page }, make) => {
41
+ if (page.canonicals.length <= 1) {
42
+ return null;
43
+ }
44
+ const distinct = new Set(page.canonicals.map((c) => c.trim()));
45
+ return make(`Page has ${page.canonicals.length} canonical links${distinct.size > 1 ? " with different targets" : " (identical)"}.`, {
46
+ selector: SELECTOR,
47
+ severity: distinct.size > 1 ? "error" : "warning",
48
+ evidence: { canonicals: cap(page.canonicals) },
49
+ });
50
+ }),
51
+ defineRule({
52
+ id: "canonical.invalid",
53
+ category: "canonical",
54
+ severity: "error",
55
+ description: "The canonical href is empty or cannot be resolved to an http(s) URL.",
56
+ recommendation: "Set the canonical href to a valid absolute URL such as https://www.example.com/path/.",
57
+ docs: GOOGLE_DOCS.canonical,
58
+ }, ({ page }, make) => {
59
+ const [first] = page.canonicals;
60
+ if (first === undefined) {
61
+ return null;
62
+ }
63
+ return page.canonical
64
+ ? null
65
+ : make(`Canonical href "${first}" is not a valid URL.`, {
66
+ selector: SELECTOR,
67
+ evidence: { href: first },
68
+ });
69
+ }),
70
+ defineRule({
71
+ id: "canonical.relative",
72
+ category: "canonical",
73
+ severity: "warning",
74
+ description: "The canonical href is a relative URL.",
75
+ recommendation: "Use an absolute URL in the canonical link. Relative canonicals are error-prone and some crawlers resolve them unexpectedly.",
76
+ docs: GOOGLE_DOCS.canonical,
77
+ }, ({ page }, make) => {
78
+ const [first] = page.canonicals;
79
+ if (!first || !page.canonical) {
80
+ return null;
81
+ }
82
+ return /^https?:\/\//iu.test(first)
83
+ ? null
84
+ : make(`Canonical href "${first}" is relative.`, {
85
+ selector: SELECTOR,
86
+ evidence: { href: first, resolved: page.canonical },
87
+ });
88
+ }),
89
+ defineRule({
90
+ id: "canonical.protocol-mismatch",
91
+ category: "canonical",
92
+ severity: "warning",
93
+ description: "The page is served over https but the canonical points to http.",
94
+ recommendation: "Point the canonical at the https URL.",
95
+ docs: GOOGLE_DOCS.canonical,
96
+ }, ({ page, source }, make) => {
97
+ if (source !== "url" || !page.canonical) {
98
+ return null;
99
+ }
100
+ return page.url.startsWith("https://") &&
101
+ page.canonical.startsWith("http://")
102
+ ? make("Canonical uses http:// while the page is served over https://.", {
103
+ selector: SELECTOR,
104
+ evidence: { canonical: page.canonical, page: page.url },
105
+ })
106
+ : null;
107
+ }),
108
+ defineRule({
109
+ id: "canonical.other-page",
110
+ category: "canonical",
111
+ severity: "info",
112
+ description: "The canonical points to a different URL, so search engines will treat this page as a duplicate of that URL.",
113
+ recommendation: "If this page should rank on its own, make the canonical self-referencing. If it is intentionally a duplicate (print view, filtered list, tracking URL), this is correct.",
114
+ docs: GOOGLE_DOCS.canonical,
115
+ }, ({ page, source }, make) => {
116
+ if (!page.canonical) {
117
+ return null;
118
+ }
119
+ const pageUrl = normalizeUrl(page.url);
120
+ const canon = normalizeUrl(page.canonical);
121
+ if (!pageUrl || !canon) {
122
+ return null;
123
+ }
124
+ // For files and local dev servers the host is synthetic; compare paths only.
125
+ const pathOnly = source !== "url" || isLocalHost(page.url);
126
+ const same = pathOnly
127
+ ? samePath(pageUrl, canon)
128
+ : stripTrailingSlash(pageUrl) === stripTrailingSlash(canon);
129
+ return same
130
+ ? null
131
+ : make(`Canonical points to ${page.canonical}, not to this page.`, {
132
+ selector: SELECTOR,
133
+ evidence: {
134
+ canonical: page.canonical,
135
+ page: page.url,
136
+ comparedBy: pathOnly ? "path" : "url",
137
+ },
138
+ });
139
+ }),
140
+ ];
141
+ //# sourceMappingURL=canonical.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonical.js","sourceRoot":"","sources":["../../../src/core/rules/canonical.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare const contentRules: import("./rule.js").Rule[];
@@ -0,0 +1,55 @@
1
+ import { fleschReadingEase, hasPlaceholderText } from "../html/text.js";
2
+ import { defineRule, GOOGLE_DOCS } from "./rule.js";
3
+ export const contentRules = [
4
+ defineRule({
5
+ id: "content.empty",
6
+ category: "content",
7
+ severity: "warning",
8
+ description: "The HTML contains no visible text.",
9
+ recommendation: "If the page is rendered client-side, use server-side rendering or static generation so the HTML carries the content. Re-run with render enabled to compare the rendered DOM.",
10
+ docs: GOOGLE_DOCS.javascript,
11
+ }, ({ page }, make) => page.wordCount === 0
12
+ ? make("Page body has no visible text in the HTML response.")
13
+ : null),
14
+ defineRule({
15
+ id: "content.thin",
16
+ category: "content",
17
+ severity: "warning",
18
+ description: "The page has very little text (default: fewer than 300 words).",
19
+ recommendation: "Add substantive, useful content, or set noindex if the page is a utility page (login, cart, thank-you). Thin pages rarely rank and can drag down site quality signals.",
20
+ }, ({ page, thresholds }, make) => page.wordCount > 0 && page.wordCount < thresholds.thinContentWords
21
+ ? make(`Page has only ${page.wordCount} words of visible text (threshold ${thresholds.thinContentWords}).`, {
22
+ evidence: {
23
+ wordCount: page.wordCount,
24
+ threshold: thresholds.thinContentWords,
25
+ },
26
+ })
27
+ : null),
28
+ defineRule({
29
+ id: "content.placeholder-text",
30
+ category: "content",
31
+ severity: "error",
32
+ description: 'Placeholder text ("lorem ipsum") is present in the page.',
33
+ recommendation: "Replace the placeholder copy with real content before publishing.",
34
+ }, ({ page }, make) => hasPlaceholderText(page.text)
35
+ ? make('Page contains "lorem ipsum" placeholder text.')
36
+ : null),
37
+ defineRule({
38
+ id: "content.readability-low",
39
+ category: "content",
40
+ severity: "info",
41
+ description: "Flesch Reading Ease below 30 (very difficult). Only evaluated for English or unspecified language, 100+ words.",
42
+ recommendation: "Shorten sentences and prefer common words. Aim for a Flesch score of 50-70 for general audiences.",
43
+ }, ({ page }, make) => {
44
+ if (page.lang && !page.lang.toLowerCase().startsWith("en")) {
45
+ return null;
46
+ }
47
+ const score = fleschReadingEase(page.text);
48
+ return score !== null && score < 30
49
+ ? make(`Flesch Reading Ease is ${score} (very difficult).`, {
50
+ evidence: { fleschReadingEase: score, wordCount: page.wordCount },
51
+ })
52
+ : null;
53
+ }),
54
+ ];
55
+ //# sourceMappingURL=content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content.js","sourceRoot":"","sources":["../../../src/core/rules/content.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAEpD,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,UAAU,CACR;QACE,EAAE,EAAE,eAAe;QACnB,QAAQ,EAAE,SAAS;QACnB,QAAQ,EAAE,SAAS;QACnB,WAAW,EAAE,oCAAoC;QACjD,cAAc,EACZ,8KAA8K;QAChL,IAAI,EAAE,WAAW,CAAC,UAAU;KAC7B,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,CACjB,IAAI,CAAC,SAAS,KAAK,CAAC;QAClB,CAAC,CAAC,IAAI,CAAC,qDAAqD,CAAC;QAC7D,CAAC,CAAC,IAAI,CACX;IACD,UAAU,CACR;QACE,EAAE,EAAE,cAAc;QAClB,QAAQ,EAAE,SAAS;QACnB,QAAQ,EAAE,SAAS;QACnB,WAAW,EACT,gEAAgE;QAClE,cAAc,EACZ,wKAAwK;KAC3K,EACD,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,EAAE,CAC7B,IAAI,CAAC,SAAS,GAAG,CAAC,IAAI,IAAI,CAAC,SAAS,GAAG,UAAU,CAAC,gBAAgB;QAChE,CAAC,CAAC,IAAI,CACF,iBAAiB,IAAI,CAAC,SAAS,qCAAqC,UAAU,CAAC,gBAAgB,IAAI,EACnG;YACE,QAAQ,EAAE;gBACR,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,SAAS,EAAE,UAAU,CAAC,gBAAgB;aACvC;SACF,CACF;QACH,CAAC,CAAC,IAAI,CACX;IACD,UAAU,CACR;QACE,EAAE,EAAE,0BAA0B;QAC9B,QAAQ,EAAE,SAAS;QACnB,QAAQ,EAAE,OAAO;QACjB,WAAW,EAAE,0DAA0D;QACvE,cAAc,EACZ,mEAAmE;KACtE,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,CACjB,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC;QAC3B,CAAC,CAAC,IAAI,CAAC,+CAA+C,CAAC;QACvD,CAAC,CAAC,IAAI,CACX;IACD,UAAU,CACR;QACE,EAAE,EAAE,yBAAyB;QAC7B,QAAQ,EAAE,SAAS;QACnB,QAAQ,EAAE,MAAM;QAChB,WAAW,EACT,gHAAgH;QAClH,cAAc,EACZ,mGAAmG;KACtG,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE;QACjB,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3D,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,KAAK,GAAG,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAE3C,OAAO,KAAK,KAAK,IAAI,IAAI,KAAK,GAAG,EAAE;YACjC,CAAC,CAAC,IAAI,CAAC,0BAA0B,KAAK,oBAAoB,EAAE;gBACxD,QAAQ,EAAE,EAAE,iBAAiB,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE;aAClE,CAAC;YACJ,CAAC,CAAC,IAAI,CAAC;IACX,CAAC,CACF;CACF,CAAC"}
@@ -0,0 +1 @@
1
+ export declare const documentRules: import("./rule.js").Rule[];
@@ -0,0 +1,83 @@
1
+ import { defineRule, GOOGLE_DOCS } from "./rule.js";
2
+ const BCP47 = /^[a-zA-Z]{2,3}(?:-[a-zA-Z0-9]{2,8})*$/u;
3
+ export const documentRules = [
4
+ defineRule({
5
+ id: "html.lang-missing",
6
+ category: "document",
7
+ severity: "warning",
8
+ description: "The <html> element has no lang attribute.",
9
+ recommendation: 'Set <html lang="xx"> (for example "en", "nl", "en-GB") so search engines and screen readers know the content language.',
10
+ }, ({ page }, make) => page.lang
11
+ ? null
12
+ : make("<html> has no lang attribute.", { selector: "html" })),
13
+ defineRule({
14
+ id: "html.lang-invalid",
15
+ category: "document",
16
+ severity: "warning",
17
+ description: "The <html lang> value is not a valid BCP 47 language tag.",
18
+ recommendation: 'Use a BCP 47 tag such as "en", "en-US" or "pt-BR".',
19
+ }, ({ page }, make) => page.lang && !BCP47.test(page.lang)
20
+ ? make(`<html lang="${page.lang}"> is not a valid language tag.`, {
21
+ selector: "html",
22
+ evidence: { lang: page.lang },
23
+ })
24
+ : null),
25
+ defineRule({
26
+ id: "viewport.missing",
27
+ category: "document",
28
+ severity: "warning",
29
+ description: 'No <meta name="viewport"> is present, so the page is not mobile-friendly.',
30
+ recommendation: 'Add <meta name="viewport" content="width=device-width, initial-scale=1"> to <head>. Google indexes the mobile version of pages first.',
31
+ docs: GOOGLE_DOCS.mobile,
32
+ }, ({ page }, make) => page.viewport
33
+ ? null
34
+ : make("Page has no viewport meta tag.", {
35
+ selector: 'head > meta[name="viewport"]',
36
+ })),
37
+ defineRule({
38
+ id: "viewport.not-scalable",
39
+ category: "document",
40
+ severity: "info",
41
+ description: "The viewport disables user zoom (user-scalable=no or maximum-scale=1).",
42
+ recommendation: "Remove user-scalable=no and maximum-scale limits; blocking zoom is an accessibility problem flagged by Lighthouse.",
43
+ }, ({ page }, make) => {
44
+ const v = (page.viewport ?? "").toLowerCase().replaceAll(/\s/gu, "");
45
+ return /user-scalable=(?:no|0)/u.test(v) ||
46
+ /maximum-scale=1(?:\.0*)?(?:,|$)/u.test(v)
47
+ ? make("Viewport prevents users from zooming.", {
48
+ selector: 'head > meta[name="viewport"]',
49
+ evidence: { viewport: page.viewport },
50
+ })
51
+ : null;
52
+ }),
53
+ defineRule({
54
+ id: "charset.missing",
55
+ category: "document",
56
+ severity: "info",
57
+ description: "No character encoding is declared.",
58
+ recommendation: 'Add <meta charset="utf-8"> as the first element in <head>.',
59
+ }, ({ page }, make) => page.charset
60
+ ? null
61
+ : make("No <meta charset> declaration found.", {
62
+ selector: "head > meta[charset]",
63
+ })),
64
+ defineRule({
65
+ id: "doctype.missing",
66
+ category: "document",
67
+ severity: "info",
68
+ description: "The document does not start with <!DOCTYPE html>.",
69
+ recommendation: "Start the document with <!DOCTYPE html> to avoid quirks-mode rendering.",
70
+ }, ({ page }, make) => page.hasDoctype ? null : make("Document has no HTML5 doctype.")),
71
+ defineRule({
72
+ id: "document.html-too-large",
73
+ category: "document",
74
+ severity: "info",
75
+ description: "The HTML document is larger than 1.5 MB.",
76
+ recommendation: "Reduce inline scripts/styles/data (large JSON state, inline SVG sprites). Very large documents slow parsing and may be truncated by crawlers.",
77
+ }, ({ page }, make) => page.htmlBytes > 1_500_000
78
+ ? make(`HTML is ${(page.htmlBytes / 1_000_000).toFixed(1)} MB.`, {
79
+ evidence: { bytes: page.htmlBytes },
80
+ })
81
+ : null),
82
+ ];
83
+ //# sourceMappingURL=document.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document.js","sourceRoot":"","sources":["../../../src/core/rules/document.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare const headingRules: import("./rule.js").Rule[];