@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.
- package/LICENSE +21 -0
- package/README.md +210 -2
- package/dist/brand.d.ts +11 -0
- package/dist/brand.js +22 -0
- package/dist/brand.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +194 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/audit.d.ts +72 -0
- package/dist/core/audit.js +452 -0
- package/dist/core/audit.js.map +1 -0
- package/dist/core/concurrency.d.ts +4 -0
- package/dist/core/concurrency.js +39 -0
- package/dist/core/concurrency.js.map +1 -0
- package/dist/core/crawl.d.ts +40 -0
- package/dist/core/crawl.js +324 -0
- package/dist/core/crawl.js.map +1 -0
- package/dist/core/fetch.d.ts +27 -0
- package/dist/core/fetch.js +186 -0
- package/dist/core/fetch.js.map +1 -0
- package/dist/core/html/dom.d.ts +13 -0
- package/dist/core/html/dom.js +17 -0
- package/dist/core/html/dom.js.map +1 -0
- package/dist/core/html/extract.d.ts +10 -0
- package/dist/core/html/extract.js +207 -0
- package/dist/core/html/extract.js.map +1 -0
- package/dist/core/html/structured-data.d.ts +21 -0
- package/dist/core/html/structured-data.js +220 -0
- package/dist/core/html/structured-data.js.map +1 -0
- package/dist/core/html/text.d.ts +23 -0
- package/dist/core/html/text.js +152 -0
- package/dist/core/html/text.js.map +1 -0
- package/dist/core/lighthouse.d.ts +50 -0
- package/dist/core/lighthouse.js +216 -0
- package/dist/core/lighthouse.js.map +1 -0
- package/dist/core/performance-rules.d.ts +3 -0
- package/dist/core/performance-rules.js +125 -0
- package/dist/core/performance-rules.js.map +1 -0
- package/dist/core/render-rules.d.ts +8 -0
- package/dist/core/render-rules.js +144 -0
- package/dist/core/render-rules.js.map +1 -0
- package/dist/core/render.d.ts +22 -0
- package/dist/core/render.js +79 -0
- package/dist/core/render.js.map +1 -0
- package/dist/core/report/markdown.d.ts +2 -0
- package/dist/core/report/markdown.js +131 -0
- package/dist/core/report/markdown.js.map +1 -0
- package/dist/core/robots.d.ts +11 -0
- package/dist/core/robots.js +48 -0
- package/dist/core/robots.js.map +1 -0
- package/dist/core/rules/canonical.d.ts +2 -0
- package/dist/core/rules/canonical.js +141 -0
- package/dist/core/rules/canonical.js.map +1 -0
- package/dist/core/rules/content.d.ts +1 -0
- package/dist/core/rules/content.js +55 -0
- package/dist/core/rules/content.js.map +1 -0
- package/dist/core/rules/document.d.ts +1 -0
- package/dist/core/rules/document.js +83 -0
- package/dist/core/rules/document.js.map +1 -0
- package/dist/core/rules/headings.d.ts +1 -0
- package/dist/core/rules/headings.js +81 -0
- package/dist/core/rules/headings.js.map +1 -0
- package/dist/core/rules/hreflang.d.ts +1 -0
- package/dist/core/rules/hreflang.js +100 -0
- package/dist/core/rules/hreflang.js.map +1 -0
- package/dist/core/rules/images.d.ts +1 -0
- package/dist/core/rules/images.js +77 -0
- package/dist/core/rules/images.js.map +1 -0
- package/dist/core/rules/index.d.ts +14 -0
- package/dist/core/rules/index.js +77 -0
- package/dist/core/rules/index.js.map +1 -0
- package/dist/core/rules/indexing.d.ts +10 -0
- package/dist/core/rules/indexing.js +75 -0
- package/dist/core/rules/indexing.js.map +1 -0
- package/dist/core/rules/keyword.d.ts +1 -0
- package/dist/core/rules/keyword.js +111 -0
- package/dist/core/rules/keyword.js.map +1 -0
- package/dist/core/rules/links.d.ts +1 -0
- package/dist/core/rules/links.js +107 -0
- package/dist/core/rules/links.js.map +1 -0
- package/dist/core/rules/meta.d.ts +1 -0
- package/dist/core/rules/meta.js +81 -0
- package/dist/core/rules/meta.js.map +1 -0
- package/dist/core/rules/response.d.ts +1 -0
- package/dist/core/rules/response.js +93 -0
- package/dist/core/rules/response.js.map +1 -0
- package/dist/core/rules/rule.d.ts +42 -0
- package/dist/core/rules/rule.js +51 -0
- package/dist/core/rules/rule.js.map +1 -0
- package/dist/core/rules/social.d.ts +1 -0
- package/dist/core/rules/social.js +115 -0
- package/dist/core/rules/social.js.map +1 -0
- package/dist/core/rules/structured-data.d.ts +25 -0
- package/dist/core/rules/structured-data.js +381 -0
- package/dist/core/rules/structured-data.js.map +1 -0
- package/dist/core/rules/title.d.ts +1 -0
- package/dist/core/rules/title.js +74 -0
- package/dist/core/rules/title.js.map +1 -0
- package/dist/core/score.d.ts +3 -0
- package/dist/core/score.js +17 -0
- package/dist/core/score.js.map +1 -0
- package/dist/core/site-rules.d.ts +64 -0
- package/dist/core/site-rules.js +462 -0
- package/dist/core/site-rules.js.map +1 -0
- package/dist/core/sitemap.d.ts +13 -0
- package/dist/core/sitemap.js +140 -0
- package/dist/core/sitemap.js.map +1 -0
- package/dist/core/static.d.ts +9 -0
- package/dist/core/static.js +281 -0
- package/dist/core/static.js.map +1 -0
- package/dist/core/summary.d.ts +2 -0
- package/dist/core/summary.js +33 -0
- package/dist/core/summary.js.map +1 -0
- package/dist/core/types.d.ts +296 -0
- package/dist/core/types.js +33 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/url.d.ts +20 -0
- package/dist/core/url.js +191 -0
- package/dist/core/url.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-bin.d.ts +2 -0
- package/dist/mcp-bin.js +10 -0
- package/dist/mcp-bin.js.map +1 -0
- package/dist/mcp.d.ts +5 -0
- package/dist/mcp.js +380 -0
- package/dist/mcp.js.map +1 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +6 -0
- package/dist/version.js.map +1 -0
- package/package.json +107 -3
- package/skills/seo-audit/SKILL.md +53 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sitemap.js","sourceRoot":"","sources":["../../src/core/sitemap.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAcvC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,gBAAgB,EAAE,IAAI;IACtB,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,SAAS;CACxD,CAAC,CAAC;AAWH,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAmB,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC;AAEhF,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAmB,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC;AAEhF,MAAM,OAAO,GAAG,CAAC,KAAc,EAA8B,EAAE,CAC7D,OAAO,CAAC,KAAK,CAAC,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC;AAE9C,MAAM,MAAM,GAAG,CAAC,OAAkB,EAAY,EAAE;IAC9C,MAAM,GAAG,GAAa,EAAE,CAAC;IAEzB,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;QAE3C,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;YAChC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QACvB,CAAC;aAAM,IAAI,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACzB,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,IAAY,EACZ,WAAW,GAAkB,IAAI,EAClB,EAAE;IACjB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAE5B,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;IACvC,CAAC;IAED,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC7B,IACE,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC;YAC1C,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,EAC9B,CAAC;YACD,OAAO;gBACL,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,OAAO;qBACV,KAAK,CAAC,QAAQ,CAAC;qBACf,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;qBACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;aAC3C,CAAC;QACJ,CAAC;QAED,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;IACvC,CAAC;IAED,IAAI,GAAe,CAAC;IAEpB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;IACvC,CAAC;IAED,IAAI,GAAG,CAAC,YAAY,EAAE,CAAC;QACrB,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,CAAC,OAAO,CAAC;QAEzC,OAAO;YACL,IAAI,EAAE,cAAc;YACpB,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE;SACpD,CAAC;IACJ,CAAC;IAED,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC;QAE/B,OAAO;YACL,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE;SACpD,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;AACvC,CAAC,CAAC;AAQF,MAAM,YAAY,GAAG,KAAK,EACxB,GAAW,EACX,UAAoB,EACpB,OAAqB,EACE,EAAE;IACzB,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO;YACL,IAAI,EAAE,OAAO;YACb,OAAO,EAAE,GAAG,GAAG,uCAAuC;SACvD,CAAC;IACJ,CAAC;IAED,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;QACxD,YAAY,EAAE,UAAU;KACzB,CAAC,CAAC;IAEH,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;QACd,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,GAAG,KAAK,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC;IAC5D,CAAC;IAED,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;QAC5C,8DAA8D;QAC9D,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACvB,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,GAAG,UAAU,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC;QAClE,CAAC;QAED,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC1B,CAAC;IAED,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC;IAEvD,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACpC,CAAC;IAED,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC;QAC7C,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,GAAG,gCAAgC,EAAE,CAAC;IAC5E,CAAC;IAED,0FAA0F;IAC1F,IAAI,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC1B,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,GAAG,kCAAkC,EAAE,CAAC;AAC9E,CAAC,CAAC;AAEF,kFAAkF;AAClF,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAC/B,UAAoB,EACpB,OAAO,GAAiB,EAAE,EAC1B,MAAM,GAAkB,EAAE,EACJ,EAAE;IACxB,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC;IAC7C,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,IAAI,CAAC;IACvC,MAAM,IAAI,GAAgB,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAC7E,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAE/B,OACE,KAAK,CAAC,MAAM,GAAG,CAAC;QAChB,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,WAAW;QACjC,IAAI,CAAC,IAAI,GAAG,OAAO,EACnB,CAAC;QACD,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;QAE1B,IAAI,GAAG,KAAK,SAAS,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACvC,SAAS;QACX,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAEd,uJAAuJ;QACvJ,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,GAAG,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;QAE7D,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC7B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACpC,CAAC;QAED,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC9B,SAAS;QACX,CAAC;QAED,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;QAC3B,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAEvB,IAAI,MAAM,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;YACnC,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC;aAAM,CAAC;YACN,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;gBAC9B,IAAI,IAAI,CAAC,IAAI,GAAG,OAAO,EAAE,CAAC;oBACxB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBAChB,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,CAAC,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IACtB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC;IAE1B,OAAO,IAAI,CAAC;AACd,CAAC,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CrawlResult } from "./crawl.js";
|
|
2
|
+
export interface DirectoryOptions {
|
|
3
|
+
/** Base URL used to build page URLs and resolve links. Defaults to http://localhost/ */
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
includeSubdomains: boolean;
|
|
6
|
+
maxPages: number;
|
|
7
|
+
userAgent?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const scanDirectory: (dir: string, options: DirectoryOptions) => Promise<CrawlResult>;
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { DEFAULT_USER_AGENT } from "./fetch.js";
|
|
4
|
+
import { extractPage } from "./html/extract.js";
|
|
5
|
+
import { buildRobots } from "./robots.js";
|
|
6
|
+
import { parseSitemap } from "./sitemap.js";
|
|
7
|
+
import { normalizePath, normalizeUrl } from "./url.js";
|
|
8
|
+
const SKIP_DIRS = new Set([
|
|
9
|
+
"node_modules",
|
|
10
|
+
".git",
|
|
11
|
+
".next",
|
|
12
|
+
".nuxt",
|
|
13
|
+
".svelte-kit",
|
|
14
|
+
".cache",
|
|
15
|
+
".vercel",
|
|
16
|
+
".output/server",
|
|
17
|
+
]);
|
|
18
|
+
const MISSING_FILE = "No matching file in output directory";
|
|
19
|
+
const walk = async (dir) => {
|
|
20
|
+
const out = [];
|
|
21
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
22
|
+
for (const entry of entries) {
|
|
23
|
+
if (entry.name.startsWith(".") && entry.name !== ".well-known") {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const full = path.join(dir, entry.name);
|
|
27
|
+
if (entry.isDirectory()) {
|
|
28
|
+
if (SKIP_DIRS.has(entry.name)) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
// oxlint-disable-next-line eslint/no-await-in-loop -- the walk reads one directory at a time, so a deep tree never opens thousands of handles at once
|
|
32
|
+
out.push(...(await walk(full)));
|
|
33
|
+
}
|
|
34
|
+
else if (entry.isFile()) {
|
|
35
|
+
out.push(full);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
};
|
|
40
|
+
const fileExists = async (root, pathname) => {
|
|
41
|
+
let decoded = pathname;
|
|
42
|
+
try {
|
|
43
|
+
decoded = decodeURIComponent(pathname);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
/* keep */
|
|
47
|
+
}
|
|
48
|
+
const candidates = [
|
|
49
|
+
decoded,
|
|
50
|
+
`${decoded}.html`,
|
|
51
|
+
`${decoded}.htm`,
|
|
52
|
+
path.posix.join(decoded, "index.html"),
|
|
53
|
+
path.posix.join(decoded, "index.htm"),
|
|
54
|
+
];
|
|
55
|
+
for (const c of candidates) {
|
|
56
|
+
const full = path.join(root, c);
|
|
57
|
+
if (!full.startsWith(root)) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
// oxlint-disable-next-line eslint/no-await-in-loop -- the candidates are tried in order and the first file found ends the search
|
|
61
|
+
const stat = await fs.stat(full).catch(() => null);
|
|
62
|
+
if (stat?.isFile()) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
};
|
|
68
|
+
/** The check a link gets in a directory audit: 200 when the file is there, 404 when not. */
|
|
69
|
+
const fileCheck = (url, finalUrl, exists) => ({
|
|
70
|
+
url,
|
|
71
|
+
status: exists ? 200 : 404,
|
|
72
|
+
finalUrl,
|
|
73
|
+
redirectChain: [],
|
|
74
|
+
error: exists ? undefined : MISSING_FILE,
|
|
75
|
+
});
|
|
76
|
+
const readPages = async (root, files, base, options, toUrlPath) => {
|
|
77
|
+
const records = [];
|
|
78
|
+
const recordByKey = new Map();
|
|
79
|
+
for (const file of files) {
|
|
80
|
+
// oxlint-disable-next-line eslint/no-await-in-loop -- one file at a time on purpose: a build can hold thousands of pages
|
|
81
|
+
const html = await fs.readFile(file, "utf-8");
|
|
82
|
+
const urlPath = toUrlPath(file).replace(/\/index\.html?$/iu, "/");
|
|
83
|
+
const url = new URL(urlPath, base).href;
|
|
84
|
+
const page = extractPage(html, {
|
|
85
|
+
url,
|
|
86
|
+
siteHost: base.hostname,
|
|
87
|
+
includeSubdomains: options.includeSubdomains,
|
|
88
|
+
});
|
|
89
|
+
const record = {
|
|
90
|
+
url,
|
|
91
|
+
requestedUrl: url,
|
|
92
|
+
page,
|
|
93
|
+
depth: -1,
|
|
94
|
+
file: path.relative(root, file),
|
|
95
|
+
};
|
|
96
|
+
records.push(record);
|
|
97
|
+
recordByKey.set(normalizePath(new URL(url).pathname), record);
|
|
98
|
+
}
|
|
99
|
+
return { records, recordByKey };
|
|
100
|
+
};
|
|
101
|
+
/** Every internal link of every page, checked against the files in the directory. */
|
|
102
|
+
const checkLinks = async (root, records, fileByPathKey) => {
|
|
103
|
+
const linkChecks = new Map();
|
|
104
|
+
const linkSources = new Map();
|
|
105
|
+
for (const record of records) {
|
|
106
|
+
for (const link of record.page.links) {
|
|
107
|
+
if (!link.internal || !link.url) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const target = normalizeUrl(link.url) ?? link.url;
|
|
111
|
+
let sources = linkSources.get(target);
|
|
112
|
+
if (!sources) {
|
|
113
|
+
sources = new Set();
|
|
114
|
+
linkSources.set(target, sources);
|
|
115
|
+
}
|
|
116
|
+
sources.add(record.url);
|
|
117
|
+
if (!linkChecks.has(target)) {
|
|
118
|
+
const { pathname } = new URL(target);
|
|
119
|
+
const exists = fileByPathKey.has(normalizePath(pathname)) ||
|
|
120
|
+
// oxlint-disable-next-line eslint/no-await-in-loop -- a target is checked once, so the next link to it must see this check
|
|
121
|
+
(await fileExists(root, pathname));
|
|
122
|
+
linkChecks.set(target, fileCheck(target, target, exists));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return { linkChecks, linkSources };
|
|
127
|
+
};
|
|
128
|
+
/** Depth via breadth-first search from the root index (if present); returns the start page. */
|
|
129
|
+
const assignDepths = (records, recordByKey) => {
|
|
130
|
+
const start = recordByKey.get("/") ?? records[0];
|
|
131
|
+
if (!start) {
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
start.depth = 0;
|
|
135
|
+
const queue = [start];
|
|
136
|
+
for (let current = queue.shift(); current; current = queue.shift()) {
|
|
137
|
+
for (const link of current.page.links) {
|
|
138
|
+
if (!link.internal || !link.url) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const target = recordByKey.get(normalizePath(new URL(link.url).pathname));
|
|
142
|
+
if (target && target.depth === -1) {
|
|
143
|
+
target.depth = current.depth + 1;
|
|
144
|
+
queue.push(target);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return start;
|
|
149
|
+
};
|
|
150
|
+
/** sitemap.xml, and the sitemaps robots.txt names, read from the output directory. */
|
|
151
|
+
const readSitemaps = async (root, base, robots) => {
|
|
152
|
+
const sitemaps = {
|
|
153
|
+
sources: [],
|
|
154
|
+
urls: [],
|
|
155
|
+
urlCount: 0,
|
|
156
|
+
errors: [],
|
|
157
|
+
};
|
|
158
|
+
const candidates = [
|
|
159
|
+
...new Set([...robots.info.sitemaps, new URL("/sitemap.xml", base).href]),
|
|
160
|
+
];
|
|
161
|
+
const seen = new Set();
|
|
162
|
+
const urls = new Set();
|
|
163
|
+
while (seen.size < 25) {
|
|
164
|
+
const candidate = candidates.shift();
|
|
165
|
+
if (candidate === undefined) {
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
if (seen.has(candidate)) {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
seen.add(candidate);
|
|
172
|
+
let pathname;
|
|
173
|
+
try {
|
|
174
|
+
pathname = decodeURIComponent(new URL(candidate, base).pathname);
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
sitemaps.errors.push(`${candidate}: invalid URL`);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const file = path.join(root, pathname);
|
|
181
|
+
// oxlint-disable-next-line eslint/no-await-in-loop -- a sitemap index adds the sitemaps it lists to the candidates, so each read decides what comes next
|
|
182
|
+
const xml = await fs.readFile(file, "utf-8").catch(() => null);
|
|
183
|
+
if (xml === null) {
|
|
184
|
+
if (robots.info.sitemaps.includes(candidate)) {
|
|
185
|
+
sitemaps.errors.push(`${candidate}: declared in robots.txt but ${pathname} is not in the output directory`);
|
|
186
|
+
}
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const parsed = parseSitemap(xml);
|
|
190
|
+
if (parsed.kind === "unknown") {
|
|
191
|
+
sitemaps.errors.push(`${pathname}: could not parse as a sitemap`);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
sitemaps.sources.push(candidate);
|
|
195
|
+
if (parsed.kind === "sitemapindex") {
|
|
196
|
+
candidates.push(...parsed.locs);
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
for (const loc of parsed.locs) {
|
|
200
|
+
urls.add(loc);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
sitemaps.urls = [...urls];
|
|
205
|
+
sitemaps.urlCount = urls.size;
|
|
206
|
+
return sitemaps;
|
|
207
|
+
};
|
|
208
|
+
/** A check for each sitemap URL, against the files in the directory, unless a link had one. */
|
|
209
|
+
const checkSitemapUrls = (urls, base, fileByPathKey, linkChecks) => {
|
|
210
|
+
for (const u of urls) {
|
|
211
|
+
let key;
|
|
212
|
+
try {
|
|
213
|
+
key = normalizePath(new URL(u).pathname);
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
const synthetic = new URL(key === "/" ? "/" : key, base).href;
|
|
219
|
+
if (!linkChecks.has(synthetic)) {
|
|
220
|
+
linkChecks.set(synthetic, fileCheck(u, u, fileByPathKey.has(key)));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
export const scanDirectory = async (dir, options) => {
|
|
225
|
+
const started = performance.now();
|
|
226
|
+
const root = path.resolve(dir);
|
|
227
|
+
const stat = await fs.stat(root).catch(() => null);
|
|
228
|
+
if (!stat?.isDirectory()) {
|
|
229
|
+
throw new Error(`Not a directory: ${root}`);
|
|
230
|
+
}
|
|
231
|
+
const base = new URL(options.baseUrl.endsWith("/") ? options.baseUrl : `${options.baseUrl}/`);
|
|
232
|
+
const walked = await walk(root);
|
|
233
|
+
const files = walked.toSorted();
|
|
234
|
+
const htmlFiles = files.filter((f) => /\.html?$/iu.test(f));
|
|
235
|
+
const hitLimit = htmlFiles.length > options.maxPages;
|
|
236
|
+
const selected = htmlFiles.slice(0, options.maxPages);
|
|
237
|
+
const toUrlPath = (file) => `/${path.relative(root, file).split(path.sep).map(encodeURIComponent).join("/")}`;
|
|
238
|
+
const fileByPathKey = new Map();
|
|
239
|
+
for (const f of htmlFiles) {
|
|
240
|
+
fileByPathKey.set(normalizePath(toUrlPath(f)), f);
|
|
241
|
+
}
|
|
242
|
+
const { records, recordByKey } = await readPages(root, selected, base, options, toUrlPath);
|
|
243
|
+
const { linkChecks, linkSources } = await checkLinks(root, records, fileByPathKey);
|
|
244
|
+
const start = assignDepths(records, recordByKey);
|
|
245
|
+
const linkedKeys = new Set([...linkSources.keys()].map((u) => normalizePath(new URL(u).pathname)));
|
|
246
|
+
const unlinkedPages = records.flatMap((r) => r !== start && !linkedKeys.has(normalizePath(new URL(r.url).pathname))
|
|
247
|
+
? [r.file ?? r.url]
|
|
248
|
+
: []);
|
|
249
|
+
/* robots.txt and sitemap.xml from the output directory. */
|
|
250
|
+
const robotsText = await fs
|
|
251
|
+
.readFile(path.join(root, "robots.txt"), "utf-8")
|
|
252
|
+
.catch(() => null);
|
|
253
|
+
const robots = buildRobots(new URL("/robots.txt", base).href, robotsText === null ? 404 : 200, undefined, robotsText, options.userAgent ?? DEFAULT_USER_AGENT);
|
|
254
|
+
const sitemaps = await readSitemaps(root, base, robots);
|
|
255
|
+
checkSitemapUrls(sitemaps.urls, base, fileByPathKey, linkChecks);
|
|
256
|
+
return {
|
|
257
|
+
startUrl: base.href,
|
|
258
|
+
records,
|
|
259
|
+
linkChecks,
|
|
260
|
+
linkSources,
|
|
261
|
+
robots: robots.info,
|
|
262
|
+
robotsAllows: robots.info.found ? robots.allows : null,
|
|
263
|
+
blockedByRobots: robots.info.found
|
|
264
|
+
? records.flatMap((r) => (robots.allows(r.url) ? [] : [r.url]))
|
|
265
|
+
: [],
|
|
266
|
+
sitemaps,
|
|
267
|
+
probes: {},
|
|
268
|
+
stats: {
|
|
269
|
+
pagesCrawled: records.length,
|
|
270
|
+
pagesDiscovered: htmlFiles.length,
|
|
271
|
+
linksChecked: linkChecks.size,
|
|
272
|
+
maxPages: options.maxPages,
|
|
273
|
+
maxDepth: Infinity,
|
|
274
|
+
hitPageLimit: hitLimit,
|
|
275
|
+
durationMs: Math.round(performance.now() - started),
|
|
276
|
+
respectedRobots: false,
|
|
277
|
+
},
|
|
278
|
+
unlinkedPages,
|
|
279
|
+
};
|
|
280
|
+
};
|
|
281
|
+
//# sourceMappingURL=static.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"static.js","sourceRoot":"","sources":["../../src/core/static.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { robotsDirectives } from "./rules/indexing.js";
|
|
2
|
+
export const buildSummary = (page, response) => {
|
|
3
|
+
const robots = robotsDirectives(page, response);
|
|
4
|
+
return {
|
|
5
|
+
title: page.title,
|
|
6
|
+
titleLength: page.title?.length ?? 0,
|
|
7
|
+
metaDescription: page.metaDescription,
|
|
8
|
+
metaDescriptionLength: page.metaDescription?.length ?? 0,
|
|
9
|
+
canonical: page.canonical,
|
|
10
|
+
lang: page.lang,
|
|
11
|
+
robots: robots.directives,
|
|
12
|
+
indexable: !robots.noindex && (response?.status ?? 200) < 400,
|
|
13
|
+
h1: page.h1s,
|
|
14
|
+
headings: page.headings.slice(0, 40),
|
|
15
|
+
wordCount: page.wordCount,
|
|
16
|
+
images: {
|
|
17
|
+
total: page.images.length,
|
|
18
|
+
missingAlt: page.images.filter((i) => i.alt === null).length,
|
|
19
|
+
},
|
|
20
|
+
links: {
|
|
21
|
+
internal: page.links.filter((l) => l.internal).length,
|
|
22
|
+
external: page.links.filter((l) => l.url && !l.internal).length,
|
|
23
|
+
nofollow: page.links.filter((l) => l.nofollow).length,
|
|
24
|
+
},
|
|
25
|
+
openGraph: page.og,
|
|
26
|
+
twitterCard: page.twitter["card"] ?? null,
|
|
27
|
+
structuredDataTypes: [
|
|
28
|
+
...new Set(page.structuredData.flatMap((i) => i.types)),
|
|
29
|
+
],
|
|
30
|
+
hreflangCount: page.hreflang.length,
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=summary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summary.js","sourceRoot":"","sources":["../../src/core/summary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAGvD,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,IAAmB,EACnB,QAAuB,EACV,EAAE;IACf,MAAM,MAAM,GAAG,gBAAgB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAEhD,OAAO;QACL,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,WAAW,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC;QACpC,eAAe,EAAE,IAAI,CAAC,eAAe;QACrC,qBAAqB,EAAE,IAAI,CAAC,eAAe,EAAE,MAAM,IAAI,CAAC;QACxD,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,MAAM,EAAE,MAAM,CAAC,UAAU;QACzB,SAAS,EAAE,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,CAAC,GAAG,GAAG;QAC7D,EAAE,EAAE,IAAI,CAAC,GAAG;QACZ,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;QACpC,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,MAAM,EAAE;YACN,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM;YACzB,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC,MAAM;SAC7D;QACD,KAAK,EAAE;YACL,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM;YACrD,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM;YAC/D,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM;SACtD;QACD,SAAS,EAAE,IAAI,CAAC,EAAE;QAClB,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,IAAI;QACzC,mBAAmB,EAAE;YACnB,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;SACxD;QACD,aAAa,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM;KACpC,CAAC;AACJ,CAAC,CAAC"}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the SEO audit core. These shapes are the public contract
|
|
3
|
+
* of the CLI JSON output and the MCP tool results (`schemaVersion: 1`).
|
|
4
|
+
*/
|
|
5
|
+
export type Severity = "error" | "warning" | "info";
|
|
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";
|
|
9
|
+
/** Static description of a rule, exposed through `seo_list_rules` / `agentic-seo-check rules`. */
|
|
10
|
+
export interface RuleMeta {
|
|
11
|
+
id: string;
|
|
12
|
+
category: Category;
|
|
13
|
+
/** Typical severity. Individual findings may escalate or downgrade. */
|
|
14
|
+
severity: Severity;
|
|
15
|
+
description: string;
|
|
16
|
+
recommendation: string;
|
|
17
|
+
docs?: string;
|
|
18
|
+
/** Only runs when this capability/mode is active. */
|
|
19
|
+
requires?: Requirement;
|
|
20
|
+
}
|
|
21
|
+
/** One actionable problem. Designed to be read by an agent that will edit code. */
|
|
22
|
+
export interface Finding {
|
|
23
|
+
rule: string;
|
|
24
|
+
severity: Severity;
|
|
25
|
+
category: Category;
|
|
26
|
+
/** What is wrong, stated as a fact about this page/site. */
|
|
27
|
+
message: string;
|
|
28
|
+
/** What to change. Concrete and framework-agnostic. */
|
|
29
|
+
recommendation: string;
|
|
30
|
+
/** Page URL or file path the finding belongs to (absent for site-wide findings that list pages in evidence). */
|
|
31
|
+
url?: string;
|
|
32
|
+
/** CSS selector or location hint for where the problem lives in the HTML. */
|
|
33
|
+
selector?: string;
|
|
34
|
+
/** Current values, counts, offending items (capped lists). */
|
|
35
|
+
evidence?: Record<string, unknown>;
|
|
36
|
+
docs?: string;
|
|
37
|
+
}
|
|
38
|
+
export interface SeverityCounts {
|
|
39
|
+
error: number;
|
|
40
|
+
warning: number;
|
|
41
|
+
info: number;
|
|
42
|
+
}
|
|
43
|
+
export interface HeadingInfo {
|
|
44
|
+
level: number;
|
|
45
|
+
text: string;
|
|
46
|
+
}
|
|
47
|
+
export interface ImageInfo {
|
|
48
|
+
src: string | null;
|
|
49
|
+
alt: string | null;
|
|
50
|
+
width: string | null;
|
|
51
|
+
height: string | null;
|
|
52
|
+
loading: string | null;
|
|
53
|
+
}
|
|
54
|
+
export interface LinkInfo {
|
|
55
|
+
href: string;
|
|
56
|
+
/** Absolute resolved URL, or null if it could not be resolved (mailto:, javascript:, malformed). */
|
|
57
|
+
url: string | null;
|
|
58
|
+
text: string;
|
|
59
|
+
rel: string | null;
|
|
60
|
+
internal: boolean;
|
|
61
|
+
nofollow: boolean;
|
|
62
|
+
/** True when the link has visible text, an aria-label, a title, or an image with alt text. */
|
|
63
|
+
hasAccessibleName: boolean;
|
|
64
|
+
}
|
|
65
|
+
export interface HreflangInfo {
|
|
66
|
+
lang: string;
|
|
67
|
+
href: string;
|
|
68
|
+
url: string | null;
|
|
69
|
+
}
|
|
70
|
+
export interface StructuredDataItem {
|
|
71
|
+
source: "json-ld" | "microdata";
|
|
72
|
+
types: string[];
|
|
73
|
+
context: string | null;
|
|
74
|
+
data: Record<string, unknown>;
|
|
75
|
+
}
|
|
76
|
+
export interface StructuredDataError {
|
|
77
|
+
source: "json-ld";
|
|
78
|
+
message: string;
|
|
79
|
+
snippet: string;
|
|
80
|
+
}
|
|
81
|
+
/** Everything the extractor pulls out of one HTML document. Rules read only this. */
|
|
82
|
+
export interface ExtractedPage {
|
|
83
|
+
/** URL used to resolve relative links (the final URL after redirects, or the base URL for files/inline HTML). */
|
|
84
|
+
url: string;
|
|
85
|
+
hasDoctype: boolean;
|
|
86
|
+
lang: string | null;
|
|
87
|
+
charset: string | null;
|
|
88
|
+
viewport: string | null;
|
|
89
|
+
titles: string[];
|
|
90
|
+
title: string | null;
|
|
91
|
+
metaDescriptions: string[];
|
|
92
|
+
metaDescription: string | null;
|
|
93
|
+
/** Content of <meta name="robots"> and <meta name="googlebot">, lower-cased. */
|
|
94
|
+
metaRobots: string[];
|
|
95
|
+
canonicals: string[];
|
|
96
|
+
canonical: string | null;
|
|
97
|
+
headings: HeadingInfo[];
|
|
98
|
+
h1s: string[];
|
|
99
|
+
og: Record<string, string>;
|
|
100
|
+
twitter: Record<string, string>;
|
|
101
|
+
images: ImageInfo[];
|
|
102
|
+
links: LinkInfo[];
|
|
103
|
+
hreflang: HreflangInfo[];
|
|
104
|
+
structuredData: StructuredDataItem[];
|
|
105
|
+
structuredDataErrors: StructuredDataError[];
|
|
106
|
+
/** Visible text of <body> with scripts/styles removed and whitespace collapsed. */
|
|
107
|
+
text: string;
|
|
108
|
+
wordCount: number;
|
|
109
|
+
htmlBytes: number;
|
|
110
|
+
hasBaseHref: boolean;
|
|
111
|
+
}
|
|
112
|
+
export interface RedirectHop {
|
|
113
|
+
url: string;
|
|
114
|
+
status: number;
|
|
115
|
+
}
|
|
116
|
+
export interface ResponseInfo {
|
|
117
|
+
status: number | null;
|
|
118
|
+
finalUrl: string;
|
|
119
|
+
redirectChain: RedirectHop[];
|
|
120
|
+
timeMs: number | null;
|
|
121
|
+
contentType: string | null;
|
|
122
|
+
/** Lower-cased header names. Only SEO-relevant headers are kept. */
|
|
123
|
+
headers: Record<string, string>;
|
|
124
|
+
error?: string;
|
|
125
|
+
}
|
|
126
|
+
export interface PageSummary {
|
|
127
|
+
title: string | null;
|
|
128
|
+
titleLength: number;
|
|
129
|
+
metaDescription: string | null;
|
|
130
|
+
metaDescriptionLength: number;
|
|
131
|
+
canonical: string | null;
|
|
132
|
+
lang: string | null;
|
|
133
|
+
robots: string[];
|
|
134
|
+
indexable: boolean;
|
|
135
|
+
h1: string[];
|
|
136
|
+
headings: HeadingInfo[];
|
|
137
|
+
wordCount: number;
|
|
138
|
+
images: {
|
|
139
|
+
total: number;
|
|
140
|
+
missingAlt: number;
|
|
141
|
+
};
|
|
142
|
+
links: {
|
|
143
|
+
internal: number;
|
|
144
|
+
external: number;
|
|
145
|
+
nofollow: number;
|
|
146
|
+
};
|
|
147
|
+
openGraph: Record<string, string>;
|
|
148
|
+
twitterCard: string | null;
|
|
149
|
+
structuredDataTypes: string[];
|
|
150
|
+
hreflangCount: number;
|
|
151
|
+
}
|
|
152
|
+
export interface LighthouseMetrics {
|
|
153
|
+
performanceScore: number | null;
|
|
154
|
+
seoScore: number | null;
|
|
155
|
+
agenticBrowsingScore: number | null;
|
|
156
|
+
lcpMs: number | null;
|
|
157
|
+
cls: number | null;
|
|
158
|
+
tbtMs: number | null;
|
|
159
|
+
fcpMs: number | null;
|
|
160
|
+
speedIndexMs: number | null;
|
|
161
|
+
ttfbMs: number | null;
|
|
162
|
+
inpMs: number | null;
|
|
163
|
+
}
|
|
164
|
+
export interface LighthouseOpportunity {
|
|
165
|
+
id: string;
|
|
166
|
+
title: string;
|
|
167
|
+
description: string;
|
|
168
|
+
score: number | null;
|
|
169
|
+
savingsMs: number | null;
|
|
170
|
+
savingsBytes: number | null;
|
|
171
|
+
}
|
|
172
|
+
export interface LighthouseAuditSummary {
|
|
173
|
+
id: string;
|
|
174
|
+
title: string;
|
|
175
|
+
description: string;
|
|
176
|
+
score: number | null;
|
|
177
|
+
displayValue: string | null;
|
|
178
|
+
}
|
|
179
|
+
export interface PerformanceResult {
|
|
180
|
+
tool: "lighthouse";
|
|
181
|
+
lighthouseVersion: string;
|
|
182
|
+
formFactor: "mobile" | "desktop";
|
|
183
|
+
requestedUrl: string;
|
|
184
|
+
finalUrl: string;
|
|
185
|
+
metrics: LighthouseMetrics;
|
|
186
|
+
opportunities: LighthouseOpportunity[];
|
|
187
|
+
failedSeoAudits: LighthouseAuditSummary[];
|
|
188
|
+
agenticBrowsingAudits: LighthouseAuditSummary[];
|
|
189
|
+
}
|
|
190
|
+
export interface RenderComparison {
|
|
191
|
+
tool: "playwright";
|
|
192
|
+
renderedUrl: string;
|
|
193
|
+
consoleErrors: string[];
|
|
194
|
+
raw: RenderSnapshot;
|
|
195
|
+
rendered: RenderSnapshot;
|
|
196
|
+
}
|
|
197
|
+
export interface RenderSnapshot {
|
|
198
|
+
title: string | null;
|
|
199
|
+
metaDescription: string | null;
|
|
200
|
+
canonical: string | null;
|
|
201
|
+
robots: string[];
|
|
202
|
+
h1Count: number;
|
|
203
|
+
wordCount: number;
|
|
204
|
+
internalLinks: number;
|
|
205
|
+
structuredDataCount: number;
|
|
206
|
+
}
|
|
207
|
+
export type PageSource = "url" | "file" | "html";
|
|
208
|
+
export interface PageAudit {
|
|
209
|
+
schemaVersion: 1;
|
|
210
|
+
kind: "page";
|
|
211
|
+
/** What the caller asked for (URL, path, or "inline-html"). */
|
|
212
|
+
target: string;
|
|
213
|
+
/** Final URL after redirects, or the file path. */
|
|
214
|
+
url: string;
|
|
215
|
+
source: PageSource;
|
|
216
|
+
auditedAt: string;
|
|
217
|
+
response?: ResponseInfo;
|
|
218
|
+
summary: PageSummary;
|
|
219
|
+
findings: Finding[];
|
|
220
|
+
counts: SeverityCounts;
|
|
221
|
+
/** 0-100. Indicative only: 100 minus weighted deductions per finding. */
|
|
222
|
+
score: number;
|
|
223
|
+
performance?: PerformanceResult;
|
|
224
|
+
rendering?: RenderComparison;
|
|
225
|
+
/** Crawl depth from the start URL (site audits only). */
|
|
226
|
+
depth?: number;
|
|
227
|
+
}
|
|
228
|
+
export interface RobotsTxtInfo {
|
|
229
|
+
url: string;
|
|
230
|
+
status: number | null;
|
|
231
|
+
found: boolean;
|
|
232
|
+
sitemaps: string[];
|
|
233
|
+
/** Raw text, capped. */
|
|
234
|
+
text: string | null;
|
|
235
|
+
error?: string;
|
|
236
|
+
}
|
|
237
|
+
export interface SitemapInfo {
|
|
238
|
+
/** Sitemap (and sitemap index) URLs that were fetched. */
|
|
239
|
+
sources: string[];
|
|
240
|
+
/** Page URLs listed across all sitemaps (capped). */
|
|
241
|
+
urls: string[];
|
|
242
|
+
urlCount: number;
|
|
243
|
+
errors: string[];
|
|
244
|
+
}
|
|
245
|
+
export interface CrawlStats {
|
|
246
|
+
pagesCrawled: number;
|
|
247
|
+
pagesDiscovered: number;
|
|
248
|
+
linksChecked: number;
|
|
249
|
+
maxPages: number;
|
|
250
|
+
maxDepth: number;
|
|
251
|
+
hitPageLimit: boolean;
|
|
252
|
+
durationMs: number;
|
|
253
|
+
respectedRobots: boolean;
|
|
254
|
+
}
|
|
255
|
+
export interface IssueRollup {
|
|
256
|
+
rule: string;
|
|
257
|
+
severity: Severity;
|
|
258
|
+
category: Category;
|
|
259
|
+
pages: number;
|
|
260
|
+
message: string;
|
|
261
|
+
recommendation: string;
|
|
262
|
+
/** Up to 10 example URLs. */
|
|
263
|
+
examples: string[];
|
|
264
|
+
}
|
|
265
|
+
export interface SiteAudit {
|
|
266
|
+
schemaVersion: 1;
|
|
267
|
+
kind: "site";
|
|
268
|
+
target: string;
|
|
269
|
+
source: "crawl" | "directory";
|
|
270
|
+
auditedAt: string;
|
|
271
|
+
crawl: CrawlStats;
|
|
272
|
+
robotsTxt: RobotsTxtInfo | null;
|
|
273
|
+
sitemaps: SitemapInfo;
|
|
274
|
+
pages: PageAudit[];
|
|
275
|
+
/** Findings that span pages (duplicates, broken links, sitemap mismatches, ...). */
|
|
276
|
+
siteFindings: Finding[];
|
|
277
|
+
/** Page-level findings rolled up by rule, most severe and most frequent first. */
|
|
278
|
+
issues: IssueRollup[];
|
|
279
|
+
counts: SeverityCounts;
|
|
280
|
+
score: number;
|
|
281
|
+
}
|
|
282
|
+
export type Audit = PageAudit | SiteAudit;
|
|
283
|
+
export interface Thresholds {
|
|
284
|
+
titleMinChars: number;
|
|
285
|
+
titleMaxChars: number;
|
|
286
|
+
descriptionMinChars: number;
|
|
287
|
+
descriptionMaxChars: number;
|
|
288
|
+
h1MaxChars: number;
|
|
289
|
+
thinContentWords: number;
|
|
290
|
+
slowResponseMs: number;
|
|
291
|
+
deepPageDepth: number;
|
|
292
|
+
maxKeywordDensityPercent: number;
|
|
293
|
+
}
|
|
294
|
+
export declare const DEFAULT_THRESHOLDS: Thresholds;
|
|
295
|
+
export declare const emptyCounts: () => SeverityCounts;
|
|
296
|
+
export declare const countFindings: (findings: Finding[]) => SeverityCounts;
|