@rivi-software/agentic-seo-check 0.0.0-stage → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +160 -2
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +149 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/audit.d.ts +72 -0
- package/dist/core/audit.js +337 -0
- package/dist/core/audit.js.map +1 -0
- package/dist/core/concurrency.d.ts +4 -0
- package/dist/core/concurrency.js +29 -0
- package/dist/core/concurrency.js.map +1 -0
- package/dist/core/crawl.d.ts +40 -0
- package/dist/core/crawl.js +195 -0
- package/dist/core/crawl.js.map +1 -0
- package/dist/core/fetch.d.ts +27 -0
- package/dist/core/fetch.js +135 -0
- package/dist/core/fetch.js.map +1 -0
- package/dist/core/html/extract.d.ts +10 -0
- package/dist/core/html/extract.js +150 -0
- package/dist/core/html/extract.js.map +1 -0
- package/dist/core/html/structured-data.d.ts +11 -0
- package/dist/core/html/structured-data.js +185 -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 +149 -0
- package/dist/core/html/text.js.map +1 -0
- package/dist/core/lighthouse.d.ts +49 -0
- package/dist/core/lighthouse.js +140 -0
- package/dist/core/lighthouse.js.map +1 -0
- package/dist/core/performance-rules.d.ts +3 -0
- package/dist/core/performance-rules.js +53 -0
- package/dist/core/performance-rules.js.map +1 -0
- package/dist/core/render-rules.d.ts +7 -0
- package/dist/core/render-rules.js +77 -0
- package/dist/core/render-rules.js.map +1 -0
- package/dist/core/render.d.ts +22 -0
- package/dist/core/render.js +73 -0
- package/dist/core/render.js.map +1 -0
- package/dist/core/report/markdown.d.ts +2 -0
- package/dist/core/report/markdown.js +152 -0
- package/dist/core/report/markdown.js.map +1 -0
- package/dist/core/robots.d.ts +11 -0
- package/dist/core/robots.js +33 -0
- package/dist/core/robots.js.map +1 -0
- package/dist/core/rules/canonical.d.ts +2 -0
- package/dist/core/rules/canonical.js +105 -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 +41 -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 +60 -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 +61 -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 +68 -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 +59 -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 +76 -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 +53 -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 +74 -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 +72 -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 +60 -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 +59 -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 +74 -0
- package/dist/core/rules/social.js.map +1 -0
- package/dist/core/rules/structured-data.d.ts +23 -0
- package/dist/core/rules/structured-data.js +169 -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 +59 -0
- package/dist/core/rules/title.js.map +1 -0
- package/dist/core/score.d.ts +3 -0
- package/dist/core/score.js +15 -0
- package/dist/core/score.js.map +1 -0
- package/dist/core/site-rules.d.ts +64 -0
- package/dist/core/site-rules.js +251 -0
- package/dist/core/site-rules.js.map +1 -0
- package/dist/core/sitemap.d.ts +13 -0
- package/dist/core/sitemap.js +95 -0
- package/dist/core/sitemap.js.map +1 -0
- package/dist/core/static.d.ts +9 -0
- package/dist/core/static.js +184 -0
- package/dist/core/static.js.map +1 -0
- package/dist/core/summary.d.ts +2 -0
- package/dist/core/summary.js +28 -0
- package/dist/core/summary.js.map +1 -0
- package/dist/core/types.d.ts +296 -0
- package/dist/core/types.js +26 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/url.d.ts +20 -0
- package/dist/core/url.js +139 -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 +7 -0
- package/dist/mcp-bin.js.map +1 -0
- package/dist/mcp.d.ts +5 -0
- package/dist/mcp.js +190 -0
- package/dist/mcp.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +4 -0
- package/dist/version.js.map +1 -0
- package/package.json +89 -3
- package/skills/seo-audit/SKILL.md +53 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { ExtractedPage, Finding, ResponseInfo, RobotsTxtInfo, RuleMeta, SitemapInfo, Thresholds, CrawlStats } from './types.js';
|
|
2
|
+
export declare const SITE_RULES: RuleMeta[];
|
|
3
|
+
export interface PageRecord {
|
|
4
|
+
/** Normalized final URL (or synthetic URL for files). */
|
|
5
|
+
url: string;
|
|
6
|
+
requestedUrl: string;
|
|
7
|
+
page: ExtractedPage;
|
|
8
|
+
response?: ResponseInfo;
|
|
9
|
+
depth: number;
|
|
10
|
+
/** File path for directory audits. */
|
|
11
|
+
file?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface LinkCheck {
|
|
14
|
+
url: string;
|
|
15
|
+
status: number | null;
|
|
16
|
+
finalUrl: string;
|
|
17
|
+
redirectChain: {
|
|
18
|
+
url: string;
|
|
19
|
+
status: number;
|
|
20
|
+
}[];
|
|
21
|
+
error?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface ProbeResults {
|
|
24
|
+
soft404?: {
|
|
25
|
+
url: string;
|
|
26
|
+
status: number | null;
|
|
27
|
+
} | null;
|
|
28
|
+
httpRedirect?: {
|
|
29
|
+
url: string;
|
|
30
|
+
status: number | null;
|
|
31
|
+
finalUrl: string | null;
|
|
32
|
+
redirectsToHttps: boolean;
|
|
33
|
+
} | null;
|
|
34
|
+
hostVariant?: {
|
|
35
|
+
url: string;
|
|
36
|
+
status: number | null;
|
|
37
|
+
finalUrl: string | null;
|
|
38
|
+
redirectsToMain: boolean;
|
|
39
|
+
} | null;
|
|
40
|
+
}
|
|
41
|
+
export interface SiteContext {
|
|
42
|
+
startUrl: string;
|
|
43
|
+
source: 'crawl' | 'directory';
|
|
44
|
+
pages: PageRecord[];
|
|
45
|
+
/** Status of every internal URL that was fetched or probed, keyed by normalized URL. */
|
|
46
|
+
linkChecks: Map<string, LinkCheck>;
|
|
47
|
+
/** For every internal target URL, the pages linking to it. */
|
|
48
|
+
linkSources: Map<string, Set<string>>;
|
|
49
|
+
robots: RobotsTxtInfo | null;
|
|
50
|
+
robotsAllows: ((url: string) => boolean) | null;
|
|
51
|
+
blockedByRobots: string[];
|
|
52
|
+
sitemaps: SitemapInfo;
|
|
53
|
+
probes: ProbeResults;
|
|
54
|
+
crawl: CrawlStats;
|
|
55
|
+
thresholds: Thresholds;
|
|
56
|
+
/** Compare URLs by path only (directory audits, where hosts are synthetic). */
|
|
57
|
+
pathOnly: boolean;
|
|
58
|
+
/** External link targets that were probed, keyed by URL. */
|
|
59
|
+
externalChecks?: Map<string, LinkCheck>;
|
|
60
|
+
externalSources?: Map<string, Set<string>>;
|
|
61
|
+
/** Directory audits: HTML files nobody links to. */
|
|
62
|
+
unlinkedPages?: string[];
|
|
63
|
+
}
|
|
64
|
+
export declare function runSiteRules(ctx: SiteContext): Finding[];
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { finding, GOOGLE_DOCS, cap } from './rules/rule.js';
|
|
2
|
+
import { robotsDirectives } from './rules/indexing.js';
|
|
3
|
+
import { normalizeForComparison, shingles, jaccard } from './html/text.js';
|
|
4
|
+
import { normalizeUrl, normalizePath } from './url.js';
|
|
5
|
+
const meta = (id, severity, description, recommendation, docs) => ({
|
|
6
|
+
id,
|
|
7
|
+
category: 'site',
|
|
8
|
+
severity,
|
|
9
|
+
description,
|
|
10
|
+
recommendation,
|
|
11
|
+
docs,
|
|
12
|
+
requires: 'crawl',
|
|
13
|
+
});
|
|
14
|
+
export const SITE_RULES = [
|
|
15
|
+
meta('site.robots-txt-missing', 'warning', 'No robots.txt was found at the site root.', 'Add /robots.txt. At minimum: "User-agent: *" with no disallow rules, plus a "Sitemap:" line.', GOOGLE_DOCS.robotsTxt),
|
|
16
|
+
meta('site.robots-txt-error', 'warning', 'robots.txt returned a server error or could not be fetched. Google treats 5xx robots.txt as "do not crawl anything".', 'Make /robots.txt return 200 (or 404 if you have none).', GOOGLE_DOCS.robotsTxt),
|
|
17
|
+
meta('site.robots-txt-blocks-all', 'error', 'robots.txt disallows crawling of the whole site for all user agents.', 'Remove "Disallow: /" for "User-agent: *" unless the site is intentionally private (staging).', GOOGLE_DOCS.robotsTxt),
|
|
18
|
+
meta('site.robots-blocked-pages', 'warning', 'Pages linked internally (or listed in the sitemap) are disallowed by robots.txt.', 'Allow the paths in robots.txt, or stop linking to them and remove them from the sitemap if they should stay hidden.', GOOGLE_DOCS.robotsTxt),
|
|
19
|
+
meta('site.sitemap-missing', 'warning', 'No XML sitemap was found (robots.txt Sitemap: line or /sitemap.xml).', 'Generate a sitemap.xml listing canonical, indexable URLs and reference it from robots.txt.', GOOGLE_DOCS.sitemaps),
|
|
20
|
+
meta('site.sitemap-error', 'error', 'A sitemap could not be fetched or parsed.', 'Make the sitemap return 200 with valid XML (urlset or sitemapindex).', GOOGLE_DOCS.sitemaps),
|
|
21
|
+
meta('site.sitemap-noindex-page', 'error', 'The sitemap lists pages that are set to noindex.', 'Either remove noindex (if the page should rank) or remove the URL from the sitemap. Conflicting signals waste crawl budget.', GOOGLE_DOCS.sitemaps),
|
|
22
|
+
meta('site.sitemap-non-canonical', 'warning', 'The sitemap lists URLs whose canonical points to a different URL.', 'List only canonical URLs in the sitemap.', GOOGLE_DOCS.sitemaps),
|
|
23
|
+
meta('site.sitemap-broken-url', 'error', 'The sitemap lists URLs that return 4xx/5xx.', 'Remove dead URLs from the sitemap or fix them.', GOOGLE_DOCS.sitemaps),
|
|
24
|
+
meta('site.sitemap-redirecting-url', 'warning', 'The sitemap lists URLs that redirect.', 'Replace redirecting URLs in the sitemap with their final destination.', GOOGLE_DOCS.sitemaps),
|
|
25
|
+
meta('site.not-in-sitemap', 'info', 'Indexable pages discovered by crawling are not listed in the sitemap.', 'Add the missing URLs to the sitemap (or generate it from the router so it stays complete).', GOOGLE_DOCS.sitemaps),
|
|
26
|
+
meta('site.orphan-pages', 'info', 'Sitemap URLs that no crawled page links to.', 'Link orphan pages from relevant pages or navigation; pages without internal links get little authority and are crawled less.', GOOGLE_DOCS.links),
|
|
27
|
+
meta('site.broken-internal-links', 'error', 'Internal links point to URLs that return 4xx/5xx or fail to load.', 'Fix or remove the links listed in evidence. Each entry shows the broken target and the pages linking to it.', GOOGLE_DOCS.links),
|
|
28
|
+
meta('site.redirected-internal-links', 'warning', 'Internal links point to URLs that redirect.', 'Update the links to the final URL to save a round trip for users and crawlers.', GOOGLE_DOCS.redirects),
|
|
29
|
+
meta('site.redirect-chains', 'warning', 'Internal URLs go through two or more redirects (or loop).', 'Point each redirect straight at its final destination.', GOOGLE_DOCS.redirects),
|
|
30
|
+
meta('site.duplicate-titles', 'warning', 'Several indexable pages share the same <title>.', 'Give each page a unique title that describes that page (templates: include the item name, category and brand).', GOOGLE_DOCS.titles),
|
|
31
|
+
meta('site.duplicate-descriptions', 'warning', 'Several indexable pages share the same meta description.', 'Write a unique description per page, or generate it from page-specific data.', GOOGLE_DOCS.snippets),
|
|
32
|
+
meta('site.duplicate-h1', 'info', 'Several pages share the same <h1>.', 'Make H1s page-specific.'),
|
|
33
|
+
meta('site.duplicate-content', 'warning', 'Pages with identical or near-identical body text.', 'Consolidate duplicates with a canonical to one URL, 301 redirect, or make the content distinct.', GOOGLE_DOCS.canonical),
|
|
34
|
+
meta('site.deep-pages', 'info', 'Pages that are more than the configured number of clicks (default 3) from the start URL.', 'Flatten the architecture: link important deep pages from category pages, navigation or the homepage.', GOOGLE_DOCS.links),
|
|
35
|
+
meta('site.soft-404', 'warning', 'A clearly non-existent URL returns HTTP 200 instead of 404.', 'Return a real 404 (or 410) status for unknown routes; otherwise search engines index error pages and waste crawl budget.', GOOGLE_DOCS.softs404),
|
|
36
|
+
meta('site.http-not-redirected', 'warning', 'The http:// version of the site does not redirect to https://.', 'Add a permanent (301/308) redirect from http to https at the server or CDN.', GOOGLE_DOCS.redirects),
|
|
37
|
+
meta('site.host-variant-not-redirected', 'warning', 'The www/non-www variant of the host does not redirect to the main host.', 'Redirect the alternate host (www or bare domain) to the canonical host with a 301/308.', GOOGLE_DOCS.canonical),
|
|
38
|
+
meta('site.hreflang-no-return-link', 'warning', 'hreflang alternates do not link back to the page that references them.', 'Every page in an hreflang cluster must list all alternates including itself; mismatches invalidate the annotations.', GOOGLE_DOCS.hreflang),
|
|
39
|
+
meta('site.broken-external-links', 'warning', 'External links point to URLs that return 4xx/5xx or fail to load.', 'Fix or remove the dead outbound links; they hurt user trust and signal an unmaintained page.', GOOGLE_DOCS.links),
|
|
40
|
+
meta('site.unlinked-pages', 'info', 'HTML files in the build output that no other page links to.', 'Link the pages from navigation or related content, or remove them from the build if they are obsolete.', GOOGLE_DOCS.links),
|
|
41
|
+
meta('site.crawl-limit-reached', 'info', 'The crawl stopped at the page limit, so site-wide checks (orphans, duplicates, sitemap coverage) are partial.', 'Re-run with a higher maxPages value for a complete picture.'),
|
|
42
|
+
];
|
|
43
|
+
const byId = new Map(SITE_RULES.map((r) => [r.id, r]));
|
|
44
|
+
const m = (id) => byId.get(id);
|
|
45
|
+
export function runSiteRules(ctx) {
|
|
46
|
+
const out = [];
|
|
47
|
+
const key = (url) => (ctx.pathOnly ? pathKey(url) : (normalizeUrl(url) ?? url));
|
|
48
|
+
const pagesByKey = new Map();
|
|
49
|
+
for (const p of ctx.pages)
|
|
50
|
+
pagesByKey.set(key(p.url), p);
|
|
51
|
+
const checksByKey = new Map();
|
|
52
|
+
for (const [u, c] of ctx.linkChecks)
|
|
53
|
+
if (!checksByKey.has(key(u)))
|
|
54
|
+
checksByKey.set(key(u), c);
|
|
55
|
+
const isIndexable = (p) => !robotsDirectives(p.page, p.response).noindex && (p.response?.status ?? 200) < 400;
|
|
56
|
+
const okPages = ctx.pages.filter((p) => (p.response?.status ?? 200) < 400);
|
|
57
|
+
/* robots.txt */
|
|
58
|
+
if (ctx.robots) {
|
|
59
|
+
if (ctx.robots.error || (ctx.robots.status !== null && ctx.robots.status >= 500))
|
|
60
|
+
out.push(finding(m('site.robots-txt-error'), `robots.txt at ${ctx.robots.url} could not be read (${ctx.robots.error ?? `HTTP ${ctx.robots.status}`}).`, { evidence: { url: ctx.robots.url, status: ctx.robots.status, error: ctx.robots.error } }));
|
|
61
|
+
else if (!ctx.robots.found)
|
|
62
|
+
out.push(finding(m('site.robots-txt-missing'), `No robots.txt at ${ctx.robots.url}.`, { severity: ctx.source === 'directory' ? 'info' : 'warning', evidence: { url: ctx.robots.url, status: ctx.robots.status } }));
|
|
63
|
+
else if (ctx.robotsAllows && !ctx.robotsAllows(ctx.startUrl))
|
|
64
|
+
out.push(finding(m('site.robots-txt-blocks-all'), 'robots.txt blocks the start URL for the crawler user agent.', { evidence: { url: ctx.robots.url, robotsTxt: ctx.robots.text?.slice(0, 500) } }));
|
|
65
|
+
}
|
|
66
|
+
if (ctx.blockedByRobots.length)
|
|
67
|
+
out.push(finding(m('site.robots-blocked-pages'), `${ctx.blockedByRobots.length} linked/sitemap URL(s) are disallowed by robots.txt.`, { evidence: { count: ctx.blockedByRobots.length, examples: cap(ctx.blockedByRobots) } }));
|
|
68
|
+
/* sitemaps */
|
|
69
|
+
const sitemapKeys = new Set(ctx.sitemaps.urls.map(key));
|
|
70
|
+
if (ctx.sitemaps.errors.length)
|
|
71
|
+
out.push(finding(m('site.sitemap-error'), `${ctx.sitemaps.errors.length} sitemap problem(s).`, { evidence: { errors: cap(ctx.sitemaps.errors) } }));
|
|
72
|
+
if (ctx.sitemaps.sources.length === 0 && ctx.sitemaps.errors.length === 0)
|
|
73
|
+
out.push(finding(m('site.sitemap-missing'), 'No sitemap was found.', { severity: ctx.source === 'directory' ? 'info' : 'warning' }));
|
|
74
|
+
if (ctx.sitemaps.urlCount > 0) {
|
|
75
|
+
const noindex = [];
|
|
76
|
+
const nonCanonical = [];
|
|
77
|
+
const broken = [];
|
|
78
|
+
const redirecting = [];
|
|
79
|
+
for (const smUrl of ctx.sitemaps.urls) {
|
|
80
|
+
const k = key(smUrl);
|
|
81
|
+
const page = pagesByKey.get(k);
|
|
82
|
+
if (page) {
|
|
83
|
+
if (robotsDirectives(page.page, page.response).noindex)
|
|
84
|
+
noindex.push(smUrl);
|
|
85
|
+
const canon = page.page.canonical;
|
|
86
|
+
if (canon && key(canon) !== k)
|
|
87
|
+
nonCanonical.push({ url: smUrl, canonical: canon });
|
|
88
|
+
}
|
|
89
|
+
const check = checksByKey.get(k);
|
|
90
|
+
if (check) {
|
|
91
|
+
if ((check.status ?? 0) >= 400 || check.error)
|
|
92
|
+
broken.push({ url: smUrl, status: check.status });
|
|
93
|
+
else if (check.redirectChain.length > 0)
|
|
94
|
+
redirecting.push({ url: smUrl, finalUrl: check.finalUrl });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (noindex.length)
|
|
98
|
+
out.push(finding(m('site.sitemap-noindex-page'), `${noindex.length} sitemap URL(s) are noindex.`, { evidence: { count: noindex.length, examples: cap(noindex) } }));
|
|
99
|
+
if (nonCanonical.length)
|
|
100
|
+
out.push(finding(m('site.sitemap-non-canonical'), `${nonCanonical.length} sitemap URL(s) canonicalise to another URL.`, { evidence: { count: nonCanonical.length, examples: cap(nonCanonical) } }));
|
|
101
|
+
if (broken.length)
|
|
102
|
+
out.push(finding(m('site.sitemap-broken-url'), `${broken.length} sitemap URL(s) return errors.`, { evidence: { count: broken.length, examples: cap(broken) } }));
|
|
103
|
+
if (redirecting.length)
|
|
104
|
+
out.push(finding(m('site.sitemap-redirecting-url'), `${redirecting.length} sitemap URL(s) redirect.`, { evidence: { count: redirecting.length, examples: cap(redirecting) } }));
|
|
105
|
+
const notInSitemap = okPages.filter((p) => isIndexable(p) && !sitemapKeys.has(key(p.url)) && (!p.page.canonical || key(p.page.canonical) === key(p.url))).map((p) => p.url);
|
|
106
|
+
if (notInSitemap.length)
|
|
107
|
+
out.push(finding(m('site.not-in-sitemap'), `${notInSitemap.length} crawled indexable page(s) are missing from the sitemap.`, { evidence: { count: notInSitemap.length, examples: cap(notInSitemap) } }));
|
|
108
|
+
if (!ctx.crawl.hitPageLimit) {
|
|
109
|
+
const linked = new Set();
|
|
110
|
+
for (const target of ctx.linkSources.keys())
|
|
111
|
+
linked.add(key(target));
|
|
112
|
+
linked.add(key(ctx.startUrl));
|
|
113
|
+
const orphans = ctx.sitemaps.urls.filter((u) => !linked.has(key(u)));
|
|
114
|
+
if (orphans.length)
|
|
115
|
+
out.push(finding(m('site.orphan-pages'), `${orphans.length} sitemap URL(s) are not linked from any crawled page.`, { evidence: { count: orphans.length, examples: cap(orphans) } }));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/* link health */
|
|
119
|
+
const broken = [];
|
|
120
|
+
const redirected = [];
|
|
121
|
+
const chains = [];
|
|
122
|
+
for (const [k, check] of ctx.linkChecks) {
|
|
123
|
+
const sources = [...(ctx.linkSources.get(k) ?? [])];
|
|
124
|
+
if (sources.length === 0)
|
|
125
|
+
continue;
|
|
126
|
+
if (check.error || (check.status ?? 0) >= 400)
|
|
127
|
+
broken.push({ url: check.url, status: check.status, error: check.error, linkedFrom: cap(sources, 3) });
|
|
128
|
+
else if (check.redirectChain.length >= 2)
|
|
129
|
+
chains.push({ url: check.url, hops: check.redirectChain.length, finalUrl: check.finalUrl });
|
|
130
|
+
else if (check.redirectChain.length === 1)
|
|
131
|
+
redirected.push({ url: check.url, finalUrl: check.finalUrl, status: check.redirectChain[0].status, linkedFrom: cap(sources, 3) });
|
|
132
|
+
}
|
|
133
|
+
if (broken.length)
|
|
134
|
+
out.push(finding(m('site.broken-internal-links'), `${broken.length} internal link target(s) are broken.`, { evidence: { count: broken.length, examples: cap(broken, 20) } }));
|
|
135
|
+
if (redirected.length)
|
|
136
|
+
out.push(finding(m('site.redirected-internal-links'), `${redirected.length} internal link target(s) redirect.`, { evidence: { count: redirected.length, examples: cap(redirected, 20) } }));
|
|
137
|
+
if (chains.length)
|
|
138
|
+
out.push(finding(m('site.redirect-chains'), `${chains.length} internal URL(s) have redirect chains.`, { evidence: { count: chains.length, examples: cap(chains) } }));
|
|
139
|
+
if (ctx.externalChecks) {
|
|
140
|
+
const dead = [];
|
|
141
|
+
for (const [u, check] of ctx.externalChecks) {
|
|
142
|
+
if (check.error || (check.status ?? 0) >= 400)
|
|
143
|
+
dead.push({ url: u, status: check.status, error: check.error, linkedFrom: cap([...(ctx.externalSources?.get(u) ?? [])], 3) });
|
|
144
|
+
}
|
|
145
|
+
if (dead.length)
|
|
146
|
+
out.push(finding(m('site.broken-external-links'), `${dead.length} external link target(s) are broken.`, { evidence: { count: dead.length, examples: cap(dead, 20) } }));
|
|
147
|
+
}
|
|
148
|
+
if (ctx.unlinkedPages?.length)
|
|
149
|
+
out.push(finding(m('site.unlinked-pages'), `${ctx.unlinkedPages.length} HTML file(s) are not linked from any other page.`, { evidence: { count: ctx.unlinkedPages.length, examples: cap(ctx.unlinkedPages) } }));
|
|
150
|
+
/* duplicates */
|
|
151
|
+
const indexable = okPages.filter(isIndexable);
|
|
152
|
+
pushDuplicates(out, m('site.duplicate-titles'), indexable, (p) => p.page.title, 'title');
|
|
153
|
+
pushDuplicates(out, m('site.duplicate-descriptions'), indexable, (p) => p.page.metaDescription, 'meta description');
|
|
154
|
+
pushDuplicates(out, m('site.duplicate-h1'), indexable, (p) => p.page.h1s[0] ?? null, 'H1');
|
|
155
|
+
pushDuplicateContent(out, indexable);
|
|
156
|
+
/* architecture */
|
|
157
|
+
const deep = okPages.filter((p) => p.depth > ctx.thresholds.deepPageDepth);
|
|
158
|
+
if (deep.length)
|
|
159
|
+
out.push(finding(m('site.deep-pages'), `${deep.length} page(s) are more than ${ctx.thresholds.deepPageDepth} clicks from the start URL.`, { evidence: { count: deep.length, examples: cap(deep.map((p) => ({ url: p.url, depth: p.depth }))) } }));
|
|
160
|
+
/* probes */
|
|
161
|
+
if (ctx.probes.soft404 && ctx.probes.soft404.status !== null && ctx.probes.soft404.status < 400)
|
|
162
|
+
out.push(finding(m('site.soft-404'), `${ctx.probes.soft404.url} returned HTTP ${ctx.probes.soft404.status}.`, { evidence: ctx.probes.soft404 }));
|
|
163
|
+
if (ctx.probes.httpRedirect && ctx.probes.httpRedirect.status !== null && !ctx.probes.httpRedirect.redirectsToHttps)
|
|
164
|
+
out.push(finding(m('site.http-not-redirected'), `${ctx.probes.httpRedirect.url} returned HTTP ${ctx.probes.httpRedirect.status} without redirecting to https.`, { evidence: ctx.probes.httpRedirect }));
|
|
165
|
+
if (ctx.probes.hostVariant && ctx.probes.hostVariant.status !== null && !ctx.probes.hostVariant.redirectsToMain)
|
|
166
|
+
out.push(finding(m('site.host-variant-not-redirected'), `${ctx.probes.hostVariant.url} returned HTTP ${ctx.probes.hostVariant.status} without redirecting to the main host.`, { evidence: ctx.probes.hostVariant }));
|
|
167
|
+
/* hreflang return links */
|
|
168
|
+
const missingReturn = [];
|
|
169
|
+
for (const p of ctx.pages) {
|
|
170
|
+
for (const alt of p.page.hreflang) {
|
|
171
|
+
if (!alt.url)
|
|
172
|
+
continue;
|
|
173
|
+
const target = pagesByKey.get(key(alt.url));
|
|
174
|
+
if (!target || target === p)
|
|
175
|
+
continue;
|
|
176
|
+
const linksBack = target.page.hreflang.some((h) => h.url && key(h.url) === key(p.url));
|
|
177
|
+
if (!linksBack)
|
|
178
|
+
missingReturn.push({ page: p.url, alternate: alt.url });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (missingReturn.length)
|
|
182
|
+
out.push(finding(m('site.hreflang-no-return-link'), `${missingReturn.length} hreflang alternate(s) do not link back.`, { evidence: { count: missingReturn.length, examples: cap(missingReturn) } }));
|
|
183
|
+
if (ctx.crawl.hitPageLimit)
|
|
184
|
+
out.push(finding(m('site.crawl-limit-reached'), `Crawl stopped after ${ctx.crawl.pagesCrawled} pages (limit ${ctx.crawl.maxPages}); ${ctx.crawl.pagesDiscovered} URLs were discovered.`, { evidence: { pagesCrawled: ctx.crawl.pagesCrawled, pagesDiscovered: ctx.crawl.pagesDiscovered, maxPages: ctx.crawl.maxPages } }));
|
|
185
|
+
return out;
|
|
186
|
+
}
|
|
187
|
+
function pathKey(url) {
|
|
188
|
+
try {
|
|
189
|
+
return normalizePath(new URL(url).pathname);
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return url;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function pushDuplicates(out, rule, pages, pick, label) {
|
|
196
|
+
const groups = new Map();
|
|
197
|
+
for (const p of pages) {
|
|
198
|
+
const value = pick(p);
|
|
199
|
+
if (!value)
|
|
200
|
+
continue;
|
|
201
|
+
const k = normalizeForComparison(value);
|
|
202
|
+
if (!groups.has(k))
|
|
203
|
+
groups.set(k, []);
|
|
204
|
+
groups.get(k).push(p.url);
|
|
205
|
+
}
|
|
206
|
+
const dupes = [...groups.entries()].filter(([, urls]) => urls.length > 1);
|
|
207
|
+
if (!dupes.length)
|
|
208
|
+
return;
|
|
209
|
+
const pagesAffected = dupes.reduce((n, [, urls]) => n + urls.length, 0);
|
|
210
|
+
out.push(finding(rule, `${dupes.length} ${label}(s) are shared by ${pagesAffected} pages.`, {
|
|
211
|
+
evidence: { groups: cap(dupes.map(([value, urls]) => ({ value: value.slice(0, 120), pages: cap(urls, 5), count: urls.length }))) },
|
|
212
|
+
}));
|
|
213
|
+
}
|
|
214
|
+
function pushDuplicateContent(out, pages) {
|
|
215
|
+
const candidates = pages.filter((p) => p.page.wordCount >= 50);
|
|
216
|
+
const groups = [];
|
|
217
|
+
const exact = new Map();
|
|
218
|
+
for (const p of candidates) {
|
|
219
|
+
const k = normalizeForComparison(p.page.text);
|
|
220
|
+
if (!exact.has(k))
|
|
221
|
+
exact.set(k, []);
|
|
222
|
+
exact.get(k).push(p.url);
|
|
223
|
+
}
|
|
224
|
+
const seen = new Set();
|
|
225
|
+
for (const urls of exact.values()) {
|
|
226
|
+
if (urls.length > 1) {
|
|
227
|
+
groups.push({ pages: urls, similarity: 1 });
|
|
228
|
+
urls.forEach((u) => seen.add(u));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (candidates.length <= 150) {
|
|
232
|
+
const sets = candidates.map((p) => ({ url: p.url, set: shingles(p.page.text) }));
|
|
233
|
+
for (let i = 0; i < sets.length; i++) {
|
|
234
|
+
for (let j = i + 1; j < sets.length; j++) {
|
|
235
|
+
const a = sets[i];
|
|
236
|
+
const b = sets[j];
|
|
237
|
+
if (seen.has(a.url) && seen.has(b.url))
|
|
238
|
+
continue;
|
|
239
|
+
const sim = jaccard(a.set, b.set);
|
|
240
|
+
if (sim >= 0.9) {
|
|
241
|
+
groups.push({ pages: [a.url, b.url], similarity: Number(sim.toFixed(2)) });
|
|
242
|
+
seen.add(a.url);
|
|
243
|
+
seen.add(b.url);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (groups.length)
|
|
249
|
+
out.push(finding(m('site.duplicate-content'), `${groups.length} group(s) of pages have (near-)identical body text.`, { evidence: { groups: cap(groups) } }));
|
|
250
|
+
}
|
|
251
|
+
//# sourceMappingURL=site-rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"site-rules.js","sourceRoot":"","sources":["../../src/core/site-rules.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SitemapInfo } from './types.js';
|
|
2
|
+
import { type FetchOptions } from './fetch.js';
|
|
3
|
+
export interface SitemapLimits {
|
|
4
|
+
maxSitemaps?: number;
|
|
5
|
+
maxUrls?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface ParsedSitemap {
|
|
8
|
+
kind: 'urlset' | 'sitemapindex' | 'text' | 'unknown';
|
|
9
|
+
locs: string[];
|
|
10
|
+
}
|
|
11
|
+
export declare function parseSitemap(body: string, contentType?: string | null): ParsedSitemap;
|
|
12
|
+
/** Fetch sitemap candidates (following sitemap indexes) and collect page URLs. */
|
|
13
|
+
export declare function loadSitemaps(candidates: string[], options?: FetchOptions, limits?: SitemapLimits): Promise<SitemapInfo>;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { XMLParser } from 'fast-xml-parser';
|
|
2
|
+
import { fetchPage } from './fetch.js';
|
|
3
|
+
const parser = new XMLParser({ ignoreAttributes: true, isArray: (name) => name === 'url' || name === 'sitemap' });
|
|
4
|
+
export function parseSitemap(body, contentType = null) {
|
|
5
|
+
const trimmed = body.trim();
|
|
6
|
+
if (!trimmed)
|
|
7
|
+
return { kind: 'unknown', locs: [] };
|
|
8
|
+
if (!trimmed.startsWith('<')) {
|
|
9
|
+
if ((contentType ?? '').includes('text/plain') || /^https?:\/\//m.test(trimmed)) {
|
|
10
|
+
return { kind: 'text', locs: trimmed.split(/\r?\n/).map((l) => l.trim()).filter((l) => /^https?:\/\//i.test(l)) };
|
|
11
|
+
}
|
|
12
|
+
return { kind: 'unknown', locs: [] };
|
|
13
|
+
}
|
|
14
|
+
let doc;
|
|
15
|
+
try {
|
|
16
|
+
doc = parser.parse(trimmed);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return { kind: 'unknown', locs: [] };
|
|
20
|
+
}
|
|
21
|
+
const index = doc['sitemapindex'];
|
|
22
|
+
if (index)
|
|
23
|
+
return { kind: 'sitemapindex', locs: locsOf(index['sitemap']) };
|
|
24
|
+
const urlset = doc['urlset'];
|
|
25
|
+
if (urlset)
|
|
26
|
+
return { kind: 'urlset', locs: locsOf(urlset['url']) };
|
|
27
|
+
return { kind: 'unknown', locs: [] };
|
|
28
|
+
}
|
|
29
|
+
function locsOf(entries) {
|
|
30
|
+
if (!Array.isArray(entries))
|
|
31
|
+
return [];
|
|
32
|
+
const out = [];
|
|
33
|
+
for (const e of entries) {
|
|
34
|
+
const loc = e && typeof e === 'object' ? e['loc'] : undefined;
|
|
35
|
+
if (typeof loc === 'string' && loc.trim())
|
|
36
|
+
out.push(loc.trim());
|
|
37
|
+
else if (typeof loc === 'number')
|
|
38
|
+
out.push(String(loc));
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
/** Fetch sitemap candidates (following sitemap indexes) and collect page URLs. */
|
|
43
|
+
export async function loadSitemaps(candidates, options = {}, limits = {}) {
|
|
44
|
+
const maxSitemaps = limits.maxSitemaps ?? 25;
|
|
45
|
+
const maxUrls = limits.maxUrls ?? 5000;
|
|
46
|
+
const info = { sources: [], urls: [], urlCount: 0, errors: [] };
|
|
47
|
+
const queue = [...new Set(candidates)];
|
|
48
|
+
const seen = new Set();
|
|
49
|
+
const urls = new Set();
|
|
50
|
+
while (queue.length && info.sources.length < maxSitemaps && urls.size < maxUrls) {
|
|
51
|
+
const url = queue.shift();
|
|
52
|
+
if (seen.has(url))
|
|
53
|
+
continue;
|
|
54
|
+
seen.add(url);
|
|
55
|
+
if (url.endsWith('.gz')) {
|
|
56
|
+
info.errors.push(`${url}: gzip sitemaps are not supported yet`);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const res = await fetchPage(url, { ...options, timeoutMs: Math.min(options.timeoutMs ?? 15_000, 20_000), maxBodyBytes: 15_000_000 });
|
|
60
|
+
if (res.error) {
|
|
61
|
+
info.errors.push(`${url}: ${res.error}`);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (res.status !== 200 || typeof res.body !== 'string') {
|
|
65
|
+
// A missing default /sitemap.xml is not an error; a declared one is.
|
|
66
|
+
if (res.status !== 404 || candidates.indexOf(url) === -1 || candidates.length > 1) {
|
|
67
|
+
if (res.status !== 404)
|
|
68
|
+
info.errors.push(`${url}: HTTP ${res.status}`);
|
|
69
|
+
}
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const parsed = parseSitemap(res.body, res.contentType);
|
|
73
|
+
if (parsed.kind === 'unknown') {
|
|
74
|
+
if (/<html/i.test(res.body.slice(0, 1000))) {
|
|
75
|
+
if (res.status === 200 && candidates.includes(url) && candidates.length === 1)
|
|
76
|
+
continue; // SPA fallback page for /sitemap.xml
|
|
77
|
+
info.errors.push(`${url}: returned an HTML page, not XML`);
|
|
78
|
+
}
|
|
79
|
+
else
|
|
80
|
+
info.errors.push(`${url}: could not parse as a sitemap`);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
info.sources.push(url);
|
|
84
|
+
if (parsed.kind === 'sitemapindex')
|
|
85
|
+
queue.push(...parsed.locs);
|
|
86
|
+
else
|
|
87
|
+
for (const loc of parsed.locs)
|
|
88
|
+
if (urls.size < maxUrls)
|
|
89
|
+
urls.add(loc);
|
|
90
|
+
}
|
|
91
|
+
info.urls = [...urls];
|
|
92
|
+
info.urlCount = urls.size;
|
|
93
|
+
return info;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=sitemap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sitemap.js","sourceRoot":"","sources":["../../src/core/sitemap.ts"],"names":[],"mappings":"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"}
|
|
@@ -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 function scanDirectory(dir: string, options: DirectoryOptions): Promise<CrawlResult>;
|