@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
package/dist/core/lighthouse.js
CHANGED
|
@@ -1,140 +1,216 @@
|
|
|
1
1
|
export class LighthouseUnavailableError extends Error {
|
|
2
2
|
constructor(message) {
|
|
3
3
|
super(message);
|
|
4
|
-
this.name =
|
|
4
|
+
this.name = "LighthouseUnavailableError";
|
|
5
5
|
}
|
|
6
6
|
}
|
|
7
|
-
export const LIGHTHOUSE_INSTALL_HINT =
|
|
7
|
+
export const LIGHTHOUSE_INSTALL_HINT = "Install Lighthouse and a Chrome: `pnpm add -D lighthouse chrome-launcher` plus Google Chrome, or `pnpm exec playwright install chromium` (the Playwright Chromium is used automatically when found).";
|
|
8
8
|
/** Audits we already cover with our own rules; Lighthouse duplicates are suppressed. */
|
|
9
|
-
const COVERED_SEO_AUDITS = new Set([
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
const COVERED_SEO_AUDITS = new Set([
|
|
10
|
+
"document-title",
|
|
11
|
+
"meta-description",
|
|
12
|
+
"image-alt",
|
|
13
|
+
"canonical",
|
|
14
|
+
"hreflang",
|
|
15
|
+
"link-text",
|
|
16
|
+
"http-status-code",
|
|
17
|
+
"is-crawlable",
|
|
18
|
+
"structured-data",
|
|
19
|
+
"viewport",
|
|
20
|
+
]);
|
|
21
|
+
/** The metrics whose savings Lighthouse reports in milliseconds. */
|
|
22
|
+
const MS_SAVINGS_METRICS = ["LCP", "FCP", "TBT", "INP", "SI"];
|
|
23
|
+
const stripMarkdownLinks = (text) => text
|
|
24
|
+
.replaceAll(/\[(?<text>[^\]]+)\]\((?<href>https?:[^)]+)\)/gu, "$<text> ($<href>)")
|
|
25
|
+
.trim();
|
|
26
|
+
const percent = (score) => score === null || score === undefined ? null : Math.round(score * 100);
|
|
27
|
+
/** An audit in the performance category as an opportunity, or null when it saves nothing. */
|
|
28
|
+
const opportunityOf = (audit) => {
|
|
29
|
+
const savings = audit.metricSavings ?? {};
|
|
30
|
+
const msValues = MS_SAVINGS_METRICS.flatMap((metric) => {
|
|
31
|
+
const value = savings[metric];
|
|
32
|
+
return value !== undefined && value > 0 ? [value] : [];
|
|
33
|
+
});
|
|
34
|
+
const savingsMs = audit.details?.overallSavingsMs ??
|
|
35
|
+
(msValues.length > 0 ? Math.max(...msValues) : null);
|
|
36
|
+
const savingsBytes = audit.details?.overallSavingsBytes ?? null;
|
|
37
|
+
const hasSavings = (savingsMs ?? 0) > 0 ||
|
|
38
|
+
(savingsBytes ?? 0) > 0 ||
|
|
39
|
+
(savings["CLS"] ?? 0) > 0 ||
|
|
40
|
+
audit.details?.type === "opportunity";
|
|
41
|
+
if (!hasSavings) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
id: audit.id,
|
|
46
|
+
title: audit.title,
|
|
47
|
+
description: stripMarkdownLinks(audit.description),
|
|
48
|
+
score: audit.score,
|
|
49
|
+
savingsMs: savingsMs ?? null,
|
|
50
|
+
savingsBytes,
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
const opportunitiesOf = (lhr) => {
|
|
54
|
+
const perfRefs = lhr.categories["performance"]?.auditRefs ?? [];
|
|
55
|
+
const opportunities = [];
|
|
56
|
+
for (const ref of perfRefs) {
|
|
57
|
+
const audit = lhr.audits[ref.id];
|
|
58
|
+
if (!audit || audit.score === null || audit.score >= 0.9) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (ref.group === "metrics" || ref.group === "hidden") {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const opportunity = opportunityOf(audit);
|
|
65
|
+
if (opportunity) {
|
|
66
|
+
opportunities.push(opportunity);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
opportunities.sort((a, b) => (b.savingsMs ?? 0) - (a.savingsMs ?? 0) ||
|
|
70
|
+
(b.savingsBytes ?? 0) - (a.savingsBytes ?? 0));
|
|
71
|
+
return opportunities;
|
|
72
|
+
};
|
|
73
|
+
const failedAudits = (lhr, category, skip) => {
|
|
74
|
+
const refs = lhr.categories[category]?.auditRefs ?? [];
|
|
75
|
+
const out = [];
|
|
76
|
+
for (const ref of refs) {
|
|
77
|
+
const audit = lhr.audits[ref.id];
|
|
78
|
+
if (!audit || skip.has(audit.id)) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (!["binary", "numeric", "metricSavings"].includes(audit.scoreDisplayMode)) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (audit.score === null || audit.score >= 1) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
out.push({
|
|
88
|
+
id: audit.id,
|
|
89
|
+
title: audit.title,
|
|
90
|
+
description: stripMarkdownLinks(audit.description),
|
|
91
|
+
score: audit.score,
|
|
92
|
+
displayValue: audit.displayValue ?? null,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
};
|
|
97
|
+
export const summarize = (lhr, requestedUrl, formFactor) => {
|
|
98
|
+
const numeric = (id) => {
|
|
99
|
+
const value = lhr.audits[id]?.numericValue;
|
|
100
|
+
return value !== undefined && Number.isFinite(value) ? value : null;
|
|
101
|
+
};
|
|
102
|
+
return {
|
|
103
|
+
tool: "lighthouse",
|
|
104
|
+
lighthouseVersion: lhr.lighthouseVersion,
|
|
105
|
+
formFactor,
|
|
106
|
+
requestedUrl,
|
|
107
|
+
finalUrl: lhr.finalDisplayedUrl ?? lhr.finalUrl ?? requestedUrl,
|
|
108
|
+
metrics: {
|
|
109
|
+
performanceScore: percent(lhr.categories["performance"]?.score),
|
|
110
|
+
seoScore: percent(lhr.categories["seo"]?.score),
|
|
111
|
+
agenticBrowsingScore: percent(lhr.categories["agentic-browsing"]?.score),
|
|
112
|
+
lcpMs: numeric("largest-contentful-paint"),
|
|
113
|
+
cls: numeric("cumulative-layout-shift"),
|
|
114
|
+
tbtMs: numeric("total-blocking-time"),
|
|
115
|
+
fcpMs: numeric("first-contentful-paint"),
|
|
116
|
+
speedIndexMs: numeric("speed-index"),
|
|
117
|
+
ttfbMs: numeric("server-response-time"),
|
|
118
|
+
inpMs: numeric("interaction-to-next-paint"),
|
|
119
|
+
},
|
|
120
|
+
opportunities: opportunitiesOf(lhr),
|
|
121
|
+
failedSeoAudits: failedAudits(lhr, "seo", COVERED_SEO_AUDITS),
|
|
122
|
+
agenticBrowsingAudits: failedAudits(lhr, "agentic-browsing", new Set()),
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
const playwrightChromiumPath = async () => {
|
|
126
|
+
try {
|
|
127
|
+
const pw = await import("playwright");
|
|
128
|
+
const p = pw.chromium.executablePath();
|
|
129
|
+
const { promises: fs } = await import("node:fs");
|
|
130
|
+
await fs.access(p);
|
|
131
|
+
return p;
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
/** Lighthouse and chrome-launcher are optional peers, so they load only when a run asks for them. */
|
|
138
|
+
const loadLighthouse = async () => {
|
|
14
139
|
try {
|
|
15
|
-
|
|
16
|
-
|
|
140
|
+
const lighthouseModule = await import("lighthouse");
|
|
141
|
+
const chromeLauncher = await import("chrome-launcher");
|
|
142
|
+
return {
|
|
143
|
+
lighthouse: lighthouseModule.default,
|
|
144
|
+
launch: chromeLauncher.launch,
|
|
145
|
+
};
|
|
17
146
|
}
|
|
18
147
|
catch {
|
|
19
148
|
throw new LighthouseUnavailableError(`Lighthouse or chrome-launcher is not installed. ${LIGHTHOUSE_INSTALL_HINT}`);
|
|
20
149
|
}
|
|
21
|
-
|
|
150
|
+
};
|
|
151
|
+
export const runLighthouse = async (url, options = {}) => {
|
|
152
|
+
const formFactor = options.formFactor ?? "mobile";
|
|
153
|
+
const { lighthouse, launch } = await loadLighthouse();
|
|
154
|
+
const chromePath = options.chromePath ??
|
|
155
|
+
process.env["CHROME_PATH"] ??
|
|
156
|
+
(await playwrightChromiumPath());
|
|
157
|
+
const launchOptions = {
|
|
158
|
+
chromeFlags: [
|
|
159
|
+
"--headless=new",
|
|
160
|
+
"--no-first-run",
|
|
161
|
+
"--disable-gpu",
|
|
162
|
+
"--no-default-browser-check",
|
|
163
|
+
],
|
|
164
|
+
logLevel: "silent",
|
|
165
|
+
};
|
|
166
|
+
if (chromePath) {
|
|
167
|
+
launchOptions.chromePath = chromePath;
|
|
168
|
+
}
|
|
22
169
|
let chrome;
|
|
23
170
|
try {
|
|
24
|
-
chrome = await launch(
|
|
25
|
-
chromeFlags: ['--headless=new', '--no-first-run', '--disable-gpu', '--no-default-browser-check'],
|
|
26
|
-
...(chromePath ? { chromePath } : {}),
|
|
27
|
-
logLevel: 'silent',
|
|
28
|
-
});
|
|
171
|
+
chrome = await launch(launchOptions);
|
|
29
172
|
}
|
|
30
|
-
catch (
|
|
31
|
-
throw new LighthouseUnavailableError(`Could not launch Chrome: ${
|
|
173
|
+
catch (error) {
|
|
174
|
+
throw new LighthouseUnavailableError(`Could not launch Chrome: ${error instanceof Error ? error.message : String(error)}. ${LIGHTHOUSE_INSTALL_HINT}`);
|
|
32
175
|
}
|
|
33
176
|
const timeoutMs = options.timeoutMs ?? 120_000;
|
|
34
177
|
let timer;
|
|
35
178
|
try {
|
|
36
|
-
|
|
179
|
+
let config;
|
|
180
|
+
if (formFactor === "desktop") {
|
|
181
|
+
const desktop = await import("lighthouse/core/config/desktop-config.js");
|
|
182
|
+
config = desktop.default;
|
|
183
|
+
}
|
|
37
184
|
const flags = {
|
|
38
185
|
port: chrome.port,
|
|
39
|
-
output:
|
|
40
|
-
logLevel:
|
|
41
|
-
onlyCategories: [
|
|
186
|
+
output: "json",
|
|
187
|
+
logLevel: "silent",
|
|
188
|
+
onlyCategories: ["performance", "seo", "agentic-browsing"],
|
|
42
189
|
extraHeaders: options.headers,
|
|
43
|
-
...(options.userAgent ? { emulatedUserAgent: options.userAgent } : {}),
|
|
44
190
|
};
|
|
191
|
+
if (options.userAgent) {
|
|
192
|
+
flags.emulatedUserAgent = options.userAgent;
|
|
193
|
+
}
|
|
45
194
|
const run = lighthouse(url, flags, config);
|
|
46
|
-
|
|
195
|
+
// oxlint-disable-next-line promise/avoid-new -- setTimeout has no promise form that can be cleared without settling it
|
|
196
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
47
197
|
timer = setTimeout(() => reject(new Error(`Lighthouse timed out after ${timeoutMs} ms`)), timeoutMs);
|
|
48
198
|
});
|
|
49
199
|
const result = await Promise.race([run, timeout]);
|
|
50
|
-
if (!result)
|
|
51
|
-
throw new Error(
|
|
200
|
+
if (!result) {
|
|
201
|
+
throw new Error("Lighthouse returned no result");
|
|
202
|
+
}
|
|
52
203
|
const lhr = result.lhr;
|
|
53
|
-
if (lhr.runtimeError && lhr.runtimeError.code !==
|
|
204
|
+
if (lhr.runtimeError && lhr.runtimeError.code !== "NO_ERROR") {
|
|
54
205
|
throw new Error(`Lighthouse runtime error ${lhr.runtimeError.code}: ${lhr.runtimeError.message}`);
|
|
206
|
+
}
|
|
55
207
|
return summarize(lhr, url, formFactor);
|
|
56
208
|
}
|
|
57
209
|
finally {
|
|
58
|
-
if (timer)
|
|
210
|
+
if (timer) {
|
|
59
211
|
clearTimeout(timer);
|
|
212
|
+
}
|
|
60
213
|
chrome.kill();
|
|
61
214
|
}
|
|
62
|
-
}
|
|
63
|
-
async function playwrightChromiumPath() {
|
|
64
|
-
try {
|
|
65
|
-
const pw = await import('playwright');
|
|
66
|
-
const p = pw.chromium.executablePath();
|
|
67
|
-
const { promises: fs } = await import('node:fs');
|
|
68
|
-
await fs.access(p);
|
|
69
|
-
return p;
|
|
70
|
-
}
|
|
71
|
-
catch {
|
|
72
|
-
return undefined;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
export function summarize(lhr, requestedUrl, formFactor) {
|
|
76
|
-
const numeric = (id) => {
|
|
77
|
-
const v = lhr.audits[id]?.numericValue;
|
|
78
|
-
return typeof v === 'number' && Number.isFinite(v) ? v : null;
|
|
79
|
-
};
|
|
80
|
-
const pct = (score) => (typeof score === 'number' ? Math.round(score * 100) : null);
|
|
81
|
-
const perfRefs = lhr.categories['performance']?.auditRefs ?? [];
|
|
82
|
-
const opportunities = [];
|
|
83
|
-
for (const ref of perfRefs) {
|
|
84
|
-
const audit = lhr.audits[ref.id];
|
|
85
|
-
if (!audit || audit.score === null || audit.score >= 0.9)
|
|
86
|
-
continue;
|
|
87
|
-
if (ref.group === 'metrics' || ref.group === 'hidden')
|
|
88
|
-
continue;
|
|
89
|
-
const savings = audit.metricSavings ?? {};
|
|
90
|
-
const msValues = ['LCP', 'FCP', 'TBT', 'INP', 'SI'].map((k) => savings[k]).filter((v) => typeof v === 'number' && v > 0);
|
|
91
|
-
const savingsMs = audit.details?.overallSavingsMs ?? (msValues.length ? Math.max(...msValues) : null);
|
|
92
|
-
const savingsBytes = audit.details?.overallSavingsBytes ?? null;
|
|
93
|
-
const hasSavings = (savingsMs ?? 0) > 0 || (savingsBytes ?? 0) > 0 || (typeof savings['CLS'] === 'number' && savings['CLS'] > 0) || audit.details?.type === 'opportunity';
|
|
94
|
-
if (!hasSavings)
|
|
95
|
-
continue;
|
|
96
|
-
opportunities.push({ id: audit.id, title: audit.title, description: stripMarkdownLinks(audit.description), score: audit.score, savingsMs: savingsMs ?? null, savingsBytes });
|
|
97
|
-
}
|
|
98
|
-
opportunities.sort((a, b) => (b.savingsMs ?? 0) - (a.savingsMs ?? 0) || (b.savingsBytes ?? 0) - (a.savingsBytes ?? 0));
|
|
99
|
-
const failed = (category, skip) => {
|
|
100
|
-
const refs = lhr.categories[category]?.auditRefs ?? [];
|
|
101
|
-
const out = [];
|
|
102
|
-
for (const ref of refs) {
|
|
103
|
-
const audit = lhr.audits[ref.id];
|
|
104
|
-
if (!audit || skip.has(audit.id))
|
|
105
|
-
continue;
|
|
106
|
-
if (!['binary', 'numeric', 'metricSavings'].includes(audit.scoreDisplayMode))
|
|
107
|
-
continue;
|
|
108
|
-
if (audit.score === null || audit.score >= 1)
|
|
109
|
-
continue;
|
|
110
|
-
out.push({ id: audit.id, title: audit.title, description: stripMarkdownLinks(audit.description), score: audit.score, displayValue: audit.displayValue ?? null });
|
|
111
|
-
}
|
|
112
|
-
return out;
|
|
113
|
-
};
|
|
114
|
-
return {
|
|
115
|
-
tool: 'lighthouse',
|
|
116
|
-
lighthouseVersion: lhr.lighthouseVersion,
|
|
117
|
-
formFactor,
|
|
118
|
-
requestedUrl,
|
|
119
|
-
finalUrl: lhr.finalDisplayedUrl ?? lhr.finalUrl ?? requestedUrl,
|
|
120
|
-
metrics: {
|
|
121
|
-
performanceScore: pct(lhr.categories['performance']?.score),
|
|
122
|
-
seoScore: pct(lhr.categories['seo']?.score),
|
|
123
|
-
agenticBrowsingScore: pct(lhr.categories['agentic-browsing']?.score),
|
|
124
|
-
lcpMs: numeric('largest-contentful-paint'),
|
|
125
|
-
cls: numeric('cumulative-layout-shift'),
|
|
126
|
-
tbtMs: numeric('total-blocking-time'),
|
|
127
|
-
fcpMs: numeric('first-contentful-paint'),
|
|
128
|
-
speedIndexMs: numeric('speed-index'),
|
|
129
|
-
ttfbMs: numeric('server-response-time'),
|
|
130
|
-
inpMs: numeric('interaction-to-next-paint'),
|
|
131
|
-
},
|
|
132
|
-
opportunities,
|
|
133
|
-
failedSeoAudits: failed('seo', COVERED_SEO_AUDITS),
|
|
134
|
-
agenticBrowsingAudits: failed('agentic-browsing', new Set()),
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
function stripMarkdownLinks(text) {
|
|
138
|
-
return text.replace(/\[([^\]]+)\]\((https?:[^)]+)\)/g, '$1 ($2)').trim();
|
|
139
|
-
}
|
|
215
|
+
};
|
|
140
216
|
//# sourceMappingURL=lighthouse.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lighthouse.js","sourceRoot":"","sources":["../../src/core/lighthouse.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"lighthouse.js","sourceRoot":"","sources":["../../src/core/lighthouse.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { Finding, PerformanceResult, RuleMeta } from
|
|
1
|
+
import type { Finding, PerformanceResult, RuleMeta } from "./types.js";
|
|
2
2
|
export declare const PERFORMANCE_RULES: RuleMeta[];
|
|
3
|
-
export declare
|
|
3
|
+
export declare const performanceFindings: (result: PerformanceResult) => Finding[];
|
|
@@ -1,53 +1,125 @@
|
|
|
1
|
-
import { finding, GOOGLE_DOCS } from
|
|
1
|
+
import { finding, GOOGLE_DOCS } from "./rules/rule.js";
|
|
2
2
|
const meta = (id, severity, description, recommendation, docs = GOOGLE_DOCS.cwv) => ({
|
|
3
3
|
id,
|
|
4
|
-
category:
|
|
4
|
+
category: "performance",
|
|
5
5
|
severity,
|
|
6
6
|
description,
|
|
7
7
|
recommendation,
|
|
8
8
|
docs,
|
|
9
|
-
requires:
|
|
9
|
+
requires: "lighthouse",
|
|
10
10
|
});
|
|
11
11
|
export const PERFORMANCE_RULES = [
|
|
12
|
-
meta(
|
|
13
|
-
meta(
|
|
14
|
-
meta(
|
|
15
|
-
meta(
|
|
16
|
-
meta(
|
|
17
|
-
meta(
|
|
18
|
-
meta(
|
|
19
|
-
meta(
|
|
20
|
-
meta(
|
|
12
|
+
meta("perf.lcp-slow", "warning", "Largest Contentful Paint above 2.5 s (poor above 4 s). Lab measurement from Lighthouse.", "Preload the LCP image/font, serve it from a CDN in a modern format, inline critical CSS, and cut server response time."),
|
|
13
|
+
meta("perf.cls-high", "warning", "Cumulative Layout Shift above 0.1 (poor above 0.25).", "Reserve space for images, ads and embeds with width/height or aspect-ratio; avoid inserting content above existing content; use font-display: optional or size-adjusted fallbacks."),
|
|
14
|
+
meta("perf.tbt-high", "warning", "Total Blocking Time above 200 ms (poor above 600 ms). Lab proxy for Interaction to Next Paint.", "Split long JavaScript tasks, defer non-critical scripts, remove unused JavaScript, and move work off the main thread."),
|
|
15
|
+
meta("perf.fcp-slow", "info", "First Contentful Paint above 1.8 s (poor above 3 s).", "Eliminate render-blocking resources, inline critical CSS, and reduce server response time."),
|
|
16
|
+
meta("perf.ttfb-slow", "warning", "Server response time (TTFB) above 800 ms.", "Cache HTML at the edge, avoid slow data fetches during render, and use a nearby region/CDN."),
|
|
17
|
+
meta("perf.score-low", "warning", "Lighthouse performance score below 50.", "Work through the opportunities listed in the performance result, biggest savings first."),
|
|
18
|
+
meta("perf.opportunity", "info", "A Lighthouse opportunity with measurable estimated savings.", "Apply the Lighthouse recommendation described in the finding."),
|
|
19
|
+
meta("lighthouse.seo-audit-failed", "warning", "A Lighthouse SEO audit failed (rendered-DOM checks such as crawlable anchors, robots.txt validity).", "Follow the Lighthouse audit description in the finding.", "https://developer.chrome.com/docs/lighthouse/seo/"),
|
|
20
|
+
meta("lighthouse.agentic-audit-failed", "info", "A Lighthouse agentic-browsing audit failed (llms.txt, accessibility tree quality, WebMCP). Experimental.", "Follow the Lighthouse audit description. These checks reflect how well AI agents can use the site and are not classic ranking factors.", "https://developer.chrome.com/docs/lighthouse/"),
|
|
21
21
|
];
|
|
22
22
|
const byId = new Map(PERFORMANCE_RULES.map((r) => [r.id, r]));
|
|
23
|
-
const m = (id) =>
|
|
24
|
-
|
|
23
|
+
const m = (id) => {
|
|
24
|
+
const rule = byId.get(id);
|
|
25
|
+
if (!rule) {
|
|
26
|
+
throw new Error(`Unknown performance rule: ${id}`);
|
|
27
|
+
}
|
|
28
|
+
return rule;
|
|
29
|
+
};
|
|
30
|
+
/** LCP, CLS and TBT against the Core Web Vitals thresholds. */
|
|
31
|
+
const vitalsFindings = (result) => {
|
|
25
32
|
const out = [];
|
|
26
|
-
const
|
|
27
|
-
const
|
|
33
|
+
const add = (f) => out.push(f);
|
|
34
|
+
const { metrics } = result;
|
|
35
|
+
if (metrics.lcpMs !== null && metrics.lcpMs > 2500) {
|
|
36
|
+
add(finding(m("perf.lcp-slow"), `LCP is ${(metrics.lcpMs / 1000).toFixed(1)} s (${metrics.lcpMs > 4000 ? "poor" : "needs improvement"}).`, {
|
|
37
|
+
severity: metrics.lcpMs > 4000 ? "error" : "warning",
|
|
38
|
+
evidence: {
|
|
39
|
+
lcpMs: Math.round(metrics.lcpMs),
|
|
40
|
+
formFactor: result.formFactor,
|
|
41
|
+
},
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
if (metrics.cls !== null && metrics.cls > 0.1) {
|
|
45
|
+
add(finding(m("perf.cls-high"), `CLS is ${metrics.cls.toFixed(3)} (${metrics.cls > 0.25 ? "poor" : "needs improvement"}).`, {
|
|
46
|
+
severity: metrics.cls > 0.25 ? "error" : "warning",
|
|
47
|
+
evidence: { cls: Number(metrics.cls.toFixed(3)) },
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
if (metrics.tbtMs !== null && metrics.tbtMs > 200) {
|
|
51
|
+
add(finding(m("perf.tbt-high"), `TBT is ${Math.round(metrics.tbtMs)} ms (${metrics.tbtMs > 600 ? "poor" : "needs improvement"}).`, {
|
|
52
|
+
severity: metrics.tbtMs > 600 ? "error" : "warning",
|
|
53
|
+
evidence: { tbtMs: Math.round(metrics.tbtMs) },
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
};
|
|
58
|
+
/** FCP, server response time and the overall Lighthouse score. */
|
|
59
|
+
const timingFindings = (result) => {
|
|
60
|
+
const out = [];
|
|
61
|
+
const add = (f) => out.push(f);
|
|
28
62
|
const { metrics } = result;
|
|
29
|
-
if (metrics.
|
|
30
|
-
add(finding(m(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (metrics.
|
|
36
|
-
add(finding(m(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
63
|
+
if (metrics.fcpMs !== null && metrics.fcpMs > 1800) {
|
|
64
|
+
add(finding(m("perf.fcp-slow"), `FCP is ${(metrics.fcpMs / 1000).toFixed(1)} s.`, {
|
|
65
|
+
severity: metrics.fcpMs > 3000 ? "warning" : "info",
|
|
66
|
+
evidence: { fcpMs: Math.round(metrics.fcpMs) },
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
if (metrics.ttfbMs !== null && metrics.ttfbMs > 800) {
|
|
70
|
+
add(finding(m("perf.ttfb-slow"), `Server response time is ${Math.round(metrics.ttfbMs)} ms.`, { evidence: { ttfbMs: Math.round(metrics.ttfbMs) } }));
|
|
71
|
+
}
|
|
72
|
+
if (metrics.performanceScore !== null && metrics.performanceScore < 50) {
|
|
73
|
+
add(finding(m("perf.score-low"), `Lighthouse performance score is ${metrics.performanceScore}/100.`, {
|
|
74
|
+
evidence: {
|
|
75
|
+
performanceScore: metrics.performanceScore,
|
|
76
|
+
formFactor: result.formFactor,
|
|
77
|
+
},
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
};
|
|
82
|
+
/** Lighthouse opportunities and failed SEO and agentic-browsing audits. */
|
|
83
|
+
const auditFindings = (result) => {
|
|
84
|
+
const out = [];
|
|
85
|
+
const add = (f) => out.push(f);
|
|
41
86
|
for (const opp of result.opportunities.slice(0, 8)) {
|
|
42
|
-
const savings = [
|
|
43
|
-
|
|
87
|
+
const savings = [
|
|
88
|
+
opp.savingsMs ? `${Math.round(opp.savingsMs)} ms` : null,
|
|
89
|
+
opp.savingsBytes ? `${Math.round(opp.savingsBytes / 1024)} KiB` : null,
|
|
90
|
+
]
|
|
91
|
+
.filter(Boolean)
|
|
92
|
+
.join(", ");
|
|
93
|
+
add(finding(m("perf.opportunity"), `${opp.title}${savings ? ` (est. savings ${savings})` : ""}.`, {
|
|
94
|
+
recommendation: opp.description,
|
|
95
|
+
evidence: {
|
|
96
|
+
auditId: opp.id,
|
|
97
|
+
savingsMs: opp.savingsMs,
|
|
98
|
+
savingsBytes: opp.savingsBytes,
|
|
99
|
+
score: opp.score,
|
|
100
|
+
},
|
|
101
|
+
}));
|
|
44
102
|
}
|
|
45
103
|
for (const audit of result.failedSeoAudits) {
|
|
46
|
-
add(finding(m(
|
|
104
|
+
add(finding(m("lighthouse.seo-audit-failed"), `Lighthouse SEO audit "${audit.title}" failed${audit.displayValue ? `: ${audit.displayValue}` : ""}.`, {
|
|
105
|
+
recommendation: audit.description,
|
|
106
|
+
evidence: { auditId: audit.id, score: audit.score },
|
|
107
|
+
}));
|
|
47
108
|
}
|
|
48
109
|
for (const audit of result.agenticBrowsingAudits) {
|
|
49
|
-
add(finding(m(
|
|
110
|
+
add(finding(m("lighthouse.agentic-audit-failed"), `Lighthouse agentic-browsing audit "${audit.title}" failed${audit.displayValue ? `: ${audit.displayValue}` : ""}.`, {
|
|
111
|
+
recommendation: audit.description,
|
|
112
|
+
evidence: { auditId: audit.id, score: audit.score },
|
|
113
|
+
}));
|
|
50
114
|
}
|
|
51
115
|
return out;
|
|
52
|
-
}
|
|
116
|
+
};
|
|
117
|
+
export const performanceFindings = (result) => {
|
|
118
|
+
const url = result.finalUrl || result.requestedUrl;
|
|
119
|
+
return [
|
|
120
|
+
...vitalsFindings(result),
|
|
121
|
+
...timingFindings(result),
|
|
122
|
+
...auditFindings(result),
|
|
123
|
+
].map((f) => ({ ...f, url }));
|
|
124
|
+
};
|
|
53
125
|
//# sourceMappingURL=performance-rules.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"performance-rules.js","sourceRoot":"","sources":["../../src/core/performance-rules.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"performance-rules.js","sourceRoot":"","sources":["../../src/core/performance-rules.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { ExtractedPage, Finding, RenderComparison, RenderSnapshot, ResponseInfo, RuleMeta } from
|
|
1
|
+
import type { ExtractedPage, Finding, RenderComparison, RenderSnapshot, ResponseInfo, RuleMeta } from "./types.js";
|
|
2
2
|
export declare const RENDER_RULES: RuleMeta[];
|
|
3
|
-
export declare
|
|
4
|
-
export
|
|
3
|
+
export declare const snapshot: (page: ExtractedPage, response?: ResponseInfo) => RenderSnapshot;
|
|
4
|
+
export interface RenderCompareResult {
|
|
5
5
|
comparison: RenderComparison;
|
|
6
6
|
findings: Finding[];
|
|
7
|
-
}
|
|
7
|
+
}
|
|
8
|
+
export declare const compareRender: (raw: ExtractedPage, rendered: ExtractedPage, renderedUrl: string, consoleErrors: string[], rawResponse?: ResponseInfo) => RenderCompareResult;
|