ax-audit 2.4.0 → 3.0.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/CHANGELOG.md +34 -0
- package/README.md +39 -17
- package/dist/checks/agent-json.d.ts +10 -0
- package/dist/checks/agent-json.d.ts.map +1 -1
- package/dist/checks/agent-json.js +66 -2
- package/dist/checks/agent-json.js.map +1 -1
- package/dist/checks/html-rendering.d.ts +21 -0
- package/dist/checks/html-rendering.d.ts.map +1 -0
- package/dist/checks/html-rendering.js +204 -0
- package/dist/checks/html-rendering.js.map +1 -0
- package/dist/checks/html-utils.d.ts +37 -0
- package/dist/checks/html-utils.d.ts.map +1 -0
- package/dist/checks/html-utils.js +93 -0
- package/dist/checks/html-utils.js.map +1 -0
- package/dist/checks/http-headers.js +1 -1
- package/dist/checks/http-headers.js.map +1 -1
- package/dist/checks/index.d.ts.map +1 -1
- package/dist/checks/index.js +10 -0
- package/dist/checks/index.js.map +1 -1
- package/dist/checks/llms-txt.d.ts.map +1 -1
- package/dist/checks/llms-txt.js +17 -2
- package/dist/checks/llms-txt.js.map +1 -1
- package/dist/checks/mcp.d.ts.map +1 -1
- package/dist/checks/mcp.js +11 -2
- package/dist/checks/mcp.js.map +1 -1
- package/dist/checks/meta-tags.d.ts +13 -0
- package/dist/checks/meta-tags.d.ts.map +1 -1
- package/dist/checks/meta-tags.js +104 -24
- package/dist/checks/meta-tags.js.map +1 -1
- package/dist/checks/openapi.d.ts.map +1 -1
- package/dist/checks/openapi.js +11 -2
- package/dist/checks/openapi.js.map +1 -1
- package/dist/checks/robots-txt.js +1 -1
- package/dist/checks/security-txt.js +1 -1
- package/dist/checks/seo-basics.d.ts +13 -0
- package/dist/checks/seo-basics.d.ts.map +1 -0
- package/dist/checks/seo-basics.js +222 -0
- package/dist/checks/seo-basics.js.map +1 -0
- package/dist/checks/sitemap.d.ts +12 -0
- package/dist/checks/sitemap.d.ts.map +1 -0
- package/dist/checks/sitemap.js +241 -0
- package/dist/checks/sitemap.js.map +1 -0
- package/dist/checks/structured-data.js +1 -1
- package/dist/checks/structured-data.js.map +1 -1
- package/dist/checks/tls-https.d.ts +13 -0
- package/dist/checks/tls-https.d.ts.map +1 -0
- package/dist/checks/tls-https.js +164 -0
- package/dist/checks/tls-https.js.map +1 -0
- package/dist/checks/utils.d.ts +13 -1
- package/dist/checks/utils.d.ts.map +1 -1
- package/dist/checks/utils.js +28 -0
- package/dist/checks/utils.js.map +1 -1
- package/dist/checks/well-known-ai.d.ts +17 -0
- package/dist/checks/well-known-ai.d.ts.map +1 -0
- package/dist/checks/well-known-ai.js +123 -0
- package/dist/checks/well-known-ai.js.map +1 -0
- package/dist/constants.d.ts +19 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +47 -10
- package/dist/constants.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CheckContext, CheckResult, CheckMeta } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* "seo-basics" — the head-tag fundamentals every AI agent and search crawler relies on
|
|
4
|
+
* before anything else: `<title>`, `<meta description>`, `<link rel="canonical">`,
|
|
5
|
+
* `<html lang>`, `viewport`, `charset`, and `hreflang` for multilingual sites.
|
|
6
|
+
*
|
|
7
|
+
* These are not "SEO tricks"; they are the unambiguous identity, language, and topic
|
|
8
|
+
* signals an agent uses to summarize, rank, or cite a page. ax-audit treats this as
|
|
9
|
+
* baseline hygiene rather than optimization.
|
|
10
|
+
*/
|
|
11
|
+
export declare const meta: CheckMeta;
|
|
12
|
+
export default function check(ctx: CheckContext): Promise<CheckResult>;
|
|
13
|
+
//# sourceMappingURL=seo-basics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seo-basics.d.ts","sourceRoot":"","sources":["../../src/checks/seo-basics.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,EAAW,MAAM,aAAa,CAAC;AAIjF;;;;;;;;GAQG;AACH,eAAO,MAAM,IAAI,EAAE,SAKlB,CAAC;AAOF,wBAA8B,KAAK,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAmM3E"}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { guideUrl } from '../guide-urls.js';
|
|
2
|
+
import { extractVisibleText, findLinkTags, getAttribute, getMetaContent, getTagAttribute } from './html-utils.js';
|
|
3
|
+
import { buildResult } from './utils.js';
|
|
4
|
+
/**
|
|
5
|
+
* "seo-basics" — the head-tag fundamentals every AI agent and search crawler relies on
|
|
6
|
+
* before anything else: `<title>`, `<meta description>`, `<link rel="canonical">`,
|
|
7
|
+
* `<html lang>`, `viewport`, `charset`, and `hreflang` for multilingual sites.
|
|
8
|
+
*
|
|
9
|
+
* These are not "SEO tricks"; they are the unambiguous identity, language, and topic
|
|
10
|
+
* signals an agent uses to summarize, rank, or cite a page. ax-audit treats this as
|
|
11
|
+
* baseline hygiene rather than optimization.
|
|
12
|
+
*/
|
|
13
|
+
export const meta = {
|
|
14
|
+
id: 'seo-basics',
|
|
15
|
+
name: 'SEO Basics',
|
|
16
|
+
description: 'Checks <title>, meta description, canonical, lang, charset, viewport, hreflang',
|
|
17
|
+
weight: 7,
|
|
18
|
+
};
|
|
19
|
+
const TITLE_MIN = 20;
|
|
20
|
+
const TITLE_MAX = 70;
|
|
21
|
+
const DESCRIPTION_MIN = 70;
|
|
22
|
+
const DESCRIPTION_MAX = 160;
|
|
23
|
+
export default async function check(ctx) {
|
|
24
|
+
const start = performance.now();
|
|
25
|
+
const findings = [];
|
|
26
|
+
let score = 100;
|
|
27
|
+
const html = ctx.html;
|
|
28
|
+
if (!html) {
|
|
29
|
+
findings.push({
|
|
30
|
+
status: 'fail',
|
|
31
|
+
message: 'Could not fetch homepage HTML',
|
|
32
|
+
hint: 'Verify the homepage returns a non-empty 200 response on the root URL.',
|
|
33
|
+
learnMoreUrl: guideUrl(meta.id, 'no-html'),
|
|
34
|
+
});
|
|
35
|
+
return buildResult(meta, 0, findings, start);
|
|
36
|
+
}
|
|
37
|
+
const titleMatch = html.match(/<title\b[^>]*>([\s\S]*?)<\/title>/i);
|
|
38
|
+
const title = titleMatch ? extractVisibleText(titleMatch[1]) : '';
|
|
39
|
+
if (!title) {
|
|
40
|
+
findings.push({
|
|
41
|
+
status: 'fail',
|
|
42
|
+
message: '<title> is missing or empty',
|
|
43
|
+
hint: 'Add a <title> in <head> describing the page in 20-70 characters. Agents quote it as the document name.',
|
|
44
|
+
learnMoreUrl: guideUrl(meta.id, 'no-title'),
|
|
45
|
+
});
|
|
46
|
+
score -= 25;
|
|
47
|
+
}
|
|
48
|
+
else if (title.length < TITLE_MIN) {
|
|
49
|
+
findings.push({
|
|
50
|
+
status: 'warn',
|
|
51
|
+
message: `<title> is too short (${title.length} chars): "${title}"`,
|
|
52
|
+
hint: `Lengthen the title to ${TITLE_MIN}-${TITLE_MAX} characters with a clear topic indicator.`,
|
|
53
|
+
learnMoreUrl: guideUrl(meta.id, 'short-title'),
|
|
54
|
+
});
|
|
55
|
+
score -= 10;
|
|
56
|
+
}
|
|
57
|
+
else if (title.length > TITLE_MAX) {
|
|
58
|
+
findings.push({
|
|
59
|
+
status: 'warn',
|
|
60
|
+
message: `<title> is too long (${title.length} chars)`,
|
|
61
|
+
detail: `"${title.slice(0, 80)}…"`,
|
|
62
|
+
hint: `Shorten the title to ${TITLE_MIN}-${TITLE_MAX} characters. Many agents and search engines truncate beyond ~70.`,
|
|
63
|
+
learnMoreUrl: guideUrl(meta.id, 'long-title'),
|
|
64
|
+
});
|
|
65
|
+
score -= 5;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
findings.push({ status: 'pass', message: `<title> length ${title.length} chars: "${title}"` });
|
|
69
|
+
}
|
|
70
|
+
const description = getMetaContent(html, 'description');
|
|
71
|
+
if (!description) {
|
|
72
|
+
findings.push({
|
|
73
|
+
status: 'fail',
|
|
74
|
+
message: '<meta name="description"> is missing',
|
|
75
|
+
hint: 'Add <meta name="description" content="..."> in <head> with a 70-160 character summary. Agents use this as the canonical short description.',
|
|
76
|
+
learnMoreUrl: guideUrl(meta.id, 'no-description'),
|
|
77
|
+
});
|
|
78
|
+
score -= 20;
|
|
79
|
+
}
|
|
80
|
+
else if (description.length < DESCRIPTION_MIN) {
|
|
81
|
+
findings.push({
|
|
82
|
+
status: 'warn',
|
|
83
|
+
message: `Meta description is short (${description.length} chars)`,
|
|
84
|
+
hint: `Expand the description to ${DESCRIPTION_MIN}-${DESCRIPTION_MAX} characters with the page's main value proposition.`,
|
|
85
|
+
learnMoreUrl: guideUrl(meta.id, 'short-description'),
|
|
86
|
+
});
|
|
87
|
+
score -= 8;
|
|
88
|
+
}
|
|
89
|
+
else if (description.length > DESCRIPTION_MAX) {
|
|
90
|
+
findings.push({
|
|
91
|
+
status: 'warn',
|
|
92
|
+
message: `Meta description is long (${description.length} chars)`,
|
|
93
|
+
hint: `Trim to ${DESCRIPTION_MIN}-${DESCRIPTION_MAX} characters; longer descriptions get truncated.`,
|
|
94
|
+
learnMoreUrl: guideUrl(meta.id, 'long-description'),
|
|
95
|
+
});
|
|
96
|
+
score -= 5;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
findings.push({ status: 'pass', message: `Meta description length ${description.length} chars` });
|
|
100
|
+
}
|
|
101
|
+
if (description && title && description.trim().toLowerCase() === title.trim().toLowerCase()) {
|
|
102
|
+
findings.push({
|
|
103
|
+
status: 'warn',
|
|
104
|
+
message: 'Meta description duplicates the <title>',
|
|
105
|
+
hint: 'Write a description that complements the title rather than repeating it. Agents lose context when both fields contain the same text.',
|
|
106
|
+
learnMoreUrl: guideUrl(meta.id, 'duplicate-title-description'),
|
|
107
|
+
});
|
|
108
|
+
score -= 5;
|
|
109
|
+
}
|
|
110
|
+
const canonicalLinks = findLinkTags(html, 'canonical');
|
|
111
|
+
if (canonicalLinks.length === 0) {
|
|
112
|
+
findings.push({
|
|
113
|
+
status: 'warn',
|
|
114
|
+
message: 'No <link rel="canonical"> found',
|
|
115
|
+
hint: 'Add <link rel="canonical" href="https://your-site.com/page"> so agents have an unambiguous URL to cite even when crawled via a redirect or query-string variant.',
|
|
116
|
+
learnMoreUrl: guideUrl(meta.id, 'no-canonical'),
|
|
117
|
+
});
|
|
118
|
+
score -= 10;
|
|
119
|
+
}
|
|
120
|
+
else if (canonicalLinks.length > 1) {
|
|
121
|
+
findings.push({
|
|
122
|
+
status: 'warn',
|
|
123
|
+
message: `${canonicalLinks.length} <link rel="canonical"> tags (must be exactly 1)`,
|
|
124
|
+
hint: 'Keep a single canonical link per page. Multiple canonical hints are ignored by agents and search engines.',
|
|
125
|
+
learnMoreUrl: guideUrl(meta.id, 'multiple-canonical'),
|
|
126
|
+
});
|
|
127
|
+
score -= 5;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
const href = getAttribute(canonicalLinks[0], 'href');
|
|
131
|
+
if (!href) {
|
|
132
|
+
findings.push({
|
|
133
|
+
status: 'warn',
|
|
134
|
+
message: '<link rel="canonical"> is missing href',
|
|
135
|
+
hint: 'Add an absolute URL to the canonical link tag.',
|
|
136
|
+
learnMoreUrl: guideUrl(meta.id, 'canonical-no-href'),
|
|
137
|
+
});
|
|
138
|
+
score -= 5;
|
|
139
|
+
}
|
|
140
|
+
else if (!/^https?:\/\//i.test(href)) {
|
|
141
|
+
findings.push({
|
|
142
|
+
status: 'warn',
|
|
143
|
+
message: `Canonical href is not absolute: ${href}`,
|
|
144
|
+
hint: 'Use an absolute URL (https://...) for the canonical href. Relative paths are ambiguous when fetched outside the original page context.',
|
|
145
|
+
learnMoreUrl: guideUrl(meta.id, 'canonical-relative'),
|
|
146
|
+
});
|
|
147
|
+
score -= 5;
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
findings.push({ status: 'pass', message: `Canonical URL: ${href}` });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const lang = getTagAttribute(html, 'html', 'lang');
|
|
154
|
+
if (!lang) {
|
|
155
|
+
findings.push({
|
|
156
|
+
status: 'warn',
|
|
157
|
+
message: '<html lang="..."> is missing',
|
|
158
|
+
hint: 'Set the document language: <html lang="en">. Multilingual agents rely on this to pick the right summarization model and avoid mixed-language ranking.',
|
|
159
|
+
learnMoreUrl: guideUrl(meta.id, 'no-lang'),
|
|
160
|
+
});
|
|
161
|
+
score -= 10;
|
|
162
|
+
}
|
|
163
|
+
else if (!/^[a-z]{2,3}(-[A-Za-z0-9]+)*$/i.test(lang)) {
|
|
164
|
+
findings.push({
|
|
165
|
+
status: 'warn',
|
|
166
|
+
message: `<html lang> looks invalid: "${lang}"`,
|
|
167
|
+
hint: 'Use a valid BCP 47 tag (e.g., en, en-US, es-419, zh-Hant).',
|
|
168
|
+
learnMoreUrl: guideUrl(meta.id, 'invalid-lang'),
|
|
169
|
+
});
|
|
170
|
+
score -= 5;
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
findings.push({ status: 'pass', message: `<html lang="${lang}">` });
|
|
174
|
+
}
|
|
175
|
+
if (/<meta\s+charset\s*=\s*["']?utf-8["']?/i.test(html) ||
|
|
176
|
+
/<meta\s+http-equiv\s*=\s*["']content-type["'][^>]*charset=utf-8/i.test(html)) {
|
|
177
|
+
findings.push({ status: 'pass', message: 'UTF-8 charset declared' });
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
findings.push({
|
|
181
|
+
status: 'warn',
|
|
182
|
+
message: 'No UTF-8 charset declaration in HTML head',
|
|
183
|
+
hint: 'Add <meta charset="utf-8"> as the first child of <head>. Without it, agents can mis-decode non-ASCII content.',
|
|
184
|
+
learnMoreUrl: guideUrl(meta.id, 'no-charset'),
|
|
185
|
+
});
|
|
186
|
+
score -= 5;
|
|
187
|
+
}
|
|
188
|
+
const viewport = getMetaContent(html, 'viewport');
|
|
189
|
+
if (viewport && /width\s*=/.test(viewport)) {
|
|
190
|
+
findings.push({ status: 'pass', message: `Viewport meta present: "${viewport}"` });
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
findings.push({
|
|
194
|
+
status: 'warn',
|
|
195
|
+
message: 'Missing or incomplete viewport meta tag',
|
|
196
|
+
hint: 'Add <meta name="viewport" content="width=device-width, initial-scale=1">. Helps mobile agents render the page correctly.',
|
|
197
|
+
learnMoreUrl: guideUrl(meta.id, 'no-viewport'),
|
|
198
|
+
});
|
|
199
|
+
score -= 5;
|
|
200
|
+
}
|
|
201
|
+
const hreflangs = findLinkTags(html, 'alternate').filter((tag) => /\bhreflang\s*=/.test(tag));
|
|
202
|
+
if (hreflangs.length > 0) {
|
|
203
|
+
const hasXDefault = hreflangs.some((tag) => /hreflang\s*=\s*["']x-default["']/.test(tag));
|
|
204
|
+
if (hasXDefault) {
|
|
205
|
+
findings.push({
|
|
206
|
+
status: 'pass',
|
|
207
|
+
message: `${hreflangs.length} hreflang alternate(s) including x-default`,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
findings.push({
|
|
212
|
+
status: 'warn',
|
|
213
|
+
message: `${hreflangs.length} hreflang alternate(s) but no x-default`,
|
|
214
|
+
hint: 'Add <link rel="alternate" hreflang="x-default" href="..."> as a fallback for unmatched locales.',
|
|
215
|
+
learnMoreUrl: guideUrl(meta.id, 'no-x-default'),
|
|
216
|
+
});
|
|
217
|
+
score -= 3;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return buildResult(meta, score, findings, start);
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=seo-basics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seo-basics.js","sourceRoot":"","sources":["../../src/checks/seo-basics.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CheckContext, CheckResult, CheckMeta } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* "sitemap" — fetch and validate the XML sitemap so AI agents and search crawlers can discover
|
|
4
|
+
* the full URL surface. Sitemaps may be referenced from `robots.txt` (`Sitemap:` directive) or
|
|
5
|
+
* served at the conventional `/sitemap.xml` path. This check covers both, validates the XML
|
|
6
|
+
* shape, and respects sitemap-index files (which point to multiple child sitemaps).
|
|
7
|
+
*
|
|
8
|
+
* Reference: https://www.sitemaps.org/protocol.html
|
|
9
|
+
*/
|
|
10
|
+
export declare const meta: CheckMeta;
|
|
11
|
+
export default function check(ctx: CheckContext): Promise<CheckResult>;
|
|
12
|
+
//# sourceMappingURL=sitemap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sitemap.d.ts","sourceRoot":"","sources":["../../src/checks/sitemap.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,EAA0B,MAAM,aAAa,CAAC;AAGhG;;;;;;;GAOG;AACH,eAAO,MAAM,IAAI,EAAE,SAKlB,CAAC;AAMF,wBAA8B,KAAK,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CA2J3E"}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { guideUrl } from '../guide-urls.js';
|
|
2
|
+
import { buildResult } from './utils.js';
|
|
3
|
+
/**
|
|
4
|
+
* "sitemap" — fetch and validate the XML sitemap so AI agents and search crawlers can discover
|
|
5
|
+
* the full URL surface. Sitemaps may be referenced from `robots.txt` (`Sitemap:` directive) or
|
|
6
|
+
* served at the conventional `/sitemap.xml` path. This check covers both, validates the XML
|
|
7
|
+
* shape, and respects sitemap-index files (which point to multiple child sitemaps).
|
|
8
|
+
*
|
|
9
|
+
* Reference: https://www.sitemaps.org/protocol.html
|
|
10
|
+
*/
|
|
11
|
+
export const meta = {
|
|
12
|
+
id: 'sitemap',
|
|
13
|
+
name: 'Sitemap',
|
|
14
|
+
description: 'Checks sitemap.xml presence, structure, and freshness',
|
|
15
|
+
weight: 4,
|
|
16
|
+
};
|
|
17
|
+
const MAX_URLS_PER_SITEMAP = 50_000;
|
|
18
|
+
const MAX_BYTES_PER_SITEMAP = 50 * 1024 * 1024;
|
|
19
|
+
const STALE_DAYS = 365;
|
|
20
|
+
export default async function check(ctx) {
|
|
21
|
+
const start = performance.now();
|
|
22
|
+
const findings = [];
|
|
23
|
+
let score = 100;
|
|
24
|
+
const sitemapUrl = await locateSitemap(ctx);
|
|
25
|
+
if (!sitemapUrl) {
|
|
26
|
+
findings.push({
|
|
27
|
+
status: 'fail',
|
|
28
|
+
message: 'No sitemap found',
|
|
29
|
+
detail: 'Tried robots.txt Sitemap: directive and /sitemap.xml',
|
|
30
|
+
hint: 'Publish an XML sitemap at /sitemap.xml and reference it from robots.txt with: Sitemap: https://your-site.com/sitemap.xml',
|
|
31
|
+
learnMoreUrl: guideUrl(meta.id, 'not-found'),
|
|
32
|
+
});
|
|
33
|
+
return buildResult(meta, 0, findings, start);
|
|
34
|
+
}
|
|
35
|
+
findings.push({ status: 'pass', message: `Sitemap located: ${sitemapUrl.url}` });
|
|
36
|
+
const res = sitemapUrl.response;
|
|
37
|
+
if (res.body.length > MAX_BYTES_PER_SITEMAP) {
|
|
38
|
+
findings.push({
|
|
39
|
+
status: 'warn',
|
|
40
|
+
message: `Sitemap exceeds 50 MB (${formatBytes(res.body.length)})`,
|
|
41
|
+
hint: 'Split the sitemap into a sitemap-index referencing multiple smaller sitemaps. The sitemaps.org spec caps individual sitemaps at 50 MB.',
|
|
42
|
+
learnMoreUrl: guideUrl(meta.id, 'too-large'),
|
|
43
|
+
});
|
|
44
|
+
score -= 10;
|
|
45
|
+
}
|
|
46
|
+
if (!isXml(res.body)) {
|
|
47
|
+
findings.push({
|
|
48
|
+
status: 'fail',
|
|
49
|
+
message: 'Sitemap response does not look like XML',
|
|
50
|
+
detail: snippet(res.body),
|
|
51
|
+
hint: 'Serve the sitemap with valid XML and Content-Type: application/xml. Confirm the file starts with <?xml ...?> and contains <urlset> or <sitemapindex>.',
|
|
52
|
+
learnMoreUrl: guideUrl(meta.id, 'not-xml'),
|
|
53
|
+
});
|
|
54
|
+
return buildResult(meta, 20, findings, start);
|
|
55
|
+
}
|
|
56
|
+
const contentType = res.headers['content-type'] ?? '';
|
|
57
|
+
if (/(application|text)\/xml/i.test(contentType)) {
|
|
58
|
+
findings.push({ status: 'pass', message: `Content-Type is XML (${contentType.split(';')[0]})` });
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
findings.push({
|
|
62
|
+
status: 'warn',
|
|
63
|
+
message: `Unexpected Content-Type: ${contentType || '(missing)'}`,
|
|
64
|
+
hint: 'Serve the sitemap with Content-Type: application/xml so AI agents and search crawlers parse it correctly.',
|
|
65
|
+
learnMoreUrl: guideUrl(meta.id, 'wrong-content-type'),
|
|
66
|
+
});
|
|
67
|
+
score -= 5;
|
|
68
|
+
}
|
|
69
|
+
const isIndex = /<sitemapindex\b/i.test(res.body);
|
|
70
|
+
if (isIndex) {
|
|
71
|
+
const childRefs = [...res.body.matchAll(/<sitemap>[\s\S]*?<loc>\s*([^<]+?)\s*<\/loc>[\s\S]*?<\/sitemap>/gi)].map((m) => m[1].trim());
|
|
72
|
+
if (childRefs.length === 0) {
|
|
73
|
+
findings.push({
|
|
74
|
+
status: 'fail',
|
|
75
|
+
message: '<sitemapindex> contains no <sitemap> entries',
|
|
76
|
+
hint: 'A sitemap-index must contain at least one <sitemap><loc>...</loc></sitemap> child entry.',
|
|
77
|
+
learnMoreUrl: guideUrl(meta.id, 'empty-index'),
|
|
78
|
+
});
|
|
79
|
+
score -= 20;
|
|
80
|
+
return buildResult(meta, score, findings, start);
|
|
81
|
+
}
|
|
82
|
+
findings.push({ status: 'pass', message: `Sitemap-index references ${childRefs.length} child sitemap(s)` });
|
|
83
|
+
const sample = childRefs.slice(0, 3);
|
|
84
|
+
const reachable = [];
|
|
85
|
+
let totalUrls = 0;
|
|
86
|
+
let newestLastmod = null;
|
|
87
|
+
for (const childUrl of sample) {
|
|
88
|
+
const child = await ctx.fetch(childUrl);
|
|
89
|
+
if (!child.ok || !isXml(child.body))
|
|
90
|
+
continue;
|
|
91
|
+
reachable.push(childUrl);
|
|
92
|
+
const stats = analyzeUrlset(child.body);
|
|
93
|
+
totalUrls += stats.urlCount;
|
|
94
|
+
if (stats.newestLastmod && (!newestLastmod || stats.newestLastmod > newestLastmod)) {
|
|
95
|
+
newestLastmod = stats.newestLastmod;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (reachable.length === sample.length) {
|
|
99
|
+
findings.push({
|
|
100
|
+
status: 'pass',
|
|
101
|
+
message: `${reachable.length}/${sample.length} sample child sitemap(s) reachable`,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
findings.push({
|
|
106
|
+
status: 'warn',
|
|
107
|
+
message: `${reachable.length}/${sample.length} sample child sitemap(s) reachable`,
|
|
108
|
+
detail: 'Some referenced child sitemaps could not be fetched.',
|
|
109
|
+
hint: 'Make sure every <loc> URL inside the sitemap-index returns 200 OK and is publicly accessible.',
|
|
110
|
+
learnMoreUrl: guideUrl(meta.id, 'unreachable-children'),
|
|
111
|
+
});
|
|
112
|
+
score -= 10;
|
|
113
|
+
}
|
|
114
|
+
findings.push({
|
|
115
|
+
status: 'pass',
|
|
116
|
+
message: `Sample yielded ${totalUrls} URL(s) across ${reachable.length} child(ren)`,
|
|
117
|
+
});
|
|
118
|
+
reportLastmod(newestLastmod, findings, () => {
|
|
119
|
+
score -= 5;
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
const stats = analyzeUrlset(res.body);
|
|
124
|
+
if (stats.urlCount === 0) {
|
|
125
|
+
findings.push({
|
|
126
|
+
status: 'fail',
|
|
127
|
+
message: '<urlset> contains no <url> entries',
|
|
128
|
+
hint: 'Populate the sitemap with at least one <url><loc>...</loc></url> entry per page you want crawlers to index.',
|
|
129
|
+
learnMoreUrl: guideUrl(meta.id, 'empty-urlset'),
|
|
130
|
+
});
|
|
131
|
+
score -= 30;
|
|
132
|
+
}
|
|
133
|
+
else if (stats.urlCount > MAX_URLS_PER_SITEMAP) {
|
|
134
|
+
findings.push({
|
|
135
|
+
status: 'warn',
|
|
136
|
+
message: `Sitemap declares ${stats.urlCount} URLs (above 50,000 limit)`,
|
|
137
|
+
hint: 'Split into a sitemap-index referencing multiple sitemaps, each below 50,000 URLs.',
|
|
138
|
+
learnMoreUrl: guideUrl(meta.id, 'too-many-urls'),
|
|
139
|
+
});
|
|
140
|
+
score -= 10;
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
findings.push({ status: 'pass', message: `${stats.urlCount} URL(s) declared` });
|
|
144
|
+
}
|
|
145
|
+
if (stats.urlCount > 0) {
|
|
146
|
+
const lastmodCoverage = stats.lastmodCount / stats.urlCount;
|
|
147
|
+
if (lastmodCoverage >= 0.5) {
|
|
148
|
+
findings.push({
|
|
149
|
+
status: 'pass',
|
|
150
|
+
message: `${(lastmodCoverage * 100).toFixed(0)}% of URLs have <lastmod>`,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
findings.push({
|
|
155
|
+
status: 'warn',
|
|
156
|
+
message: `Only ${(lastmodCoverage * 100).toFixed(0)}% of URLs have <lastmod>`,
|
|
157
|
+
hint: 'Add a <lastmod>YYYY-MM-DD</lastmod> child to each <url>. AI agents use lastmod to prioritize re-fetching.',
|
|
158
|
+
learnMoreUrl: guideUrl(meta.id, 'low-lastmod'),
|
|
159
|
+
});
|
|
160
|
+
score -= 5;
|
|
161
|
+
}
|
|
162
|
+
reportLastmod(stats.newestLastmod, findings, () => {
|
|
163
|
+
score -= 5;
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return buildResult(meta, score, findings, start);
|
|
168
|
+
}
|
|
169
|
+
async function locateSitemap(ctx) {
|
|
170
|
+
const robots = await ctx.fetch(`${ctx.url}/robots.txt`);
|
|
171
|
+
if (robots.ok) {
|
|
172
|
+
const declared = [...robots.body.matchAll(/^Sitemap:\s*(\S+)\s*$/gim)].map((m) => m[1]);
|
|
173
|
+
for (const url of declared) {
|
|
174
|
+
const res = await ctx.fetch(url);
|
|
175
|
+
if (res.ok)
|
|
176
|
+
return { url, response: res };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const fallback = `${ctx.url}/sitemap.xml`;
|
|
180
|
+
const res = await ctx.fetch(fallback);
|
|
181
|
+
if (res.ok)
|
|
182
|
+
return { url: fallback, response: res };
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
function analyzeUrlset(xml) {
|
|
186
|
+
let urlCount = 0;
|
|
187
|
+
let lastmodCount = 0;
|
|
188
|
+
let newestLastmod = null;
|
|
189
|
+
for (const m of xml.matchAll(/<url\b[\s\S]*?<\/url>/gi)) {
|
|
190
|
+
urlCount++;
|
|
191
|
+
const block = m[0];
|
|
192
|
+
const lastmod = block.match(/<lastmod>\s*([^<]+?)\s*<\/lastmod>/i);
|
|
193
|
+
if (lastmod) {
|
|
194
|
+
lastmodCount++;
|
|
195
|
+
const date = new Date(lastmod[1]);
|
|
196
|
+
if (!isNaN(date.getTime())) {
|
|
197
|
+
if (!newestLastmod || date > newestLastmod) {
|
|
198
|
+
newestLastmod = date;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return { urlCount, lastmodCount, newestLastmod };
|
|
204
|
+
}
|
|
205
|
+
function reportLastmod(newest, findings, onStale) {
|
|
206
|
+
if (!newest)
|
|
207
|
+
return;
|
|
208
|
+
const ageMs = Date.now() - newest.getTime();
|
|
209
|
+
const ageDays = Math.round(ageMs / (1000 * 60 * 60 * 24));
|
|
210
|
+
if (ageDays > STALE_DAYS) {
|
|
211
|
+
findings.push({
|
|
212
|
+
status: 'warn',
|
|
213
|
+
message: `Newest <lastmod> is ${ageDays} days old`,
|
|
214
|
+
hint: 'Refresh <lastmod> on URLs that have changed. A stale sitemap signals to crawlers that nothing on the site has updated.',
|
|
215
|
+
learnMoreUrl: guideUrl(meta.id, 'stale'),
|
|
216
|
+
});
|
|
217
|
+
onStale();
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
findings.push({
|
|
221
|
+
status: 'pass',
|
|
222
|
+
message: `Newest <lastmod> is recent (${ageDays} day(s) ago)`,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function isXml(body) {
|
|
227
|
+
const head = body.slice(0, 512).trim();
|
|
228
|
+
return /^<\?xml/i.test(head) || /^<(urlset|sitemapindex)\b/i.test(head);
|
|
229
|
+
}
|
|
230
|
+
function snippet(value) {
|
|
231
|
+
const trimmed = value.trim().slice(0, 80);
|
|
232
|
+
return trimmed.replace(/\s+/g, ' ');
|
|
233
|
+
}
|
|
234
|
+
function formatBytes(n) {
|
|
235
|
+
if (n < 1024)
|
|
236
|
+
return `${n} B`;
|
|
237
|
+
if (n < 1024 * 1024)
|
|
238
|
+
return `${(n / 1024).toFixed(1)} KB`;
|
|
239
|
+
return `${(n / 1024 / 1024).toFixed(1)} MB`;
|
|
240
|
+
}
|
|
241
|
+
//# sourceMappingURL=sitemap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sitemap.js","sourceRoot":"","sources":["../../src/checks/sitemap.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"structured-data.js","sourceRoot":"","sources":["../../src/checks/structured-data.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,MAAM,CAAC,MAAM,IAAI,GAAc;IAC7B,EAAE,EAAE,iBAAiB;IACrB,IAAI,EAAE,iBAAiB;IACvB,WAAW,EAAE,4CAA4C;IACzD,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"structured-data.js","sourceRoot":"","sources":["../../src/checks/structured-data.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CheckContext, CheckResult, CheckMeta } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* "tls-https" — verify the site is served over HTTPS, that bare HTTP redirects to HTTPS,
|
|
4
|
+
* and that HSTS is properly configured (with `preload` + `includeSubDomains` for full
|
|
5
|
+
* coverage). Many AI agents refuse to fetch plaintext HTTP and downgrade trust on
|
|
6
|
+
* misconfigured TLS.
|
|
7
|
+
*
|
|
8
|
+
* The check intentionally avoids verifying the certificate chain — Node's `fetch` already
|
|
9
|
+
* fails on invalid certificates, so a successful HTTPS fetch is itself a positive signal.
|
|
10
|
+
*/
|
|
11
|
+
export declare const meta: CheckMeta;
|
|
12
|
+
export default function check(ctx: CheckContext): Promise<CheckResult>;
|
|
13
|
+
//# sourceMappingURL=tls-https.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tls-https.d.ts","sourceRoot":"","sources":["../../src/checks/tls-https.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,EAAW,MAAM,aAAa,CAAC;AAGjF;;;;;;;;GAQG;AACH,eAAO,MAAM,IAAI,EAAE,SAKlB,CAAC;AAKF,wBAA8B,KAAK,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAqI3E"}
|