@rivi-software/agentic-seo-check 0.1.0 → 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 +1 -1
- package/README.md +65 -15
- package/dist/brand.d.ts +11 -0
- package/dist/brand.js +22 -0
- package/dist/brand.js.map +1 -0
- package/dist/cli.js +125 -80
- package/dist/cli.js.map +1 -1
- package/dist/core/audit.d.ts +16 -16
- package/dist/core/audit.js +363 -248
- package/dist/core/audit.js.map +1 -1
- package/dist/core/concurrency.d.ts +4 -4
- package/dist/core/concurrency.js +33 -23
- package/dist/core/concurrency.js.map +1 -1
- package/dist/core/crawl.d.ts +5 -5
- package/dist/core/crawl.js +250 -121
- package/dist/core/crawl.js.map +1 -1
- package/dist/core/fetch.d.ts +5 -5
- package/dist/core/fetch.js +151 -100
- package/dist/core/fetch.js.map +1 -1
- 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 +2 -2
- package/dist/core/html/extract.js +168 -111
- package/dist/core/html/extract.js.map +1 -1
- package/dist/core/html/structured-data.d.ts +16 -6
- package/dist/core/html/structured-data.js +180 -145
- package/dist/core/html/structured-data.js.map +1 -1
- package/dist/core/html/text.d.ts +14 -14
- package/dist/core/html/text.js +110 -107
- package/dist/core/html/text.js.map +1 -1
- package/dist/core/lighthouse.d.ts +26 -25
- package/dist/core/lighthouse.js +181 -105
- package/dist/core/lighthouse.js.map +1 -1
- package/dist/core/performance-rules.d.ts +2 -2
- package/dist/core/performance-rules.js +105 -33
- package/dist/core/performance-rules.js.map +1 -1
- package/dist/core/render-rules.d.ts +5 -4
- package/dist/core/render-rules.js +129 -62
- package/dist/core/render-rules.js.map +1 -1
- package/dist/core/render.d.ts +2 -2
- package/dist/core/render.js +29 -23
- package/dist/core/render.js.map +1 -1
- package/dist/core/report/markdown.d.ts +2 -2
- package/dist/core/report/markdown.js +110 -131
- package/dist/core/report/markdown.js.map +1 -1
- package/dist/core/robots.d.ts +5 -5
- package/dist/core/robots.js +31 -16
- package/dist/core/robots.js.map +1 -1
- package/dist/core/rules/canonical.d.ts +1 -1
- package/dist/core/rules/canonical.js +93 -57
- package/dist/core/rules/canonical.js.map +1 -1
- package/dist/core/rules/content.js +40 -26
- package/dist/core/rules/content.js.map +1 -1
- package/dist/core/rules/document.js +64 -41
- package/dist/core/rules/document.js.map +1 -1
- package/dist/core/rules/headings.js +58 -38
- package/dist/core/rules/headings.js.map +1 -1
- package/dist/core/rules/hreflang.js +70 -38
- package/dist/core/rules/hreflang.js.map +1 -1
- package/dist/core/rules/images.js +44 -26
- package/dist/core/rules/images.js.map +1 -1
- package/dist/core/rules/index.d.ts +6 -6
- package/dist/core/rules/index.js +36 -35
- package/dist/core/rules/index.js.map +1 -1
- package/dist/core/rules/indexing.d.ts +2 -2
- package/dist/core/rules/indexing.js +48 -26
- package/dist/core/rules/indexing.js.map +1 -1
- package/dist/core/rules/keyword.js +84 -47
- package/dist/core/rules/keyword.js.map +1 -1
- package/dist/core/rules/links.js +72 -37
- package/dist/core/rules/links.js.map +1 -1
- package/dist/core/rules/meta.js +55 -34
- package/dist/core/rules/meta.js.map +1 -1
- package/dist/core/rules/response.js +75 -41
- package/dist/core/rules/response.js.map +1 -1
- package/dist/core/rules/rule.d.ts +6 -6
- package/dist/core/rules/rule.js +36 -36
- package/dist/core/rules/rule.js.map +1 -1
- package/dist/core/rules/social.js +86 -45
- package/dist/core/rules/social.js.map +1 -1
- package/dist/core/rules/structured-data.d.ts +6 -4
- package/dist/core/rules/structured-data.js +314 -102
- package/dist/core/rules/structured-data.js.map +1 -1
- package/dist/core/rules/title.js +48 -33
- package/dist/core/rules/title.js.map +1 -1
- package/dist/core/score.d.ts +3 -3
- package/dist/core/score.js +11 -9
- package/dist/core/score.js.map +1 -1
- package/dist/core/site-rules.d.ts +3 -3
- package/dist/core/site-rules.js +409 -198
- package/dist/core/site-rules.js.map +1 -1
- package/dist/core/sitemap.d.ts +5 -5
- package/dist/core/sitemap.js +109 -64
- package/dist/core/sitemap.js.map +1 -1
- package/dist/core/static.d.ts +2 -2
- package/dist/core/static.js +209 -112
- package/dist/core/static.js.map +1 -1
- package/dist/core/summary.d.ts +2 -2
- package/dist/core/summary.js +11 -6
- package/dist/core/summary.js.map +1 -1
- package/dist/core/types.d.ts +14 -14
- package/dist/core/types.js +15 -8
- package/dist/core/types.js.map +1 -1
- package/dist/core/url.d.ts +11 -11
- package/dist/core/url.js +122 -70
- package/dist/core/url.js.map +1 -1
- package/dist/index.d.ts +11 -11
- package/dist/index.js +9 -9
- package/dist/index.js.map +1 -1
- package/dist/mcp-bin.js +7 -4
- package/dist/mcp-bin.js.map +1 -1
- package/dist/mcp.d.ts +3 -3
- package/dist/mcp.js +312 -122
- package/dist/mcp.js.map +1 -1
- package/dist/version.d.ts +0 -1
- package/dist/version.js +4 -2
- package/dist/version.js.map +1 -1
- package/package.json +27 -9
|
@@ -1,48 +1,74 @@
|
|
|
1
|
-
import { includesKeyword, keywordOccurrences, normalizeForComparison, words } from
|
|
2
|
-
import { defineRule } from
|
|
1
|
+
import { includesKeyword, keywordOccurrences, normalizeForComparison, words, } from "../html/text.js";
|
|
2
|
+
import { defineRule } from "./rule.js";
|
|
3
3
|
export const keywordRules = [
|
|
4
4
|
defineRule({
|
|
5
|
-
id:
|
|
6
|
-
category:
|
|
7
|
-
severity:
|
|
8
|
-
description:
|
|
9
|
-
recommendation:
|
|
10
|
-
requires:
|
|
11
|
-
}, ({ page, keyword }, make) =>
|
|
5
|
+
id: "keyword.not-in-title",
|
|
6
|
+
category: "keyword",
|
|
7
|
+
severity: "warning",
|
|
8
|
+
description: "The target keyword does not appear in the title.",
|
|
9
|
+
recommendation: "Include the target keyword (or a close variant) near the start of the title.",
|
|
10
|
+
requires: "keyword",
|
|
11
|
+
}, ({ page, keyword }, make) => keyword && !includesKeyword(page.title, keyword)
|
|
12
|
+
? make(`Title does not contain "${keyword}".`, {
|
|
13
|
+
selector: "head > title",
|
|
14
|
+
evidence: { title: page.title, keyword },
|
|
15
|
+
})
|
|
16
|
+
: null),
|
|
12
17
|
defineRule({
|
|
13
|
-
id:
|
|
14
|
-
category:
|
|
15
|
-
severity:
|
|
16
|
-
description:
|
|
17
|
-
recommendation:
|
|
18
|
-
requires:
|
|
19
|
-
}, ({ page, keyword }, make) =>
|
|
18
|
+
id: "keyword.not-in-h1",
|
|
19
|
+
category: "keyword",
|
|
20
|
+
severity: "warning",
|
|
21
|
+
description: "The target keyword does not appear in any H1.",
|
|
22
|
+
recommendation: "Use the target keyword in the H1 so the main heading matches the search intent.",
|
|
23
|
+
requires: "keyword",
|
|
24
|
+
}, ({ page, keyword }, make) => keyword &&
|
|
25
|
+
page.h1s.length > 0 &&
|
|
26
|
+
!page.h1s.some((h) => includesKeyword(h, keyword))
|
|
27
|
+
? make(`No <h1> contains "${keyword}".`, {
|
|
28
|
+
selector: "h1",
|
|
29
|
+
evidence: { h1s: page.h1s, keyword },
|
|
30
|
+
})
|
|
31
|
+
: null),
|
|
20
32
|
defineRule({
|
|
21
|
-
id:
|
|
22
|
-
category:
|
|
23
|
-
severity:
|
|
24
|
-
description:
|
|
25
|
-
recommendation:
|
|
26
|
-
requires:
|
|
27
|
-
}, ({ page, keyword }, make) =>
|
|
33
|
+
id: "keyword.not-in-description",
|
|
34
|
+
category: "keyword",
|
|
35
|
+
severity: "info",
|
|
36
|
+
description: "The target keyword does not appear in the meta description.",
|
|
37
|
+
recommendation: "Mention the keyword in the meta description; matching terms are bolded in search snippets.",
|
|
38
|
+
requires: "keyword",
|
|
39
|
+
}, ({ page, keyword }, make) => keyword &&
|
|
40
|
+
page.metaDescription &&
|
|
41
|
+
!includesKeyword(page.metaDescription, keyword)
|
|
42
|
+
? make(`Meta description does not contain "${keyword}".`, {
|
|
43
|
+
selector: 'head > meta[name="description"]',
|
|
44
|
+
evidence: { keyword },
|
|
45
|
+
})
|
|
46
|
+
: null),
|
|
28
47
|
defineRule({
|
|
29
|
-
id:
|
|
30
|
-
category:
|
|
31
|
-
severity:
|
|
32
|
-
description:
|
|
33
|
-
recommendation:
|
|
34
|
-
requires:
|
|
35
|
-
}, ({ page, keyword }, make) =>
|
|
48
|
+
id: "keyword.not-in-body",
|
|
49
|
+
category: "keyword",
|
|
50
|
+
severity: "warning",
|
|
51
|
+
description: "The target keyword does not appear in the visible body text.",
|
|
52
|
+
recommendation: "Use the keyword naturally in the body copy, ideally in the first paragraph.",
|
|
53
|
+
requires: "keyword",
|
|
54
|
+
}, ({ page, keyword }, make) => keyword &&
|
|
55
|
+
page.wordCount > 0 &&
|
|
56
|
+
keywordOccurrences(page.text, keyword) === 0
|
|
57
|
+
? make(`Body text does not contain "${keyword}".`, {
|
|
58
|
+
evidence: { keyword, wordCount: page.wordCount },
|
|
59
|
+
})
|
|
60
|
+
: null),
|
|
36
61
|
defineRule({
|
|
37
|
-
id:
|
|
38
|
-
category:
|
|
39
|
-
severity:
|
|
40
|
-
description:
|
|
41
|
-
recommendation:
|
|
42
|
-
requires:
|
|
62
|
+
id: "keyword.not-in-url",
|
|
63
|
+
category: "keyword",
|
|
64
|
+
severity: "info",
|
|
65
|
+
description: "The target keyword does not appear in the URL path.",
|
|
66
|
+
recommendation: "Where the URL is still changeable, use a short slug containing the keyword (hyphen-separated).",
|
|
67
|
+
requires: "keyword",
|
|
43
68
|
}, ({ page, keyword, source }, make) => {
|
|
44
|
-
if (!keyword || source ===
|
|
69
|
+
if (!keyword || source === "html") {
|
|
45
70
|
return null;
|
|
71
|
+
}
|
|
46
72
|
let pathname = page.url;
|
|
47
73
|
try {
|
|
48
74
|
pathname = decodeURIComponent(new URL(page.url).pathname);
|
|
@@ -50,24 +76,35 @@ export const keywordRules = [
|
|
|
50
76
|
catch {
|
|
51
77
|
/* keep */
|
|
52
78
|
}
|
|
53
|
-
const slug = pathname.
|
|
54
|
-
return includesKeyword(slug, keyword)
|
|
79
|
+
const slug = pathname.replaceAll(/[-_/.+]+/gu, " ");
|
|
80
|
+
return includesKeyword(slug, keyword)
|
|
81
|
+
? null
|
|
82
|
+
: make(`URL path does not contain "${keyword}".`, {
|
|
83
|
+
evidence: { path: pathname, keyword },
|
|
84
|
+
});
|
|
55
85
|
}),
|
|
56
86
|
defineRule({
|
|
57
|
-
id:
|
|
58
|
-
category:
|
|
59
|
-
severity:
|
|
60
|
-
description:
|
|
61
|
-
recommendation:
|
|
62
|
-
requires:
|
|
87
|
+
id: "keyword.density-high",
|
|
88
|
+
category: "keyword",
|
|
89
|
+
severity: "info",
|
|
90
|
+
description: "The target keyword makes up an unusually large share of the body text (default > 3%).",
|
|
91
|
+
recommendation: "Reduce repetition; write for readers and use synonyms. Keyword stuffing can trigger spam filters.",
|
|
92
|
+
requires: "keyword",
|
|
63
93
|
}, ({ page, keyword, thresholds }, make) => {
|
|
64
|
-
if (!keyword || page.wordCount < 50)
|
|
94
|
+
if (!keyword || page.wordCount < 50) {
|
|
65
95
|
return null;
|
|
96
|
+
}
|
|
66
97
|
const occurrences = keywordOccurrences(page.text, keyword);
|
|
67
98
|
const kwWords = words(normalizeForComparison(keyword)).length || 1;
|
|
68
99
|
const density = ((occurrences * kwWords) / page.wordCount) * 100;
|
|
69
100
|
return density > thresholds.maxKeywordDensityPercent
|
|
70
|
-
? make(`Keyword density is ${density.toFixed(1)}% (${occurrences} occurrences in ${page.wordCount} words).`, {
|
|
101
|
+
? make(`Keyword density is ${density.toFixed(1)}% (${occurrences} occurrences in ${page.wordCount} words).`, {
|
|
102
|
+
evidence: {
|
|
103
|
+
occurrences,
|
|
104
|
+
wordCount: page.wordCount,
|
|
105
|
+
densityPercent: Number(density.toFixed(2)),
|
|
106
|
+
},
|
|
107
|
+
})
|
|
71
108
|
: null;
|
|
72
109
|
}),
|
|
73
110
|
];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keyword.js","sourceRoot":"","sources":["../../../src/core/rules/keyword.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"keyword.js","sourceRoot":"","sources":["../../../src/core/rules/keyword.ts"],"names":[],"mappings":"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"}
|
package/dist/core/rules/links.js
CHANGED
|
@@ -1,72 +1,107 @@
|
|
|
1
|
-
import { isGenericAnchor } from
|
|
2
|
-
import { cap, defineRule, GOOGLE_DOCS } from
|
|
1
|
+
import { isGenericAnchor } from "../html/text.js";
|
|
2
|
+
import { cap, defineRule, GOOGLE_DOCS } from "./rule.js";
|
|
3
3
|
export const linkRules = [
|
|
4
4
|
defineRule({
|
|
5
|
-
id:
|
|
6
|
-
category:
|
|
7
|
-
severity:
|
|
8
|
-
description:
|
|
9
|
-
recommendation:
|
|
5
|
+
id: "links.no-internal",
|
|
6
|
+
category: "links",
|
|
7
|
+
severity: "warning",
|
|
8
|
+
description: "The page has no internal links, so crawlers cannot reach other pages from it and it passes no authority onward.",
|
|
9
|
+
recommendation: "Link to related pages and the main navigation with plain <a href> elements.",
|
|
10
10
|
docs: GOOGLE_DOCS.links,
|
|
11
|
-
}, ({ page }, make) =>
|
|
11
|
+
}, ({ page }, make) => page.links.some((l) => l.internal)
|
|
12
|
+
? null
|
|
13
|
+
: make("Page contains no internal links.", {
|
|
14
|
+
evidence: { totalLinks: page.links.length },
|
|
15
|
+
})),
|
|
12
16
|
defineRule({
|
|
13
|
-
id:
|
|
14
|
-
category:
|
|
15
|
-
severity:
|
|
16
|
-
description:
|
|
17
|
-
recommendation:
|
|
17
|
+
id: "links.empty-anchor",
|
|
18
|
+
category: "links",
|
|
19
|
+
severity: "warning",
|
|
20
|
+
description: "Links without any accessible name (no text, aria-label, title or image alt).",
|
|
21
|
+
recommendation: "Add descriptive link text, or aria-label for icon links. Empty links tell crawlers nothing about the target page.",
|
|
18
22
|
docs: GOOGLE_DOCS.links,
|
|
19
23
|
}, ({ page }, make) => {
|
|
20
24
|
const empty = page.links.filter((l) => l.url && !l.hasAccessibleName);
|
|
21
|
-
return empty.length
|
|
25
|
+
return empty.length
|
|
26
|
+
? make(`${empty.length} link(s) have no accessible text.`, {
|
|
27
|
+
evidence: {
|
|
28
|
+
count: empty.length,
|
|
29
|
+
examples: cap(empty.map((l) => l.href)),
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
: null;
|
|
22
33
|
}),
|
|
23
34
|
defineRule({
|
|
24
|
-
id:
|
|
25
|
-
category:
|
|
26
|
-
severity:
|
|
35
|
+
id: "links.generic-anchor-text",
|
|
36
|
+
category: "links",
|
|
37
|
+
severity: "info",
|
|
27
38
|
description: 'Links whose text is generic ("click here", "read more", "learn more").',
|
|
28
39
|
recommendation: 'Use anchor text that describes the destination, e.g. "pricing plans" instead of "click here".',
|
|
29
40
|
docs: GOOGLE_DOCS.links,
|
|
30
41
|
}, ({ page }, make) => {
|
|
31
42
|
const generic = page.links.filter((l) => l.url && l.text && isGenericAnchor(l.text));
|
|
32
43
|
return generic.length
|
|
33
|
-
? make(`${generic.length} link(s) use generic anchor text.`, {
|
|
44
|
+
? make(`${generic.length} link(s) use generic anchor text.`, {
|
|
45
|
+
evidence: {
|
|
46
|
+
count: generic.length,
|
|
47
|
+
examples: cap(generic.map((l) => ({ text: l.text, href: l.href }))),
|
|
48
|
+
},
|
|
49
|
+
})
|
|
34
50
|
: null;
|
|
35
51
|
}),
|
|
36
52
|
defineRule({
|
|
37
|
-
id:
|
|
38
|
-
category:
|
|
39
|
-
severity:
|
|
53
|
+
id: "links.uncrawlable-href",
|
|
54
|
+
category: "links",
|
|
55
|
+
severity: "info",
|
|
40
56
|
description: 'Anchors whose href is "#", empty, or a javascript: URL. Crawlers cannot follow them.',
|
|
41
|
-
recommendation:
|
|
57
|
+
recommendation: "Use <button> for actions. For navigation, give the anchor a real URL even when JavaScript intercepts the click.",
|
|
42
58
|
docs: GOOGLE_DOCS.links,
|
|
43
59
|
}, ({ page }, make) => {
|
|
44
|
-
const bad = page.links.filter((l) => l.href ===
|
|
45
|
-
return bad.length
|
|
60
|
+
const bad = page.links.filter((l) => l.href === "" || l.href === "#" || /^javascript:/iu.test(l.href));
|
|
61
|
+
return bad.length
|
|
62
|
+
? make(`${bad.length} anchor(s) have an uncrawlable href.`, {
|
|
63
|
+
evidence: {
|
|
64
|
+
count: bad.length,
|
|
65
|
+
examples: cap(bad.map((l) => ({ href: l.href, text: l.text }))),
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
: null;
|
|
46
69
|
}),
|
|
47
70
|
defineRule({
|
|
48
|
-
id:
|
|
49
|
-
category:
|
|
50
|
-
severity:
|
|
51
|
-
description:
|
|
52
|
-
recommendation:
|
|
71
|
+
id: "links.insecure-internal",
|
|
72
|
+
category: "links",
|
|
73
|
+
severity: "warning",
|
|
74
|
+
description: "Internal links use http:// while the page is served over https://.",
|
|
75
|
+
recommendation: "Update internal links to https:// (or use relative URLs) to avoid an extra redirect on every click and crawl.",
|
|
53
76
|
docs: GOOGLE_DOCS.redirects,
|
|
54
77
|
}, ({ page, source }, make) => {
|
|
55
|
-
if (source !==
|
|
78
|
+
if (source !== "url" || !page.url.startsWith("https://")) {
|
|
56
79
|
return null;
|
|
57
|
-
|
|
58
|
-
|
|
80
|
+
}
|
|
81
|
+
const insecure = page.links.filter((l) => l.internal && l.url?.startsWith("http://"));
|
|
82
|
+
return insecure.length
|
|
83
|
+
? make(`${insecure.length} internal link(s) point to http:// URLs.`, {
|
|
84
|
+
evidence: {
|
|
85
|
+
count: insecure.length,
|
|
86
|
+
examples: cap(insecure.map((l) => l.url)),
|
|
87
|
+
},
|
|
88
|
+
})
|
|
89
|
+
: null;
|
|
59
90
|
}),
|
|
60
91
|
defineRule({
|
|
61
|
-
id:
|
|
62
|
-
category:
|
|
63
|
-
severity:
|
|
92
|
+
id: "links.nofollow-internal",
|
|
93
|
+
category: "links",
|
|
94
|
+
severity: "info",
|
|
64
95
|
description: 'Internal links carry rel="nofollow".',
|
|
65
|
-
recommendation:
|
|
96
|
+
recommendation: "Remove nofollow from internal links unless the target is intentionally kept out of the index; nofollow on internal links wastes crawl signals.",
|
|
66
97
|
docs: GOOGLE_DOCS.links,
|
|
67
98
|
}, ({ page }, make) => {
|
|
68
99
|
const nf = page.links.filter((l) => l.internal && l.nofollow);
|
|
69
|
-
return nf.length
|
|
100
|
+
return nf.length
|
|
101
|
+
? make(`${nf.length} internal link(s) are rel="nofollow".`, {
|
|
102
|
+
evidence: { count: nf.length, examples: cap(nf.map((l) => l.url)) },
|
|
103
|
+
})
|
|
104
|
+
: null;
|
|
70
105
|
}),
|
|
71
106
|
];
|
|
72
107
|
//# sourceMappingURL=links.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"links.js","sourceRoot":"","sources":["../../../src/core/rules/links.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAEzD,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,UAAU,CACR;QACE,EAAE,EAAE,mBAAmB;QACvB,QAAQ,EAAE,OAAO;QACjB,QAAQ,EAAE,SAAS;QACnB,WAAW,
|
|
1
|
+
{"version":3,"file":"links.js","sourceRoot":"","sources":["../../../src/core/rules/links.ts"],"names":[],"mappings":"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"}
|
package/dist/core/rules/meta.js
CHANGED
|
@@ -1,60 +1,81 @@
|
|
|
1
|
-
import { normalizeForComparison } from
|
|
2
|
-
import { defineRule, GOOGLE_DOCS } from
|
|
1
|
+
import { normalizeForComparison } from "../html/text.js";
|
|
2
|
+
import { defineRule, GOOGLE_DOCS } from "./rule.js";
|
|
3
3
|
const SELECTOR = 'head > meta[name="description"]';
|
|
4
4
|
export const metaRules = [
|
|
5
5
|
defineRule({
|
|
6
|
-
id:
|
|
7
|
-
category:
|
|
8
|
-
severity:
|
|
6
|
+
id: "meta-description.missing",
|
|
7
|
+
category: "meta",
|
|
8
|
+
severity: "warning",
|
|
9
9
|
description: 'No <meta name="description"> is present or it is empty.',
|
|
10
|
-
recommendation:
|
|
10
|
+
recommendation: "Add a unique meta description (about 70-160 characters) that summarises the page and invites the click. Search engines may show it as the snippet.",
|
|
11
11
|
docs: GOOGLE_DOCS.snippets,
|
|
12
|
-
}, ({ page }, make) =>
|
|
12
|
+
}, ({ page }, make) => page.metaDescription
|
|
13
|
+
? null
|
|
14
|
+
: make("Page has no meta description.", { selector: SELECTOR })),
|
|
13
15
|
defineRule({
|
|
14
|
-
id:
|
|
15
|
-
category:
|
|
16
|
-
severity:
|
|
17
|
-
description:
|
|
18
|
-
recommendation:
|
|
16
|
+
id: "meta-description.multiple",
|
|
17
|
+
category: "meta",
|
|
18
|
+
severity: "warning",
|
|
19
|
+
description: "More than one meta description is present.",
|
|
20
|
+
recommendation: "Keep a single meta description; remove or merge the others.",
|
|
19
21
|
docs: GOOGLE_DOCS.snippets,
|
|
20
22
|
}, ({ page }, make) => page.metaDescriptions.length > 1
|
|
21
|
-
? make(`Page has ${page.metaDescriptions.length} meta description tags.`, {
|
|
23
|
+
? make(`Page has ${page.metaDescriptions.length} meta description tags.`, {
|
|
24
|
+
selector: SELECTOR,
|
|
25
|
+
evidence: { descriptions: page.metaDescriptions },
|
|
26
|
+
})
|
|
22
27
|
: null),
|
|
23
28
|
defineRule({
|
|
24
|
-
id:
|
|
25
|
-
category:
|
|
26
|
-
severity:
|
|
27
|
-
description:
|
|
28
|
-
recommendation:
|
|
29
|
+
id: "meta-description.too-long",
|
|
30
|
+
category: "meta",
|
|
31
|
+
severity: "warning",
|
|
32
|
+
description: "Meta description exceeds the configured maximum (default 160 characters) and will be truncated.",
|
|
33
|
+
recommendation: "Trim the description to about 160 characters, front-loading the key message.",
|
|
29
34
|
docs: GOOGLE_DOCS.snippets,
|
|
30
|
-
}, ({ page, thresholds }, make) => page.metaDescription &&
|
|
35
|
+
}, ({ page, thresholds }, make) => page.metaDescription &&
|
|
36
|
+
page.metaDescription.length > thresholds.descriptionMaxChars
|
|
31
37
|
? make(`Meta description is ${page.metaDescription.length} characters (max ${thresholds.descriptionMaxChars}).`, {
|
|
32
38
|
selector: SELECTOR,
|
|
33
|
-
evidence: {
|
|
39
|
+
evidence: {
|
|
40
|
+
description: page.metaDescription,
|
|
41
|
+
length: page.metaDescription.length,
|
|
42
|
+
max: thresholds.descriptionMaxChars,
|
|
43
|
+
},
|
|
34
44
|
})
|
|
35
45
|
: null),
|
|
36
46
|
defineRule({
|
|
37
|
-
id:
|
|
38
|
-
category:
|
|
39
|
-
severity:
|
|
40
|
-
description:
|
|
41
|
-
recommendation:
|
|
47
|
+
id: "meta-description.too-short",
|
|
48
|
+
category: "meta",
|
|
49
|
+
severity: "info",
|
|
50
|
+
description: "Meta description is shorter than the configured minimum (default 70 characters).",
|
|
51
|
+
recommendation: "Expand the description so it summarises the page content and its value to the visitor.",
|
|
42
52
|
docs: GOOGLE_DOCS.snippets,
|
|
43
|
-
}, ({ page, thresholds }, make) => page.metaDescription &&
|
|
53
|
+
}, ({ page, thresholds }, make) => page.metaDescription &&
|
|
54
|
+
page.metaDescription.length < thresholds.descriptionMinChars
|
|
44
55
|
? make(`Meta description is only ${page.metaDescription.length} characters (recommended at least ${thresholds.descriptionMinChars}).`, {
|
|
45
56
|
selector: SELECTOR,
|
|
46
|
-
evidence: {
|
|
57
|
+
evidence: {
|
|
58
|
+
description: page.metaDescription,
|
|
59
|
+
length: page.metaDescription.length,
|
|
60
|
+
min: thresholds.descriptionMinChars,
|
|
61
|
+
},
|
|
47
62
|
})
|
|
48
63
|
: null),
|
|
49
64
|
defineRule({
|
|
50
|
-
id:
|
|
51
|
-
category:
|
|
52
|
-
severity:
|
|
53
|
-
description:
|
|
54
|
-
recommendation:
|
|
65
|
+
id: "meta-description.same-as-title",
|
|
66
|
+
category: "meta",
|
|
67
|
+
severity: "info",
|
|
68
|
+
description: "Meta description is identical to the title.",
|
|
69
|
+
recommendation: "Write a description that adds information beyond the title instead of repeating it.",
|
|
55
70
|
docs: GOOGLE_DOCS.snippets,
|
|
56
|
-
}, ({ page }, make) => page.metaDescription &&
|
|
57
|
-
|
|
71
|
+
}, ({ page }, make) => page.metaDescription &&
|
|
72
|
+
page.title &&
|
|
73
|
+
normalizeForComparison(page.metaDescription) ===
|
|
74
|
+
normalizeForComparison(page.title)
|
|
75
|
+
? make("Meta description repeats the title verbatim.", {
|
|
76
|
+
selector: SELECTOR,
|
|
77
|
+
evidence: { title: page.title },
|
|
78
|
+
})
|
|
58
79
|
: null),
|
|
59
80
|
];
|
|
60
81
|
//# sourceMappingURL=meta.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"meta.js","sourceRoot":"","sources":["../../../src/core/rules/meta.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAEpD,MAAM,QAAQ,GAAG,iCAAiC,CAAC;AAEnD,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,UAAU,CACR;QACE,EAAE,EAAE,0BAA0B;QAC9B,QAAQ,EAAE,MAAM;QAChB,QAAQ,EAAE,SAAS;QACnB,WAAW,EAAE,yDAAyD;QACtE,cAAc,
|
|
1
|
+
{"version":3,"file":"meta.js","sourceRoot":"","sources":["../../../src/core/rules/meta.ts"],"names":[],"mappings":"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"}
|