@rivi-software/agentic-seo-check 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +124 -18
- package/dist/brand.d.ts +11 -0
- package/dist/brand.js +22 -0
- package/dist/brand.js.map +1 -0
- package/dist/cli.js +174 -86
- package/dist/cli.js.map +1 -1
- package/dist/core/aeo/audit.d.ts +44 -0
- package/dist/core/aeo/audit.js +310 -0
- package/dist/core/aeo/audit.js.map +1 -0
- package/dist/core/aeo/bots.d.ts +30 -0
- package/dist/core/aeo/bots.js +299 -0
- package/dist/core/aeo/bots.js.map +1 -0
- package/dist/core/aeo/llms-txt.d.ts +8 -0
- package/dist/core/aeo/llms-txt.js +88 -0
- package/dist/core/aeo/llms-txt.js.map +1 -0
- package/dist/core/aeo/page.d.ts +53 -0
- package/dist/core/aeo/page.js +312 -0
- package/dist/core/aeo/page.js.map +1 -0
- package/dist/core/aeo/probe.d.ts +10 -0
- package/dist/core/aeo/probe.js +53 -0
- package/dist/core/aeo/probe.js.map +1 -0
- package/dist/core/aeo/site-rules.d.ts +13 -0
- package/dist/core/aeo/site-rules.js +178 -0
- package/dist/core/aeo/site-rules.js.map +1 -0
- package/dist/core/audit.d.ts +37 -16
- package/dist/core/audit.js +389 -249
- 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 +9 -5
- package/dist/core/crawl.js +271 -123
- 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/answer.d.ts +4 -0
- package/dist/core/html/answer.js +198 -0
- package/dist/core/html/answer.js.map +1 -0
- package/dist/core/html/dom.d.ts +17 -0
- package/dist/core/html/dom.js +83 -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 +102 -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 +4 -2
- package/dist/core/report/markdown.js +210 -129
- package/dist/core/report/markdown.js.map +1 -1
- package/dist/core/robots.d.ts +12 -5
- package/dist/core/robots.js +43 -17
- package/dist/core/robots.js.map +1 -1
- package/dist/core/rules/aeo.d.ts +11 -0
- package/dist/core/rules/aeo.js +423 -0
- package/dist/core/rules/aeo.js.map +1 -0
- 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 +10 -6
- package/dist/core/rules/index.js +43 -36
- 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 +5 -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 +210 -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 +189 -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 +15 -11
- package/dist/index.js +11 -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 +437 -121
- 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 +32 -10
- package/skills/seo-audit/SKILL.md +8 -2
package/dist/core/fetch.js
CHANGED
|
@@ -1,119 +1,165 @@
|
|
|
1
|
-
import { VERSION } from
|
|
1
|
+
import { VERSION } from "../version.js";
|
|
2
2
|
export const DEFAULT_USER_AGENT = `Mozilla/5.0 (compatible; agentic-seo-check/${VERSION}; +https://github.com/rivi-software/agentic-seo-check)`;
|
|
3
|
-
const KEPT_HEADERS = [
|
|
3
|
+
const KEPT_HEADERS = [
|
|
4
|
+
"content-type",
|
|
5
|
+
"x-robots-tag",
|
|
6
|
+
"link",
|
|
7
|
+
"content-language",
|
|
8
|
+
"cache-control",
|
|
9
|
+
"last-modified",
|
|
10
|
+
"location",
|
|
11
|
+
"content-length",
|
|
12
|
+
"vary",
|
|
13
|
+
];
|
|
4
14
|
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
let current = url;
|
|
12
|
-
for (let hop = 0; hop <= maxRedirects; hop++) {
|
|
13
|
-
const controller = new AbortController();
|
|
14
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
15
|
-
try {
|
|
16
|
-
const res = await fetch(current, {
|
|
17
|
-
method: 'GET',
|
|
18
|
-
redirect: 'manual',
|
|
19
|
-
signal: controller.signal,
|
|
20
|
-
headers: {
|
|
21
|
-
'user-agent': options.userAgent ?? DEFAULT_USER_AGENT,
|
|
22
|
-
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
|
23
|
-
'accept-language': 'en,*;q=0.5',
|
|
24
|
-
...(options.headers ?? {}),
|
|
25
|
-
},
|
|
26
|
-
});
|
|
27
|
-
const headers = pickHeaders(res.headers);
|
|
28
|
-
if (REDIRECT_STATUSES.has(res.status) && headers['location']) {
|
|
29
|
-
chain.push({ url: current, status: res.status });
|
|
30
|
-
let nextUrl;
|
|
31
|
-
try {
|
|
32
|
-
nextUrl = new URL(headers['location'], current).href;
|
|
33
|
-
}
|
|
34
|
-
catch {
|
|
35
|
-
return result(url, current, res.status, headers, null, chain, started, `Invalid Location header: ${headers['location']}`);
|
|
36
|
-
}
|
|
37
|
-
await res.body?.cancel().catch(() => undefined);
|
|
38
|
-
if (chain.some((h) => h.url === nextUrl) || nextUrl === current) {
|
|
39
|
-
return result(url, nextUrl, res.status, headers, null, chain, started, 'Redirect loop');
|
|
40
|
-
}
|
|
41
|
-
current = nextUrl;
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
let body = null;
|
|
45
|
-
if (readBody)
|
|
46
|
-
body = await readLimited(res, options.maxBodyBytes ?? 5_000_000);
|
|
47
|
-
else
|
|
48
|
-
await res.body?.cancel().catch(() => undefined);
|
|
49
|
-
return result(url, current, res.status, headers, body, chain, started);
|
|
50
|
-
}
|
|
51
|
-
catch (err) {
|
|
52
|
-
const message = err instanceof Error ? (err.name === 'AbortError' ? `Timed out after ${timeoutMs} ms` : err.message) : String(err);
|
|
53
|
-
return result(url, current, null, {}, null, chain, started, message);
|
|
54
|
-
}
|
|
55
|
-
finally {
|
|
56
|
-
clearTimeout(timer);
|
|
15
|
+
const pickHeaders = (headers) => {
|
|
16
|
+
const out = {};
|
|
17
|
+
for (const name of KEPT_HEADERS) {
|
|
18
|
+
const value = headers.get(name);
|
|
19
|
+
if (value !== null) {
|
|
20
|
+
out[name] = value;
|
|
57
21
|
}
|
|
58
22
|
}
|
|
59
|
-
return
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
23
|
+
return out;
|
|
24
|
+
};
|
|
25
|
+
const result = (requestedUrl, finalUrl, status, headers, body, chain, started, error) => {
|
|
26
|
+
const r = {
|
|
27
|
+
requestedUrl,
|
|
28
|
+
finalUrl,
|
|
29
|
+
status,
|
|
30
|
+
headers,
|
|
31
|
+
body,
|
|
32
|
+
redirectChain: chain,
|
|
33
|
+
timeMs: Math.round(performance.now() - started),
|
|
34
|
+
contentType: headers["content-type"] ?? null,
|
|
35
|
+
};
|
|
36
|
+
if (error) {
|
|
37
|
+
r.error = error;
|
|
38
|
+
}
|
|
39
|
+
return r;
|
|
40
|
+
};
|
|
41
|
+
const readLimited = async (res, maxBytes) => {
|
|
42
|
+
if (!res.body) {
|
|
43
|
+
return "";
|
|
44
|
+
}
|
|
68
45
|
const reader = res.body.getReader();
|
|
69
46
|
const chunks = [];
|
|
70
47
|
let total = 0;
|
|
48
|
+
let truncated = false;
|
|
71
49
|
for (;;) {
|
|
50
|
+
// oxlint-disable-next-line eslint/no-await-in-loop -- a stream hands over its chunks one after another
|
|
72
51
|
const { done, value } = await reader.read();
|
|
73
|
-
if (done)
|
|
52
|
+
if (done) {
|
|
74
53
|
break;
|
|
54
|
+
}
|
|
75
55
|
if (value) {
|
|
76
56
|
chunks.push(value);
|
|
77
57
|
total += value.byteLength;
|
|
78
58
|
if (total >= maxBytes) {
|
|
79
|
-
|
|
59
|
+
truncated = true;
|
|
80
60
|
break;
|
|
81
61
|
}
|
|
82
62
|
}
|
|
83
63
|
}
|
|
84
|
-
|
|
64
|
+
if (truncated) {
|
|
65
|
+
await reader.cancel().catch(() => null);
|
|
66
|
+
}
|
|
67
|
+
const charset = /charset=(?<charset>[\w-]+)/iu.exec(res.headers.get("content-type") ?? "")?.groups?.charset;
|
|
85
68
|
try {
|
|
86
|
-
return new TextDecoder(charset ??
|
|
69
|
+
return new TextDecoder(charset ?? "utf-8").decode(Buffer.concat(chunks));
|
|
87
70
|
}
|
|
88
71
|
catch {
|
|
89
|
-
return new TextDecoder(
|
|
72
|
+
return new TextDecoder("utf-8").decode(Buffer.concat(chunks));
|
|
90
73
|
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
74
|
+
};
|
|
75
|
+
const fetchHop = async (walk, current) => {
|
|
76
|
+
const { url, options, readBody, timeoutMs, started, chain } = walk;
|
|
77
|
+
const controller = new AbortController();
|
|
78
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
79
|
+
try {
|
|
80
|
+
const res = await fetch(current, {
|
|
81
|
+
method: "GET",
|
|
82
|
+
redirect: "manual",
|
|
83
|
+
signal: controller.signal,
|
|
84
|
+
headers: {
|
|
85
|
+
"user-agent": options.userAgent ?? DEFAULT_USER_AGENT,
|
|
86
|
+
accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
87
|
+
"accept-language": "en,*;q=0.5",
|
|
88
|
+
...options.headers,
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
const headers = pickHeaders(res.headers);
|
|
92
|
+
if (REDIRECT_STATUSES.has(res.status) && headers["location"]) {
|
|
93
|
+
chain.push({ url: current, status: res.status });
|
|
94
|
+
let nextUrl;
|
|
95
|
+
try {
|
|
96
|
+
nextUrl = new URL(headers["location"], current).href;
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return {
|
|
100
|
+
done: result(url, current, res.status, headers, null, chain, started, `Invalid Location header: ${headers["location"]}`),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
await res.body?.cancel().catch(() => null);
|
|
104
|
+
if (chain.some((h) => h.url === nextUrl) || nextUrl === current) {
|
|
105
|
+
return {
|
|
106
|
+
done: result(url, nextUrl, res.status, headers, null, chain, started, "Redirect loop"),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return { next: nextUrl };
|
|
110
|
+
}
|
|
111
|
+
let body = null;
|
|
112
|
+
if (readBody) {
|
|
113
|
+
body = await readLimited(res, options.maxBodyBytes ?? 5_000_000);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
await res.body?.cancel().catch(() => null);
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
done: result(url, current, res.status, headers, body, chain, started),
|
|
120
|
+
};
|
|
98
121
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
122
|
+
catch (error) {
|
|
123
|
+
let message = String(error);
|
|
124
|
+
if (error instanceof Error) {
|
|
125
|
+
message =
|
|
126
|
+
error.name === "AbortError"
|
|
127
|
+
? `Timed out after ${timeoutMs} ms`
|
|
128
|
+
: error.message;
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
done: result(url, current, null, {}, null, chain, started, message),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
clearTimeout(timer);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
/** GET a URL, following redirects manually so the chain is recorded. */
|
|
139
|
+
export const fetchPage = async (url, options = {}, readBody = true) => {
|
|
140
|
+
const maxRedirects = options.maxRedirects ?? 10;
|
|
141
|
+
const walk = {
|
|
142
|
+
url,
|
|
143
|
+
options,
|
|
144
|
+
readBody,
|
|
145
|
+
timeoutMs: options.timeoutMs ?? 15_000,
|
|
146
|
+
started: performance.now(),
|
|
147
|
+
chain: [],
|
|
111
148
|
};
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
149
|
+
let current = url;
|
|
150
|
+
for (let hop = 0; hop <= maxRedirects; hop += 1) {
|
|
151
|
+
// oxlint-disable-next-line eslint/no-await-in-loop -- each request goes where the one before it redirected
|
|
152
|
+
const outcome = await fetchHop(walk, current);
|
|
153
|
+
if ("done" in outcome) {
|
|
154
|
+
return outcome.done;
|
|
155
|
+
}
|
|
156
|
+
current = outcome.next;
|
|
157
|
+
}
|
|
158
|
+
return result(url, current, null, {}, null, walk.chain, walk.started, `More than ${maxRedirects} redirects`);
|
|
159
|
+
};
|
|
160
|
+
/** Status/redirect check without downloading the body. */
|
|
161
|
+
export const probeUrl = (url, options = {}) => fetchPage(url, options, false);
|
|
162
|
+
export const toResponseInfo = (r) => {
|
|
117
163
|
const info = {
|
|
118
164
|
status: r.status,
|
|
119
165
|
finalUrl: r.finalUrl,
|
|
@@ -122,14 +168,19 @@ export function toResponseInfo(r) {
|
|
|
122
168
|
contentType: r.contentType,
|
|
123
169
|
headers: r.headers,
|
|
124
170
|
};
|
|
125
|
-
if (r.error)
|
|
171
|
+
if (r.error) {
|
|
126
172
|
info.error = r.error;
|
|
173
|
+
}
|
|
127
174
|
return info;
|
|
128
|
-
}
|
|
129
|
-
export
|
|
130
|
-
const ct = r.contentType?.toLowerCase() ??
|
|
131
|
-
if (ct)
|
|
132
|
-
return ct.includes(
|
|
133
|
-
|
|
134
|
-
|
|
175
|
+
};
|
|
176
|
+
export const isHtmlResponse = (r) => {
|
|
177
|
+
const ct = r.contentType?.toLowerCase() ?? "";
|
|
178
|
+
if (ct) {
|
|
179
|
+
return ct.includes("text/html") || ct.includes("application/xhtml");
|
|
180
|
+
}
|
|
181
|
+
if (!r.body) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
return /<html|<!doctype/iu.test(r.body.slice(0, 2000));
|
|
185
|
+
};
|
|
135
186
|
//# sourceMappingURL=fetch.js.map
|
package/dist/core/fetch.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch.js","sourceRoot":"","sources":["../../src/core/fetch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAGxC,MAAM,CAAC,MAAM,kBAAkB,GAAG,8CAA8C,OAAO,wDAAwD,CAAC;AAEhJ,MAAM,YAAY,GAAG,
|
|
1
|
+
{"version":3,"file":"fetch.js","sourceRoot":"","sources":["../../src/core/fetch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AnswerSignals } from "../types.js";
|
|
2
|
+
import type { Document } from "./dom.js";
|
|
3
|
+
/** Reads what the AEO rules need from a parsed document; `bodyText` is its visible body text. */
|
|
4
|
+
export declare const extractAnswerSignals: (doc: Document, bodyText: string) => AnswerSignals;
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { hasChildren, isTag, isText } from "domhandler";
|
|
2
|
+
import { attr, childElements, HIDDEN_ELEMENTS, queryAll, queryOne, text, visibleText, } from "./dom.js";
|
|
3
|
+
import { collapseWhitespace, wordCount } from "./text.js";
|
|
4
|
+
/** Meta names whose content holds robots directives for one crawler or for all of them. */
|
|
5
|
+
const ROBOTS_META_NAMES = new Set([
|
|
6
|
+
"robots",
|
|
7
|
+
"googlebot",
|
|
8
|
+
"googlebot-news",
|
|
9
|
+
"bingbot",
|
|
10
|
+
]);
|
|
11
|
+
/** Containers FAQ widgets put a question in when it is not a heading. */
|
|
12
|
+
const QUESTION_CONTAINERS = new Set(["summary", "dt"]);
|
|
13
|
+
/** Elements an answer usually starts with. */
|
|
14
|
+
const ANSWER_BLOCKS = new Set([
|
|
15
|
+
"p",
|
|
16
|
+
"li",
|
|
17
|
+
"dd",
|
|
18
|
+
"blockquote",
|
|
19
|
+
"td",
|
|
20
|
+
"figcaption",
|
|
21
|
+
"pre",
|
|
22
|
+
]);
|
|
23
|
+
/** Page chrome: lists and tables here are navigation, not content. */
|
|
24
|
+
const CHROME = new Set(["nav", "header", "footer", "aside"]);
|
|
25
|
+
/** Roots JavaScript frameworks render into; empty in the HTML when the page renders client-side. */
|
|
26
|
+
const APP_ROOTS = [
|
|
27
|
+
"#root",
|
|
28
|
+
"#app",
|
|
29
|
+
"#__next",
|
|
30
|
+
"#__nuxt",
|
|
31
|
+
"#___gatsby",
|
|
32
|
+
"#svelte",
|
|
33
|
+
"app-root",
|
|
34
|
+
"[data-reactroot]",
|
|
35
|
+
"[ng-app]",
|
|
36
|
+
];
|
|
37
|
+
/*
|
|
38
|
+
* Written dates in the visible text. Every quantifier is bounded and every alternative is a fixed
|
|
39
|
+
* word, so each regular expression stays linear on long page text.
|
|
40
|
+
*/
|
|
41
|
+
const DATE_PATTERNS = [
|
|
42
|
+
/\b\d{4}-\d{2}-\d{2}\b/u,
|
|
43
|
+
/\b\d{1,2}[./-]\d{1,2}[./-]\d{4}\b/u,
|
|
44
|
+
/\b(?:jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|june?|july?|aug(?:ust)?|sep(?:t(?:ember)?)?|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?)\.? \d{1,2}(?:st|nd|rd|th)?,? \d{4}\b/iu,
|
|
45
|
+
/\b\d{1,2}(?:st|nd|rd|th)?\.? (?:jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|june?|july?|aug(?:ust)?|sep(?:t(?:ember)?)?|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?)\.?,? \d{4}\b/iu,
|
|
46
|
+
];
|
|
47
|
+
const MAX_QUESTIONS = 100;
|
|
48
|
+
/** A question ends with a question mark; a long "heading" is a paragraph styled as one. */
|
|
49
|
+
const isQuestion = (value) => value.length > 0 && value.length <= 300 && /[??]$/u.test(value);
|
|
50
|
+
const isHeading = (el) => /^h[1-6]$/u.test(el.name);
|
|
51
|
+
const inChrome = (el) => {
|
|
52
|
+
for (let p = el.parent; p && isTag(p); p = p.parent) {
|
|
53
|
+
if (CHROME.has(p.name)) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* What a heading, `<summary>` or `<dt>` does to the question being read: "subsection" goes on with
|
|
61
|
+
* its section, "content" is ordinary text, a question starts a new section and null ends it.
|
|
62
|
+
*/
|
|
63
|
+
const headingEffect = (el, current) => {
|
|
64
|
+
const heading = isHeading(el);
|
|
65
|
+
const label = collapseWhitespace(visibleText(el));
|
|
66
|
+
const question = isQuestion(label);
|
|
67
|
+
const level = heading ? Number(el.name.slice(1)) : 0;
|
|
68
|
+
if (!question) {
|
|
69
|
+
// "Why choose us?" answered by one card per reason, each under its own subheading, is answered.
|
|
70
|
+
if (heading && current && current.level > 0 && level > current.level) {
|
|
71
|
+
return "subsection";
|
|
72
|
+
}
|
|
73
|
+
// A <summary> or <dt> ends a section only when it asks a question itself.
|
|
74
|
+
return heading ? null : "content";
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
level,
|
|
78
|
+
text: label.slice(0, 200),
|
|
79
|
+
sectionWords: 0,
|
|
80
|
+
firstBlockWords: null,
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
/** Question headings in document order, with how much text follows each before the next one. */
|
|
84
|
+
const findQuestions = (body) => {
|
|
85
|
+
const questions = [];
|
|
86
|
+
let current = null;
|
|
87
|
+
const walk = (nodes) => {
|
|
88
|
+
for (const node of nodes) {
|
|
89
|
+
if (isText(node)) {
|
|
90
|
+
if (current) {
|
|
91
|
+
current.sectionWords += wordCount(node.data);
|
|
92
|
+
}
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (!isTag(node)) {
|
|
96
|
+
if (hasChildren(node)) {
|
|
97
|
+
walk(node.children);
|
|
98
|
+
}
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (HIDDEN_ELEMENTS.has(node.name)) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (isHeading(node) || QUESTION_CONTAINERS.has(node.name)) {
|
|
105
|
+
const effect = headingEffect(node, current);
|
|
106
|
+
if (effect === "subsection") {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (effect !== "content") {
|
|
110
|
+
current = effect;
|
|
111
|
+
if (current && questions.length < MAX_QUESTIONS) {
|
|
112
|
+
questions.push(current);
|
|
113
|
+
}
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (current &&
|
|
118
|
+
current.firstBlockWords === null &&
|
|
119
|
+
ANSWER_BLOCKS.has(node.name)) {
|
|
120
|
+
current.firstBlockWords = wordCount(visibleText(node));
|
|
121
|
+
}
|
|
122
|
+
walk(node.children);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
walk(body.children);
|
|
126
|
+
return questions;
|
|
127
|
+
};
|
|
128
|
+
/** A short selector for a `data-nosnippet` element: its tag plus its id or first class. */
|
|
129
|
+
const nosnippetSelector = (el) => {
|
|
130
|
+
const id = attr(el, "id")?.trim();
|
|
131
|
+
const firstClass = attr(el, "class")?.trim().split(/\s+/u)[0];
|
|
132
|
+
let { name } = el;
|
|
133
|
+
if (id) {
|
|
134
|
+
name += `#${id}`;
|
|
135
|
+
}
|
|
136
|
+
else if (firstClass) {
|
|
137
|
+
name += `.${firstClass}`;
|
|
138
|
+
}
|
|
139
|
+
return `${name.slice(0, 80)}[data-nosnippet]`;
|
|
140
|
+
};
|
|
141
|
+
const hasNosnippetAncestor = (el) => {
|
|
142
|
+
for (let p = el.parent; p && isTag(p); p = p.parent) {
|
|
143
|
+
if (Object.hasOwn(p.attribs, "data-nosnippet")) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return false;
|
|
148
|
+
};
|
|
149
|
+
const emptyAppRoot = (body) => {
|
|
150
|
+
for (const selector of APP_ROOTS) {
|
|
151
|
+
const el = queryOne(body, selector);
|
|
152
|
+
if (el && wordCount(visibleText(el)) === 0) {
|
|
153
|
+
return selector;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
};
|
|
158
|
+
const emptySignals = (robotsMeta) => ({
|
|
159
|
+
robotsMeta,
|
|
160
|
+
questions: [],
|
|
161
|
+
lists: 0,
|
|
162
|
+
tables: 0,
|
|
163
|
+
nosnippetElements: 0,
|
|
164
|
+
nosnippetWords: 0,
|
|
165
|
+
nosnippetSelectors: [],
|
|
166
|
+
emptyAppRoot: null,
|
|
167
|
+
noscriptAsksForJavaScript: false,
|
|
168
|
+
timeElements: 0,
|
|
169
|
+
mentionsDate: false,
|
|
170
|
+
});
|
|
171
|
+
/** Reads what the AEO rules need from a parsed document; `bodyText` is its visible body text. */
|
|
172
|
+
export const extractAnswerSignals = (doc, bodyText) => {
|
|
173
|
+
const robotsMeta = queryAll(doc, "meta[name]").flatMap((el) => {
|
|
174
|
+
const name = (attr(el, "name") ?? "").trim().toLowerCase();
|
|
175
|
+
const content = collapseWhitespace(attr(el, "content") ?? "").toLowerCase();
|
|
176
|
+
return ROBOTS_META_NAMES.has(name) && content ? [{ name, content }] : [];
|
|
177
|
+
});
|
|
178
|
+
const body = queryOne(doc, "body");
|
|
179
|
+
if (!body) {
|
|
180
|
+
return emptySignals(robotsMeta);
|
|
181
|
+
}
|
|
182
|
+
const nosnippet = queryAll(body, "[data-nosnippet]").filter((el) => !hasNosnippetAncestor(el));
|
|
183
|
+
return {
|
|
184
|
+
robotsMeta,
|
|
185
|
+
questions: findQuestions(body),
|
|
186
|
+
lists: queryAll(body, "ul, ol").filter((el) => childElements(el).filter((c) => c.name === "li").length >= 2 &&
|
|
187
|
+
!inChrome(el)).length,
|
|
188
|
+
tables: queryAll(body, "table").filter((el) => queryAll(el, "tr").length >= 2 && !inChrome(el)).length,
|
|
189
|
+
nosnippetElements: nosnippet.length,
|
|
190
|
+
nosnippetWords: nosnippet.reduce((sum, el) => sum + wordCount(visibleText(el)), 0),
|
|
191
|
+
nosnippetSelectors: nosnippet.slice(0, 10).map(nosnippetSelector),
|
|
192
|
+
emptyAppRoot: emptyAppRoot(body),
|
|
193
|
+
noscriptAsksForJavaScript: queryAll(body, "noscript").some((el) => /javascript/iu.test(text(el))),
|
|
194
|
+
timeElements: queryAll(body, "time").length,
|
|
195
|
+
mentionsDate: DATE_PATTERNS.some((re) => re.test(bodyText)),
|
|
196
|
+
};
|
|
197
|
+
};
|
|
198
|
+
//# sourceMappingURL=answer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"answer.js","sourceRoot":"","sources":["../../../src/core/html/answer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AnyNode, Document, Element } from "domhandler";
|
|
2
|
+
export type { AnyNode, Document, Element } from "domhandler";
|
|
3
|
+
/** Elements whose content is never visible page text. */
|
|
4
|
+
export declare const HIDDEN_ELEMENTS: Set<string>;
|
|
5
|
+
/** The text a reader sees under `root`: hidden elements left out, whitespace collapsed. */
|
|
6
|
+
export declare const visibleText: (root: AnyNode) => string;
|
|
7
|
+
/** Parse a full HTML document with the WHATWG algorithm (parse5), scripting enabled like a browser. */
|
|
8
|
+
export declare const parseDocument: (html: string) => Document;
|
|
9
|
+
/** Descendants of `root` matching a CSS selector, in document order. */
|
|
10
|
+
export declare const queryAll: (root: AnyNode, selector: string) => Element[];
|
|
11
|
+
/** First descendant of `root` matching a CSS selector. */
|
|
12
|
+
export declare const queryOne: (root: AnyNode, selector: string) => Element | null;
|
|
13
|
+
export declare const attr: (el: Element, name: string) => string | undefined;
|
|
14
|
+
export declare const hasAttr: (el: Element, name: string) => boolean;
|
|
15
|
+
/** DOM `textContent`: concatenated text of all descendants, comments excluded. */
|
|
16
|
+
export declare const text: (node: AnyNode | AnyNode[]) => string;
|
|
17
|
+
export declare const childElements: (el: Element) => Element[];
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { selectAll, selectOne } from "css-select";
|
|
2
|
+
import { hasChildren, isTag, isText } from "domhandler";
|
|
3
|
+
import { textContent } from "domutils";
|
|
4
|
+
import { parse } from "parse5";
|
|
5
|
+
import { adapter } from "parse5-htmlparser2-tree-adapter";
|
|
6
|
+
import { collapseWhitespace } from "./text.js";
|
|
7
|
+
/** Elements whose content is never visible page text. */
|
|
8
|
+
export const HIDDEN_ELEMENTS = new Set([
|
|
9
|
+
"script",
|
|
10
|
+
"style",
|
|
11
|
+
"noscript",
|
|
12
|
+
"template",
|
|
13
|
+
"svg",
|
|
14
|
+
"iframe",
|
|
15
|
+
"head",
|
|
16
|
+
]);
|
|
17
|
+
/** Elements that break words apart; padded with spaces so adjacent words don't fuse. */
|
|
18
|
+
const BLOCK_ELEMENTS = new Set([
|
|
19
|
+
"p",
|
|
20
|
+
"div",
|
|
21
|
+
"li",
|
|
22
|
+
"h1",
|
|
23
|
+
"h2",
|
|
24
|
+
"h3",
|
|
25
|
+
"h4",
|
|
26
|
+
"h5",
|
|
27
|
+
"h6",
|
|
28
|
+
"br",
|
|
29
|
+
"tr",
|
|
30
|
+
"td",
|
|
31
|
+
"th",
|
|
32
|
+
"section",
|
|
33
|
+
"article",
|
|
34
|
+
"header",
|
|
35
|
+
"footer",
|
|
36
|
+
"nav",
|
|
37
|
+
"blockquote",
|
|
38
|
+
"pre",
|
|
39
|
+
"dd",
|
|
40
|
+
"dt",
|
|
41
|
+
"figcaption",
|
|
42
|
+
]);
|
|
43
|
+
/** The text a reader sees under `root`: hidden elements left out, whitespace collapsed. */
|
|
44
|
+
export const visibleText = (root) => {
|
|
45
|
+
const parts = [];
|
|
46
|
+
const walk = (nodes) => {
|
|
47
|
+
for (const node of nodes) {
|
|
48
|
+
if (isText(node)) {
|
|
49
|
+
parts.push(node.data);
|
|
50
|
+
}
|
|
51
|
+
else if (isTag(node)) {
|
|
52
|
+
if (HIDDEN_ELEMENTS.has(node.name)) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const block = BLOCK_ELEMENTS.has(node.name);
|
|
56
|
+
if (block) {
|
|
57
|
+
parts.push(" ");
|
|
58
|
+
}
|
|
59
|
+
walk(node.children);
|
|
60
|
+
if (block) {
|
|
61
|
+
parts.push(" ");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else if (hasChildren(node)) {
|
|
65
|
+
walk(node.children);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
walk(hasChildren(root) ? root.children : [root]);
|
|
70
|
+
return collapseWhitespace(parts.join(""));
|
|
71
|
+
};
|
|
72
|
+
/** Parse a full HTML document with the WHATWG algorithm (parse5), scripting enabled like a browser. */
|
|
73
|
+
export const parseDocument = (html) => parse(html, { scriptingEnabled: true, treeAdapter: adapter });
|
|
74
|
+
/** Descendants of `root` matching a CSS selector, in document order. */
|
|
75
|
+
export const queryAll = (root, selector) => selectAll(selector, root);
|
|
76
|
+
/** First descendant of `root` matching a CSS selector. */
|
|
77
|
+
export const queryOne = (root, selector) => selectOne(selector, root);
|
|
78
|
+
export const attr = (el, name) => Object.hasOwn(el.attribs, name) ? el.attribs[name] : undefined;
|
|
79
|
+
export const hasAttr = (el, name) => Object.hasOwn(el.attribs, name);
|
|
80
|
+
/** DOM `textContent`: concatenated text of all descendants, comments excluded. */
|
|
81
|
+
export const text = (node) => textContent(node);
|
|
82
|
+
export const childElements = (el) => el.children.filter(isTag);
|
|
83
|
+
//# sourceMappingURL=dom.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dom.js","sourceRoot":"","sources":["../../../src/core/html/dom.ts"],"names":[],"mappings":"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"}
|