@rivi-software/agentic-seo-check 0.4.0 → 0.6.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/README.md +49 -7
- package/dist/cli.js +28 -0
- package/dist/cli.js.map +1 -1
- package/dist/core/aeo/audit.d.ts +8 -0
- package/dist/core/aeo/audit.js +101 -17
- package/dist/core/aeo/audit.js.map +1 -1
- package/dist/core/aeo/bots.d.ts +48 -0
- package/dist/core/aeo/bots.js +151 -5
- package/dist/core/aeo/bots.js.map +1 -1
- package/dist/core/aeo/fixes.d.ts +38 -0
- package/dist/core/aeo/fixes.js +111 -0
- package/dist/core/aeo/fixes.js.map +1 -0
- package/dist/core/aeo/site-rules.d.ts +5 -0
- package/dist/core/aeo/site-rules.js +293 -10
- package/dist/core/aeo/site-rules.js.map +1 -1
- package/dist/core/audit.d.ts +8 -0
- package/dist/core/audit.js +144 -25
- package/dist/core/audit.js.map +1 -1
- package/dist/core/crawl.js +43 -4
- package/dist/core/crawl.js.map +1 -1
- package/dist/core/html/answer.d.ts +5 -2
- package/dist/core/html/answer.js +25 -7
- package/dist/core/html/answer.js.map +1 -1
- package/dist/core/html/dom.d.ts +13 -1
- package/dist/core/html/dom.js +30 -2
- package/dist/core/html/dom.js.map +1 -1
- package/dist/core/html/extract.js +122 -30
- package/dist/core/html/extract.js.map +1 -1
- package/dist/core/html/head.js +114 -19
- package/dist/core/html/head.js.map +1 -1
- package/dist/core/html/resources.js +15 -7
- package/dist/core/html/resources.js.map +1 -1
- package/dist/core/html/structured-data.js +21 -5
- package/dist/core/html/structured-data.js.map +1 -1
- package/dist/core/lighthouse.d.ts +5 -6
- package/dist/core/lighthouse.js +297 -7
- package/dist/core/lighthouse.js.map +1 -1
- package/dist/core/performance-fixes.d.ts +25 -0
- package/dist/core/performance-fixes.js +446 -0
- package/dist/core/performance-fixes.js.map +1 -0
- package/dist/core/performance-rules.js +56 -8
- package/dist/core/performance-rules.js.map +1 -1
- package/dist/core/recheck.d.ts +68 -0
- package/dist/core/recheck.js +247 -0
- package/dist/core/recheck.js.map +1 -0
- package/dist/core/render-fixes.d.ts +27 -0
- package/dist/core/render-fixes.js +164 -0
- package/dist/core/render-fixes.js.map +1 -0
- package/dist/core/render-rules.js +60 -21
- package/dist/core/render-rules.js.map +1 -1
- package/dist/core/render.d.ts +9 -1
- package/dist/core/render.js +36 -2
- package/dist/core/render.js.map +1 -1
- package/dist/core/report/markdown.js +68 -1
- package/dist/core/report/markdown.js.map +1 -1
- package/dist/core/robots.d.ts +5 -0
- package/dist/core/robots.js +8 -0
- package/dist/core/robots.js.map +1 -1
- package/dist/core/rules/aeo.js +339 -25
- package/dist/core/rules/aeo.js.map +1 -1
- package/dist/core/rules/canonical.js +107 -11
- package/dist/core/rules/canonical.js.map +1 -1
- package/dist/core/rules/content.js +65 -7
- package/dist/core/rules/content.js.map +1 -1
- package/dist/core/rules/document.js +73 -8
- package/dist/core/rules/document.js.map +1 -1
- package/dist/core/rules/fix.d.ts +55 -0
- package/dist/core/rules/fix.js +159 -0
- package/dist/core/rules/fix.js.map +1 -0
- package/dist/core/rules/head.js +81 -4
- package/dist/core/rules/head.js.map +1 -1
- package/dist/core/rules/headings.js +92 -9
- package/dist/core/rules/headings.js.map +1 -1
- package/dist/core/rules/hreflang.js +79 -9
- package/dist/core/rules/hreflang.js.map +1 -1
- package/dist/core/rules/images.js +49 -0
- package/dist/core/rules/images.js.map +1 -1
- package/dist/core/rules/index.d.ts +11 -0
- package/dist/core/rules/index.js +21 -1
- package/dist/core/rules/index.js.map +1 -1
- package/dist/core/rules/indexing.js +108 -1
- package/dist/core/rules/indexing.js.map +1 -1
- package/dist/core/rules/keyword.js +91 -1
- package/dist/core/rules/keyword.js.map +1 -1
- package/dist/core/rules/links.js +42 -0
- package/dist/core/rules/links.js.map +1 -1
- package/dist/core/rules/loading.js +54 -1
- package/dist/core/rules/loading.js.map +1 -1
- package/dist/core/rules/meta.js +36 -5
- package/dist/core/rules/meta.js.map +1 -1
- package/dist/core/rules/page-fixes.d.ts +39 -0
- package/dist/core/rules/page-fixes.js +107 -0
- package/dist/core/rules/page-fixes.js.map +1 -0
- package/dist/core/rules/quoted-tags.d.ts +23 -0
- package/dist/core/rules/quoted-tags.js +79 -0
- package/dist/core/rules/quoted-tags.js.map +1 -0
- package/dist/core/rules/response.js +71 -7
- package/dist/core/rules/response.js.map +1 -1
- package/dist/core/rules/rule.d.ts +17 -4
- package/dist/core/rules/rule.js +11 -2
- package/dist/core/rules/rule.js.map +1 -1
- package/dist/core/rules/social.js +101 -10
- package/dist/core/rules/social.js.map +1 -1
- package/dist/core/rules/structured-data-fixes.d.ts +33 -0
- package/dist/core/rules/structured-data-fixes.js +469 -0
- package/dist/core/rules/structured-data-fixes.js.map +1 -0
- package/dist/core/rules/structured-data-values.js +242 -31
- package/dist/core/rules/structured-data-values.js.map +1 -1
- package/dist/core/rules/structured-data.js +171 -4
- package/dist/core/rules/structured-data.js.map +1 -1
- package/dist/core/rules/title.js +39 -5
- package/dist/core/rules/title.js.map +1 -1
- package/dist/core/rules/urls.js +166 -0
- package/dist/core/rules/urls.js.map +1 -1
- package/dist/core/site-fixes.d.ts +111 -0
- package/dist/core/site-fixes.js +650 -0
- package/dist/core/site-fixes.js.map +1 -0
- package/dist/core/site-rules.js +474 -53
- package/dist/core/site-rules.js.map +1 -1
- package/dist/core/sitemap.d.ts +16 -0
- package/dist/core/sitemap.js +34 -0
- package/dist/core/sitemap.js.map +1 -1
- package/dist/core/types.d.ts +223 -3
- package/dist/core/types.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +16 -2
- package/dist/mcp.js.map +1 -1
- package/package.json +5 -7
- package/skills/seo-audit/SKILL.md +23 -6
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The edits that fix what a render comparison found: put into the server HTML what only JavaScript
|
|
3
|
+
* adds. `code` quotes the rendered DOM's own tag, so the server sends what the browser ends up
|
|
4
|
+
* with; `current` quotes the server HTML.
|
|
5
|
+
*/
|
|
6
|
+
import { addHtml, escapeText, isCut, rebuildEdit, removeEdit, removeHtml, replaceHtml, slot, withContent, } from "./rules/fix.js";
|
|
7
|
+
const SSR = "JavaScript adds this after load. Render it in the server HTML instead (SSR, SSG or prerendering; in a framework, the route's metadata or head API), so crawlers that do not run JavaScript, and Google before it renders the page, see it.";
|
|
8
|
+
/** The tag of the element whose text is `value`, from parallel lists of texts and tags. */
|
|
9
|
+
const tagFor = (texts, tags, value) => {
|
|
10
|
+
const tag = tags[texts.indexOf(value)];
|
|
11
|
+
return tag && !isCut(tag) ? tag : undefined;
|
|
12
|
+
};
|
|
13
|
+
/** Writes `code` into the server HTML: in place of the empty tag it has, or added to <head>. */
|
|
14
|
+
const intoHead = (empty, code, at, write) => empty
|
|
15
|
+
? replaceHtml(empty, code, { at, write })
|
|
16
|
+
: addHtml("head", code, write);
|
|
17
|
+
export const titleFix = (s, title) => [
|
|
18
|
+
intoHead(s.raw.markup.titles[0], tagFor(s.rendered.titles, s.rendered.markup.titles, title) ??
|
|
19
|
+
`<title>${escapeText(title)}</title>`, "head > title", SSR),
|
|
20
|
+
];
|
|
21
|
+
export const titleChangedFix = (s, serverTitle, finalTitle) => {
|
|
22
|
+
const current = s.raw.markup.titles[s.raw.titles.indexOf(serverTitle)];
|
|
23
|
+
const content = escapeText(finalTitle);
|
|
24
|
+
const write = `JavaScript changes the title to this after load; send it from the server so both agree. If the change is on purpose (an unread count, a live status), keep the server title and leave this finding.`;
|
|
25
|
+
return current
|
|
26
|
+
? [
|
|
27
|
+
replaceHtml(current, withContent(current, content) ?? `<title>${content}</title>`, { at: "head > title", write }),
|
|
28
|
+
]
|
|
29
|
+
: [addHtml("head", `<title>${content}</title>`, write)];
|
|
30
|
+
};
|
|
31
|
+
export const descriptionFix = (s, description) => {
|
|
32
|
+
const rendered = tagFor(s.rendered.metaDescriptions, s.rendered.markup.metaDescriptions, description);
|
|
33
|
+
const code = rendered ??
|
|
34
|
+
`<meta name="description" content="${description.replaceAll('"', """)}">`;
|
|
35
|
+
return [
|
|
36
|
+
intoHead(s.raw.markup.metaDescriptions[0], code, 'head > meta[name="description"]', SSR),
|
|
37
|
+
];
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* The canonical JavaScript sets, in the server HTML: added when the server sends none, in place of
|
|
41
|
+
* the server's when it sends another.
|
|
42
|
+
*/
|
|
43
|
+
export const canonicalFix = (s, serverCanonical, finalCanonical) => {
|
|
44
|
+
const [rendered] = s.rendered.markup.canonicals;
|
|
45
|
+
const code = rendered && !isCut(rendered)
|
|
46
|
+
? rendered
|
|
47
|
+
: `<link rel="canonical" href="${finalCanonical.replaceAll('"', """)}">`;
|
|
48
|
+
const at = 'head > link[rel="canonical"]';
|
|
49
|
+
const [current] = s.raw.markup.canonicals;
|
|
50
|
+
if (serverCanonical === null) {
|
|
51
|
+
return [intoHead(current, code, at, SSR)];
|
|
52
|
+
}
|
|
53
|
+
const write = `JavaScript changes the canonical from ${serverCanonical} to ${finalCanonical}. Check that ${finalCanonical} is this page's own URL, send it from the server, and stop setting it a second time in the browser (a head manager or router doing it again).`;
|
|
54
|
+
return current
|
|
55
|
+
? [replaceHtml(current, code, { at, write })]
|
|
56
|
+
: [addHtml("head", code, write)];
|
|
57
|
+
};
|
|
58
|
+
const NOINDEX = /\bcontent\s*=\s*["']?[^"'>]*\b(?:noindex|none)\b/iu;
|
|
59
|
+
/** noindex that only JavaScript adds: the tag to take out of the component that renders it. */
|
|
60
|
+
export const noindexAddedFix = (s) => {
|
|
61
|
+
const tag = s.rendered.markup.metaRobots.find((t) => NOINDEX.test(t)) ??
|
|
62
|
+
'<meta name="robots" content="noindex">';
|
|
63
|
+
return [
|
|
64
|
+
removeEdit("html", tag, {
|
|
65
|
+
at: 'meta[name="robots"]',
|
|
66
|
+
write: "JavaScript adds this tag after load (often a head manager's default or an auth guard), and Google applies it when it renders the page. Remove it from the component that renders it. If the page must stay out of search, send noindex in the server HTML or an X-Robots-Tag header instead, so the decision does not wait for rendering.",
|
|
67
|
+
}),
|
|
68
|
+
];
|
|
69
|
+
};
|
|
70
|
+
/** noindex the server sends and JavaScript removes: Google keeps the server's, so remove it there. */
|
|
71
|
+
export const noindexRemovedFix = (s) => {
|
|
72
|
+
const write = "The rendered page drops noindex, so the page is meant to be indexed, but Google reads the server's noindex and does not render the page. Remove it from the server. If the page must stay out of search, keep it and stop removing it in JavaScript instead.";
|
|
73
|
+
const tags = s.raw.markup.metaRobots.filter((t) => NOINDEX.test(t));
|
|
74
|
+
if (tags.length) {
|
|
75
|
+
return tags
|
|
76
|
+
.map((t) => removeHtml(t, 'meta[name="robots"]'))
|
|
77
|
+
.map((e) => ({
|
|
78
|
+
...e,
|
|
79
|
+
write,
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
const header = s.response?.headers["x-robots-tag"];
|
|
83
|
+
return [
|
|
84
|
+
removeEdit("http", `X-Robots-Tag: ${header ?? "noindex"}`, {
|
|
85
|
+
at: "X-Robots-Tag",
|
|
86
|
+
write,
|
|
87
|
+
}),
|
|
88
|
+
];
|
|
89
|
+
};
|
|
90
|
+
/** Where the server HTML's content belongs: its empty app root, or <body>. */
|
|
91
|
+
const contentRoot = (s) => s.raw.answer.emptyAppRoot ?? "body";
|
|
92
|
+
export const h1Fix = (s) => {
|
|
93
|
+
const index = s.rendered.headings.findIndex((h) => h.level === 1 && h.text);
|
|
94
|
+
const heading = s.rendered.headings[index];
|
|
95
|
+
const tag = s.rendered.markup.headings[index];
|
|
96
|
+
const code = tag && !isCut(tag) ? tag : `<h1>${escapeText(heading?.text ?? "")}</h1>`;
|
|
97
|
+
return [
|
|
98
|
+
addHtml(contentRoot(s), code, `${SSR} Put it where the page's main content starts.`),
|
|
99
|
+
];
|
|
100
|
+
};
|
|
101
|
+
export const contentFix = (s, words) => {
|
|
102
|
+
const start = s.rendered.text.split(" ").slice(0, 12).join(" ");
|
|
103
|
+
return [
|
|
104
|
+
addHtml(contentRoot(s), slot("the page's main content, as HTML"), `JavaScript adds ${words} words the server HTML does not have${start ? `; the rendered text starts "${start}…"` : ""}. Render this route on the server (SSR, SSG or prerendering) so the HTML carries the text.`),
|
|
105
|
+
];
|
|
106
|
+
};
|
|
107
|
+
/** Internal links only the rendered DOM has, quoted as JavaScript writes them (the first 10). */
|
|
108
|
+
export const linksFix = (s, missing) => {
|
|
109
|
+
const known = new Set(s.raw.links.flatMap((l) => (l.internal && l.url ? [l.url] : [])));
|
|
110
|
+
const seen = new Set();
|
|
111
|
+
const lines = [];
|
|
112
|
+
for (const link of s.rendered.links) {
|
|
113
|
+
if (!link.internal ||
|
|
114
|
+
!link.url ||
|
|
115
|
+
known.has(link.url) ||
|
|
116
|
+
seen.has(link.url) ||
|
|
117
|
+
isCut(link.tag)) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
seen.add(link.url);
|
|
121
|
+
lines.push(`${link.tag}${escapeText(link.text)}</a>`);
|
|
122
|
+
if (lines.length === 10) {
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const write = `${missing} internal links exist only after JavaScript runs${lines.length ? `; the first ${lines.length} are here` : ""}. Render them as plain <a href> in the server HTML (the navigation and the content lists), so crawlers find the pages without running JavaScript.`;
|
|
127
|
+
return [
|
|
128
|
+
addHtml(contentRoot(s), lines.length
|
|
129
|
+
? lines.join("\n")
|
|
130
|
+
: slot("the page's links, as <a href> elements"), write),
|
|
131
|
+
];
|
|
132
|
+
};
|
|
133
|
+
/** JSON-LD longer than this is described with a slot rather than copied into the fix. */
|
|
134
|
+
const MAX_JSON_LD = 5000;
|
|
135
|
+
/** The JSON-LD JavaScript adds, as script elements for the server HTML. */
|
|
136
|
+
export const structuredDataFix = (s) => {
|
|
137
|
+
const scripts = new Set();
|
|
138
|
+
for (const item of s.rendered.structuredData) {
|
|
139
|
+
if (item.source !== "json-ld") {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
// One <script> may hold several items (an array, an @graph): copy it once.
|
|
143
|
+
const json = item.scriptText?.trim() ||
|
|
144
|
+
JSON.stringify(item.data).replaceAll("<", String.raw `\u003c`);
|
|
145
|
+
scripts.add(json);
|
|
146
|
+
}
|
|
147
|
+
const edits = [...scripts]
|
|
148
|
+
.slice(0, 10)
|
|
149
|
+
.map((json) => addHtml("head", json.length > MAX_JSON_LD
|
|
150
|
+
? `<script type="application/ld+json">${slot("the JSON-LD JavaScript adds")}</script>`
|
|
151
|
+
: `<script type="application/ld+json">${json}</script>`, `${SSR} Google reads JSON-LD that JavaScript adds, but other consumers (social previews, other search engines, AI crawlers) do not.`));
|
|
152
|
+
return edits.length
|
|
153
|
+
? edits
|
|
154
|
+
: [
|
|
155
|
+
addHtml(contentRoot(s), slot("the microdata JavaScript adds"), `${SSR} The structured data is microdata on elements JavaScript renders.`),
|
|
156
|
+
];
|
|
157
|
+
};
|
|
158
|
+
const SCRIPT_URL = /https?:\/\/[^\s"'()<>]+?\.m?js\b/u;
|
|
159
|
+
/** The scripts that throw while the page renders, from the messages they log (the first 5). */
|
|
160
|
+
export const consoleErrorFix = (errors) => errors.slice(0, 5).map((error) => {
|
|
161
|
+
const url = SCRIPT_URL.exec(error)?.[0];
|
|
162
|
+
return rebuildEdit(url, "code that does not throw", `Fix the error ${url ? "this script" : "a script of this page"} logs while the page renders (${error}): a script that fails can leave crawlers an incomplete page.`);
|
|
163
|
+
});
|
|
164
|
+
//# sourceMappingURL=render-fixes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-fixes.js","sourceRoot":"","sources":["../../src/core/render-fixes.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { canonicalFix, consoleErrorFix, contentFix, descriptionFix, h1Fix, linksFix, noindexAddedFix, noindexRemovedFix, structuredDataFix, titleChangedFix, titleFix, } from "./render-fixes.js";
|
|
1
2
|
import { robotsDirectives } from "./rules/indexing.js";
|
|
2
3
|
import { finding, GOOGLE_DOCS } from "./rules/rule.js";
|
|
3
4
|
const meta = (id, severity, description, recommendation) => ({
|
|
@@ -42,36 +43,48 @@ export const snapshot = (page, response) => ({
|
|
|
42
43
|
internalLinks: page.links.filter((l) => l.internal).length,
|
|
43
44
|
structuredDataCount: page.structuredData.length,
|
|
44
45
|
});
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
const make = (id, message, extras) => finding(m(id), message, extras);
|
|
47
|
+
/** Title and description: what the head says before and after JavaScript runs. */
|
|
48
|
+
const textFindings = (s) => {
|
|
49
|
+
const { a, b } = s;
|
|
47
50
|
const findings = [];
|
|
48
|
-
const add = (f) => findings.push(f);
|
|
49
51
|
if (!a.title && b.title) {
|
|
50
|
-
|
|
52
|
+
findings.push(make("rendering.title-client-only", `Title "${b.title}" is only present after rendering.`, {
|
|
53
|
+
fix: titleFix(s, b.title),
|
|
51
54
|
selector: "head > title",
|
|
52
55
|
evidence: { raw: a.title, rendered: b.title },
|
|
53
56
|
}));
|
|
54
57
|
}
|
|
55
58
|
else if (a.title && b.title && a.title !== b.title) {
|
|
56
|
-
|
|
59
|
+
findings.push(make("rendering.title-changed", "Title differs between server HTML and rendered DOM.", {
|
|
60
|
+
fix: titleChangedFix(s, a.title, b.title),
|
|
57
61
|
selector: "head > title",
|
|
58
62
|
evidence: { raw: a.title, rendered: b.title },
|
|
59
63
|
}));
|
|
60
64
|
}
|
|
61
65
|
if (!a.metaDescription && b.metaDescription) {
|
|
62
|
-
|
|
66
|
+
findings.push(make("rendering.description-client-only", "Meta description is only present after rendering.", {
|
|
67
|
+
fix: descriptionFix(s, b.metaDescription),
|
|
63
68
|
selector: 'head > meta[name="description"]',
|
|
64
69
|
evidence: { rendered: b.metaDescription },
|
|
65
70
|
}));
|
|
66
71
|
}
|
|
72
|
+
return findings;
|
|
73
|
+
};
|
|
74
|
+
/** Canonical and robots: the signals that decide which URL is indexed, before and after JavaScript. */
|
|
75
|
+
const indexingFindings = (s) => {
|
|
76
|
+
const { a, b } = s;
|
|
77
|
+
const findings = [];
|
|
67
78
|
if (!a.canonical && b.canonical) {
|
|
68
|
-
|
|
79
|
+
findings.push(make("rendering.canonical-client-only", `Canonical ${b.canonical} is only present after rendering.`, {
|
|
80
|
+
fix: canonicalFix(s, null, b.canonical),
|
|
69
81
|
selector: 'head > link[rel="canonical"]',
|
|
70
82
|
evidence: { rendered: b.canonical },
|
|
71
83
|
}));
|
|
72
84
|
}
|
|
73
85
|
else if (a.canonical && b.canonical && a.canonical !== b.canonical) {
|
|
74
|
-
|
|
86
|
+
findings.push(make("rendering.canonical-changed", "Canonical differs between server HTML and rendered DOM.", {
|
|
87
|
+
fix: canonicalFix(s, a.canonical, b.canonical),
|
|
75
88
|
selector: 'head > link[rel="canonical"]',
|
|
76
89
|
evidence: { raw: a.canonical, rendered: b.canonical },
|
|
77
90
|
}));
|
|
@@ -79,46 +92,65 @@ const headFindings = (a, b) => {
|
|
|
79
92
|
const rawNoindex = a.robots.some((d) => d === "noindex" || d === "none");
|
|
80
93
|
const renderedNoindex = b.robots.some((d) => d === "noindex" || d === "none");
|
|
81
94
|
if (!rawNoindex && renderedNoindex) {
|
|
82
|
-
|
|
95
|
+
findings.push(make("rendering.noindex-client-only", "noindex is added by JavaScript.", {
|
|
96
|
+
fix: noindexAddedFix(s),
|
|
83
97
|
selector: 'head > meta[name="robots"]',
|
|
84
98
|
evidence: { raw: a.robots, rendered: b.robots },
|
|
85
99
|
}));
|
|
86
100
|
}
|
|
87
101
|
if (rawNoindex && !renderedNoindex) {
|
|
88
|
-
|
|
102
|
+
findings.push(make("rendering.noindex-removed-by-js", "Server HTML contains noindex; JavaScript removes it.", {
|
|
103
|
+
fix: noindexRemovedFix(s),
|
|
89
104
|
selector: 'head > meta[name="robots"]',
|
|
90
105
|
evidence: { raw: a.robots, rendered: b.robots },
|
|
91
106
|
}));
|
|
92
107
|
}
|
|
93
108
|
return findings;
|
|
94
109
|
};
|
|
95
|
-
/** H1, words
|
|
96
|
-
const
|
|
110
|
+
/** H1, words and links: what only the rendered body has. */
|
|
111
|
+
const contentFindings = (s) => {
|
|
112
|
+
const { a, b } = s;
|
|
97
113
|
const findings = [];
|
|
98
|
-
const add = (f) => findings.push(f);
|
|
99
114
|
if (a.h1Count === 0 && b.h1Count > 0) {
|
|
100
|
-
|
|
115
|
+
findings.push(make("rendering.h1-client-only", "The <h1> is only present after rendering.", {
|
|
116
|
+
fix: h1Fix(s),
|
|
101
117
|
selector: "h1",
|
|
102
118
|
evidence: { rawH1Count: a.h1Count, renderedH1Count: b.h1Count },
|
|
103
119
|
}));
|
|
104
120
|
}
|
|
105
121
|
if (b.wordCount - a.wordCount >= 100 && a.wordCount < b.wordCount * 0.5) {
|
|
106
|
-
|
|
122
|
+
findings.push(make("rendering.content-client-only", `Server HTML has ${a.wordCount} words; rendered DOM has ${b.wordCount}.`, {
|
|
123
|
+
fix: contentFix(s, b.wordCount - a.wordCount),
|
|
124
|
+
evidence: { rawWords: a.wordCount, renderedWords: b.wordCount },
|
|
125
|
+
}));
|
|
107
126
|
}
|
|
108
127
|
if (b.internalLinks - a.internalLinks >= 10 &&
|
|
109
128
|
a.internalLinks < b.internalLinks * 0.5) {
|
|
110
|
-
|
|
129
|
+
findings.push(make("rendering.links-client-only", `Server HTML has ${a.internalLinks} internal links; rendered DOM has ${b.internalLinks}.`, {
|
|
130
|
+
fix: linksFix(s, b.internalLinks - a.internalLinks),
|
|
111
131
|
evidence: {
|
|
112
132
|
rawLinks: a.internalLinks,
|
|
113
133
|
renderedLinks: b.internalLinks,
|
|
114
134
|
},
|
|
115
135
|
}));
|
|
116
136
|
}
|
|
137
|
+
return findings;
|
|
138
|
+
};
|
|
139
|
+
/** Structured data and script errors that only the rendered page has. */
|
|
140
|
+
const scriptFindings = (s, consoleErrors) => {
|
|
141
|
+
const { a, b } = s;
|
|
142
|
+
const findings = [];
|
|
117
143
|
if (a.structuredDataCount === 0 && b.structuredDataCount > 0) {
|
|
118
|
-
|
|
144
|
+
findings.push(make("rendering.structured-data-client-only", `${b.structuredDataCount} structured data item(s) are only present after rendering.`, {
|
|
145
|
+
fix: structuredDataFix(s),
|
|
146
|
+
evidence: { renderedCount: b.structuredDataCount },
|
|
147
|
+
}));
|
|
119
148
|
}
|
|
120
149
|
if (consoleErrors.length) {
|
|
121
|
-
|
|
150
|
+
findings.push(make("rendering.console-errors", `${consoleErrors.length} JavaScript error(s) logged during render.`, {
|
|
151
|
+
fix: consoleErrorFix(consoleErrors),
|
|
152
|
+
evidence: { errors: consoleErrors.slice(0, 5) },
|
|
153
|
+
}));
|
|
122
154
|
}
|
|
123
155
|
return findings;
|
|
124
156
|
};
|
|
@@ -126,10 +158,17 @@ export const compareRender = (raw, rendered, renderedUrl, consoleErrors, rawResp
|
|
|
126
158
|
const a = snapshot(raw, rawResponse);
|
|
127
159
|
const b = snapshot(rendered);
|
|
128
160
|
const url = rawResponse?.finalUrl ?? raw.url;
|
|
161
|
+
const sides = { raw, rendered, response: rawResponse, a, b };
|
|
129
162
|
const findings = [
|
|
130
|
-
...
|
|
131
|
-
...
|
|
132
|
-
|
|
163
|
+
...textFindings(sides),
|
|
164
|
+
...indexingFindings(sides),
|
|
165
|
+
...contentFindings(sides),
|
|
166
|
+
...scriptFindings(sides, consoleErrors),
|
|
167
|
+
];
|
|
168
|
+
// Set in place: a copy would lose the key a finding's id is made from.
|
|
169
|
+
for (const f of findings) {
|
|
170
|
+
f.url = url;
|
|
171
|
+
}
|
|
133
172
|
return {
|
|
134
173
|
comparison: {
|
|
135
174
|
tool: "playwright",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render-rules.js","sourceRoot":"","sources":["../../src/core/render-rules.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"render-rules.js","sourceRoot":"","sources":["../../src/core/render-rules.ts"],"names":[],"mappings":"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"}
|
package/dist/core/render.d.ts
CHANGED
|
@@ -17,6 +17,14 @@ export interface RenderOutput {
|
|
|
17
17
|
finalUrl: string;
|
|
18
18
|
consoleErrors: string[];
|
|
19
19
|
}
|
|
20
|
-
export declare const RENDER_INSTALL_HINT = "
|
|
20
|
+
export declare const RENDER_INSTALL_HINT = "Playwright installs with agentic-seo-check as an optional dependency, so it is missing only where optional dependencies were left out (`--omit=optional`, `--no-optional`): reinstall with them, or add `playwright` next to agentic-seo-check.";
|
|
21
|
+
export declare const BROWSER_INSTALL_HINT = "Install Google Chrome, or download the Chromium build this Playwright expects with `agentic-seo-check install-browser` (`npx @rivi-software/agentic-seo-check install-browser` with npx).";
|
|
22
|
+
/**
|
|
23
|
+
* Downloads the Chromium build the installed Playwright expects, through Playwright's own CLI: a
|
|
24
|
+
* build fetched by `npx playwright install` for another Playwright version does not launch.
|
|
25
|
+
*/
|
|
26
|
+
export declare const installBrowser: (options?: {
|
|
27
|
+
withDeps?: boolean;
|
|
28
|
+
}) => Promise<void>;
|
|
21
29
|
/** Load the page in headless Chromium and return the rendered DOM. */
|
|
22
30
|
export declare const renderPage: (url: string, options?: RenderOptions) => Promise<RenderOutput>;
|
package/dist/core/render.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { once } from "node:events";
|
|
3
|
+
import { promises as fs } from "node:fs";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import path from "node:path";
|
|
1
6
|
import { DEFAULT_USER_AGENT } from "./fetch.js";
|
|
2
7
|
export class RenderUnavailableError extends Error {
|
|
3
8
|
constructor(message) {
|
|
@@ -5,7 +10,36 @@ export class RenderUnavailableError extends Error {
|
|
|
5
10
|
this.name = "RenderUnavailableError";
|
|
6
11
|
}
|
|
7
12
|
}
|
|
8
|
-
export const RENDER_INSTALL_HINT = "
|
|
13
|
+
export const RENDER_INSTALL_HINT = "Playwright installs with agentic-seo-check as an optional dependency, so it is missing only where optional dependencies were left out (`--omit=optional`, `--no-optional`): reinstall with them, or add `playwright` next to agentic-seo-check.";
|
|
14
|
+
export const BROWSER_INSTALL_HINT = "Install Google Chrome, or download the Chromium build this Playwright expects with `agentic-seo-check install-browser` (`npx @rivi-software/agentic-seo-check install-browser` with npx).";
|
|
15
|
+
/**
|
|
16
|
+
* Downloads the Chromium build the installed Playwright expects, through Playwright's own CLI: a
|
|
17
|
+
* build fetched by `npx playwright install` for another Playwright version does not launch.
|
|
18
|
+
*/
|
|
19
|
+
export const installBrowser = async (options = {}) => {
|
|
20
|
+
let manifestPath;
|
|
21
|
+
try {
|
|
22
|
+
manifestPath = createRequire(import.meta.url).resolve("playwright/package.json");
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
throw new RenderUnavailableError(`Playwright is not installed. ${RENDER_INSTALL_HINT}`);
|
|
26
|
+
}
|
|
27
|
+
// SAFETY: the manifest of an installed package; a missing bin fails the spawn below.
|
|
28
|
+
const manifest = JSON.parse(await fs.readFile(manifestPath, "utf-8"));
|
|
29
|
+
const cli = path.join(path.dirname(manifestPath), manifest.bin?.playwright ?? "cli.js");
|
|
30
|
+
const args = [cli, "install", "chromium"];
|
|
31
|
+
if (options.withDeps) {
|
|
32
|
+
args.push("--with-deps");
|
|
33
|
+
}
|
|
34
|
+
// Playwright's progress goes to stderr, like the CLI's own: stdout is for reports.
|
|
35
|
+
const child = spawn(process.execPath, args, {
|
|
36
|
+
stdio: ["inherit", 2, "inherit"],
|
|
37
|
+
});
|
|
38
|
+
await once(child, "close");
|
|
39
|
+
if (child.exitCode !== 0) {
|
|
40
|
+
throw new Error(`playwright install chromium exited with ${child.exitCode ?? child.signalCode}.`);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
9
43
|
const launchSystemBrowser = async (playwright) => {
|
|
10
44
|
for (const channel of ["chrome", "msedge"]) {
|
|
11
45
|
try {
|
|
@@ -36,7 +70,7 @@ export const renderPage = async (url, options = {}) => {
|
|
|
36
70
|
// No downloaded Playwright browser: fall back to an installed Google Chrome / Edge.
|
|
37
71
|
const fallback = await launchSystemBrowser(playwright);
|
|
38
72
|
if (!fallback) {
|
|
39
|
-
throw new RenderUnavailableError(`Could not launch Chromium: ${firstLine(error instanceof Error ? error.message : String(error))}. ${
|
|
73
|
+
throw new RenderUnavailableError(`Could not launch Chromium: ${firstLine(error instanceof Error ? error.message : String(error))}. ${BROWSER_INSTALL_HINT}`);
|
|
40
74
|
}
|
|
41
75
|
browser = fallback;
|
|
42
76
|
}
|
package/dist/core/render.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/core/render.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/core/render.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,11 +8,53 @@ const cell = (value) => {
|
|
|
8
8
|
};
|
|
9
9
|
const ms = (value) => value === null ? "—" : `${Math.round(value)} ms`;
|
|
10
10
|
const countsLine = (c) => `${c.error} error(s), ${c.warning} warning(s), ${c.info} info`;
|
|
11
|
+
/** `text` as inline code, fenced with more backticks than it holds so a page's own backticks cannot end it. */
|
|
12
|
+
const codeSpan = (text) => {
|
|
13
|
+
const longest = Math.max(0, ...[...text.matchAll(/`+/gu)].map((m) => m[0].length));
|
|
14
|
+
const fence = "`".repeat(longest + 1);
|
|
15
|
+
const pad = text.startsWith("`") || text.endsWith("`") ? " " : "";
|
|
16
|
+
return `${fence}${pad}${text}${pad}${fence}`;
|
|
17
|
+
};
|
|
18
|
+
/** A labelled line for one line of code, or a fenced block under the label for several. */
|
|
19
|
+
const codeLines = (label, text, indent) => {
|
|
20
|
+
if (!text.includes("\n")) {
|
|
21
|
+
return [`${indent}- ${label}: ${codeSpan(text)}`];
|
|
22
|
+
}
|
|
23
|
+
const longest = Math.max(2, ...[...text.matchAll(/`+/gu)].map((m) => m[0].length));
|
|
24
|
+
const fence = "`".repeat(longest + 1);
|
|
25
|
+
return [
|
|
26
|
+
`${indent}- ${label}:`,
|
|
27
|
+
"",
|
|
28
|
+
`${indent} ${fence}`,
|
|
29
|
+
...text.split("\n").map((line) => `${indent} ${line}`),
|
|
30
|
+
`${indent} ${fence}`,
|
|
31
|
+
"",
|
|
32
|
+
];
|
|
33
|
+
};
|
|
34
|
+
const editLines = (e) => {
|
|
35
|
+
const where = [e.in, e.url, e.at ? codeSpan(e.at) : undefined]
|
|
36
|
+
.filter(Boolean)
|
|
37
|
+
.join(", ");
|
|
38
|
+
const out = [` - Edit: ${e.action} in ${where}`];
|
|
39
|
+
if (e.current !== undefined) {
|
|
40
|
+
out.push(...codeLines("Current", e.current, " "));
|
|
41
|
+
}
|
|
42
|
+
if (e.code !== undefined) {
|
|
43
|
+
out.push(...codeLines("Code", e.code, " "));
|
|
44
|
+
}
|
|
45
|
+
if (e.write) {
|
|
46
|
+
out.push(` - Write: ${e.write}`);
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
};
|
|
11
50
|
const findingLines = (f) => {
|
|
12
51
|
const out = [
|
|
13
52
|
`- **\`${f.rule}\`** ${f.message}`,
|
|
14
53
|
` - Fix: ${f.recommendation}`,
|
|
15
54
|
];
|
|
55
|
+
for (const e of f.fix ?? []) {
|
|
56
|
+
out.push(...editLines(e));
|
|
57
|
+
}
|
|
16
58
|
if (f.selector) {
|
|
17
59
|
out.push(` - Where: \`${f.selector}\``);
|
|
18
60
|
}
|
|
@@ -21,6 +63,25 @@ const findingLines = (f) => {
|
|
|
21
63
|
}
|
|
22
64
|
return out;
|
|
23
65
|
};
|
|
66
|
+
/** What a re-check found: the findings fixed since the baseline, still open and new. */
|
|
67
|
+
const baselineLines = (b, heading) => {
|
|
68
|
+
const lines = [
|
|
69
|
+
`${heading}# Re-check`,
|
|
70
|
+
"",
|
|
71
|
+
`${b.fixed.length} fixed · ${b.open.length} still open · ${b.new.length} new${b.notChecked ? ` · ${b.notChecked} not checked (their rule or page did not run this time)` : ""}`,
|
|
72
|
+
"",
|
|
73
|
+
];
|
|
74
|
+
for (const f of b.fixed) {
|
|
75
|
+
lines.push(`- Fixed: \`${f.id}\`${f.message ? ` ${f.message}` : ""}${f.url ? ` (${f.url})` : ""}`);
|
|
76
|
+
}
|
|
77
|
+
for (const id of b.new) {
|
|
78
|
+
lines.push(`- New: \`${id}\``);
|
|
79
|
+
}
|
|
80
|
+
if (b.fixed.length || b.new.length) {
|
|
81
|
+
lines.push("");
|
|
82
|
+
}
|
|
83
|
+
return lines;
|
|
84
|
+
};
|
|
24
85
|
const SEVERITY_HEADINGS = {
|
|
25
86
|
error: "Errors",
|
|
26
87
|
warning: "Warnings",
|
|
@@ -66,6 +127,9 @@ const pageMarkdown = (audit, heading = "#") => {
|
|
|
66
127
|
const r = audit.rendering;
|
|
67
128
|
lines.push(`${heading}# Rendered vs server HTML`, "", "| Field | Server HTML | Rendered DOM |", "| --- | --- | --- |", `| Title | ${cell(r.raw.title)} | ${cell(r.rendered.title)} |`, `| Meta description | ${cell(r.raw.metaDescription)} | ${cell(r.rendered.metaDescription)} |`, `| Canonical | ${cell(r.raw.canonical)} | ${cell(r.rendered.canonical)} |`, `| Robots | ${r.raw.robots.join(", ") || "—"} | ${r.rendered.robots.join(", ") || "—"} |`, `| H1 count | ${r.raw.h1Count} | ${r.rendered.h1Count} |`, `| Words | ${r.raw.wordCount} | ${r.rendered.wordCount} |`, `| Internal links | ${r.raw.internalLinks} | ${r.rendered.internalLinks} |`, `| Structured data items | ${r.raw.structuredDataCount} | ${r.rendered.structuredDataCount} |`, "");
|
|
68
129
|
}
|
|
130
|
+
if (audit.baseline) {
|
|
131
|
+
lines.push(...baselineLines(audit.baseline, heading));
|
|
132
|
+
}
|
|
69
133
|
lines.push(...findingsSection(audit.findings, heading));
|
|
70
134
|
return lines.join("\n");
|
|
71
135
|
};
|
|
@@ -90,6 +154,9 @@ const siteMarkdown = (audit) => {
|
|
|
90
154
|
`| Sitemaps | ${audit.sitemaps.sources.length ? `${audit.sitemaps.sources.length} file(s), ${audit.sitemaps.urlCount} URLs` : "none found"}${audit.sitemaps.errors.length ? `, ${audit.sitemaps.errors.length} error(s)` : ""} |`,
|
|
91
155
|
"",
|
|
92
156
|
];
|
|
157
|
+
if (audit.baseline) {
|
|
158
|
+
lines.push(...baselineLines(audit.baseline, "#"));
|
|
159
|
+
}
|
|
93
160
|
if (audit.siteFindings.length) {
|
|
94
161
|
lines.push("## Site-wide findings", "");
|
|
95
162
|
for (const f of audit.siteFindings) {
|
|
@@ -209,7 +276,7 @@ const aeoMarkdown = (audit) => {
|
|
|
209
276
|
if (audit.crawlers) {
|
|
210
277
|
lines.push(...crawlerLines(audit.crawlers, audit.llmsTxt, audit.summary.pages));
|
|
211
278
|
}
|
|
212
|
-
lines.push(...aeoPageLines(audit), ...findingsSection(audit.findings, "#"));
|
|
279
|
+
lines.push(...aeoPageLines(audit), ...(audit.baseline ? baselineLines(audit.baseline, "#") : []), ...findingsSection(audit.findings, "#"));
|
|
213
280
|
if (audit.issues?.length) {
|
|
214
281
|
lines.push("", "## Page issues by rule", "", "| Severity | Rule | Pages | Example |", "| --- | --- | --- | --- |");
|
|
215
282
|
for (const i of audit.issues) {
|