recker 1.0.78 → 1.0.79-next.70f027c
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/dist/browser/browser/mini.d.ts +2 -2
- package/dist/browser/browser/recker-mini.d.ts +8 -8
- package/dist/browser/browser/recker.d.ts +11 -8
- package/dist/browser/browser/recker.js +54 -6
- package/dist/browser/core/client.d.ts +15 -10
- package/dist/browser/core/client.js +54 -38
- package/dist/browser/index.iife.min.js +129 -130
- package/dist/browser/index.min.js +129 -130
- package/dist/browser/index.mini.iife.js +5697 -636
- package/dist/browser/index.mini.iife.min.js +47 -48
- package/dist/browser/index.mini.min.js +47 -48
- package/dist/browser/index.mini.umd.js +5697 -636
- package/dist/browser/index.mini.umd.min.js +47 -48
- package/dist/browser/index.umd.min.js +129 -130
- package/dist/browser/mini.d.ts +2 -2
- package/dist/browser/plugins/proxy-rotator.d.ts +2 -2
- package/dist/browser/plugins/proxy-rotator.js +6 -28
- package/dist/browser/recker-mini.d.ts +8 -8
- package/dist/browser/recker.d.ts +11 -8
- package/dist/browser/recker.js +54 -6
- package/dist/browser/scrape/document.js +2 -2
- package/dist/browser/scrape/element.js +7 -1
- package/dist/browser/scrape/parser/nodes/html.js +1 -1
- package/dist/browser/scrape/spider.d.ts +52 -0
- package/dist/browser/scrape/spider.js +620 -38
- package/dist/browser/scrape/types.d.ts +2 -0
- package/dist/browser/search/google.d.ts +26 -1
- package/dist/browser/search/google.js +427 -45
- package/dist/browser/seo/analyzer.d.ts +2 -0
- package/dist/browser/seo/analyzer.js +190 -5
- package/dist/browser/seo/formatter.js +20 -2
- package/dist/browser/seo/index.d.ts +1 -1
- package/dist/browser/seo/keyword-campaign-analyzer.d.ts +2 -0
- package/dist/browser/seo/keyword-campaign-analyzer.js +538 -0
- package/dist/browser/seo/keyword-campaign-seed-advanced.d.ts +17 -0
- package/dist/browser/seo/keyword-campaign-seed-advanced.js +269 -0
- package/dist/browser/seo/keyword-campaign-seed-core.d.ts +29 -0
- package/dist/browser/seo/keyword-campaign-seed-core.js +525 -0
- package/dist/browser/seo/keyword-campaign-shared.d.ts +165 -0
- package/dist/browser/seo/keyword-campaign-shared.js +59 -0
- package/dist/browser/seo/keyword-campaign.d.ts +4 -107
- package/dist/browser/seo/keyword-campaign.js +2 -380
- package/dist/browser/seo/keywords.js +5 -22
- package/dist/browser/seo/types.d.ts +26 -0
- package/dist/browser/transport/curl.d.ts +5 -1
- package/dist/browser/transport/curl.js +207 -50
- package/dist/browser/transport/undici.d.ts +4 -3
- package/dist/browser/transport/undici.js +123 -49
- package/dist/browser/types/index.d.ts +9 -3
- package/dist/browser/utils/binary-manager.js +26 -3
- package/dist/browser/utils/block-detector.d.ts +8 -0
- package/dist/browser/utils/block-detector.js +542 -7
- package/dist/cli/commands/hls-runner.js +5 -4
- package/dist/cli/commands/live-runner.js +5 -4
- package/dist/cli/commands/loadtest-runner.js +3 -2
- package/dist/cli/commands/search.d.ts +2 -0
- package/dist/cli/commands/search.js +105 -0
- package/dist/cli/commands/seo-runner.d.ts +2 -0
- package/dist/cli/commands/seo-runner.js +13 -7
- package/dist/cli/commands/seo.js +140 -1
- package/dist/cli/commands/serve.js +75 -131
- package/dist/cli/commands/server-runner.js +59 -82
- package/dist/cli/commands/spider-runner.d.ts +37 -1
- package/dist/cli/commands/spider-runner.js +134 -10
- package/dist/cli/commands/spider.d.ts +18 -1
- package/dist/cli/commands/spider.js +457 -27
- package/dist/cli/events/handlers/cli.js +30 -1
- package/dist/cli/events/handlers/tui.js +26 -0
- package/dist/cli/events/types.d.ts +27 -0
- package/dist/cli/handler.d.ts +2 -12
- package/dist/cli/handler.js +20 -15
- package/dist/cli/handlers/protocols.js +39 -12
- package/dist/cli/handlers/search.d.ts +2 -0
- package/dist/cli/handlers/search.js +171 -0
- package/dist/cli/handlers/seo-analyze.d.ts +1 -0
- package/dist/cli/handlers/seo-analyze.js +666 -0
- package/dist/cli/handlers/seo-robots.d.ts +1 -0
- package/dist/cli/handlers/seo-robots.js +76 -0
- package/dist/cli/handlers/seo-serp.d.ts +54 -0
- package/dist/cli/handlers/seo-serp.js +243 -0
- package/dist/cli/handlers/seo-sitemap.d.ts +1 -0
- package/dist/cli/handlers/seo-sitemap.js +55 -0
- package/dist/cli/handlers/seo-spider.d.ts +1 -0
- package/dist/cli/handlers/seo-spider.js +334 -0
- package/dist/cli/handlers/seo.d.ts +8 -4
- package/dist/cli/handlers/seo.js +294 -436
- package/dist/cli/handlers/spider.js +94 -17
- package/dist/cli/handlers/streaming.js +5 -1
- package/dist/cli/index.js +11 -2
- package/dist/cli/presets.d.ts +1 -1
- package/dist/cli/presets.js +15 -4
- package/dist/cli/tui/app.js +6 -1
- package/dist/cli/tui/components/rich-response.d.ts +74 -0
- package/dist/cli/tui/components/rich-response.js +118 -1
- package/dist/cli/tui/executor-commands/background.js +30 -24
- package/dist/cli/tui/executor-commands/testing.js +3 -2
- package/dist/cli/tui/hooks/useDomains.d.ts +17 -0
- package/dist/cli/tui/hooks/useHelp.js +15 -2
- package/dist/cli/tui/job-manager.d.ts +4 -4
- package/dist/cli/tui/job-manager.js +5 -1
- package/dist/cli/tui/spider-tui.d.ts +63 -0
- package/dist/cli/tui/spider-tui.js +120 -2
- package/dist/cli/types.d.ts +12 -0
- package/dist/cli/types.js +1 -0
- package/dist/cli/utils/option-helpers.d.ts +10 -0
- package/dist/cli/utils/option-helpers.js +63 -0
- package/dist/cli/utils/score-color.d.ts +11 -0
- package/dist/cli/utils/score-color.js +11 -0
- package/dist/cli/utils/serp-campaign.d.ts +53 -0
- package/dist/cli/utils/serp-campaign.js +53 -0
- package/dist/cli/utils/serp-config.d.ts +26 -0
- package/dist/cli/utils/serp-config.js +125 -0
- package/dist/core/client.d.ts +15 -10
- package/dist/core/client.js +54 -38
- package/dist/mcp/cli.js +35 -34
- package/dist/mcp/client.d.ts +2 -2
- package/dist/mcp/client.js +20 -2
- package/dist/mcp/contract.d.ts +1 -1
- package/dist/mcp/profiles.js +5 -1
- package/dist/mcp/prompts/index.js +8 -4
- package/dist/mcp/resources/index.js +46 -23
- package/dist/mcp/server.js +9 -6
- package/dist/mcp/tools/protocols.js +9 -2
- package/dist/mcp/tools/registry.js +13 -3
- package/dist/mcp/tools/seo.js +427 -2
- package/dist/mcp/types.d.ts +5 -1
- package/dist/plugins/proxy-rotator.d.ts +2 -2
- package/dist/plugins/proxy-rotator.js +6 -28
- package/dist/recker.d.ts +10 -7
- package/dist/recker.js +56 -6
- package/dist/scrape/document.js +2 -2
- package/dist/scrape/element.js +7 -1
- package/dist/scrape/parser/nodes/html.js +1 -1
- package/dist/scrape/spider.d.ts +52 -0
- package/dist/scrape/spider.js +620 -38
- package/dist/scrape/types.d.ts +2 -0
- package/dist/search/google.d.ts +26 -1
- package/dist/search/google.js +427 -45
- package/dist/search/index.d.ts +1 -1
- package/dist/seo/analyzer.d.ts +2 -0
- package/dist/seo/analyzer.js +190 -5
- package/dist/seo/formatter.js +20 -2
- package/dist/seo/index.d.ts +1 -1
- package/dist/seo/keyword-campaign-analyzer.d.ts +2 -0
- package/dist/seo/keyword-campaign-analyzer.js +538 -0
- package/dist/seo/keyword-campaign-seed-advanced.d.ts +17 -0
- package/dist/seo/keyword-campaign-seed-advanced.js +269 -0
- package/dist/seo/keyword-campaign-seed-core.d.ts +29 -0
- package/dist/seo/keyword-campaign-seed-core.js +525 -0
- package/dist/seo/keyword-campaign-shared.d.ts +165 -0
- package/dist/seo/keyword-campaign-shared.js +59 -0
- package/dist/seo/keyword-campaign.d.ts +4 -107
- package/dist/seo/keyword-campaign.js +2 -380
- package/dist/seo/keywords.js +5 -22
- package/dist/seo/types.d.ts +26 -0
- package/dist/template/index.d.ts +1 -1
- package/dist/template/types.d.ts +0 -2
- package/dist/testing/index.d.ts +1 -22
- package/dist/testing/index.js +1 -11
- package/dist/transport/curl.d.ts +5 -1
- package/dist/transport/curl.js +207 -50
- package/dist/transport/undici.d.ts +4 -3
- package/dist/transport/undici.js +123 -49
- package/dist/types/index.d.ts +9 -3
- package/dist/utils/binary-manager.js +26 -3
- package/dist/utils/block-detector.d.ts +8 -0
- package/dist/utils/block-detector.js +542 -7
- package/dist/version.js +1 -1
- package/package.json +8 -1
- package/dist/testing/mock-dns-server.d.ts +0 -69
- package/dist/testing/mock-dns-server.js +0 -269
- package/dist/testing/mock-ftp-server.d.ts +0 -89
- package/dist/testing/mock-ftp-server.js +0 -562
- package/dist/testing/mock-hls-server.d.ts +0 -80
- package/dist/testing/mock-hls-server.js +0 -381
- package/dist/testing/mock-http-server.d.ts +0 -124
- package/dist/testing/mock-http-server.js +0 -343
- package/dist/testing/mock-proxy-server.d.ts +0 -108
- package/dist/testing/mock-proxy-server.js +0 -615
- package/dist/testing/mock-sse-server.d.ts +0 -76
- package/dist/testing/mock-sse-server.js +0 -291
- package/dist/testing/mock-telnet-server.d.ts +0 -59
- package/dist/testing/mock-telnet-server.js +0 -274
- package/dist/testing/mock-udp-server.d.ts +0 -43
- package/dist/testing/mock-udp-server.js +0 -188
- package/dist/testing/mock-websocket-server.d.ts +0 -76
- package/dist/testing/mock-websocket-server.js +0 -334
- package/dist/testing/mock-whois-server.d.ts +0 -56
- package/dist/testing/mock-whois-server.js +0 -234
- package/dist/testing/proxy-certs.d.ts +0 -19
- package/dist/testing/proxy-certs.js +0 -208
package/dist/seo/analyzer.js
CHANGED
|
@@ -39,6 +39,7 @@ export class SeoAnalyzer {
|
|
|
39
39
|
const socialLinkDetails = this.analyzeSocialLinks(links, socialDomains);
|
|
40
40
|
const headings = this.analyzeHeadings();
|
|
41
41
|
const content = this.analyzeContent(headings);
|
|
42
|
+
const contentSections = this.extractContentSectionSignals(headings);
|
|
42
43
|
const linkAnalysis = this.buildLinkAnalysis(links);
|
|
43
44
|
const imageAnalysis = this.buildImageAnalysis(images);
|
|
44
45
|
const social = this.buildSocialAnalysis(og, twitter);
|
|
@@ -48,6 +49,16 @@ export class SeoAnalyzer {
|
|
|
48
49
|
const feeds = this.analyzeFeeds();
|
|
49
50
|
const conversion = this.analyzeConversionElements(links, visibleText);
|
|
50
51
|
const pageType = this.detectPageType(jsonLd);
|
|
52
|
+
const linkAnchorTexts = links
|
|
53
|
+
.map((link) => link.text?.trim())
|
|
54
|
+
.filter((text) => Boolean(text))
|
|
55
|
+
.filter((text) => text.length <= 140)
|
|
56
|
+
.filter((text) => !text.match(/^\s*$/))
|
|
57
|
+
.slice(0, 80);
|
|
58
|
+
const linkUrlSamples = links
|
|
59
|
+
.map((link) => extractReadablePathFromUrl(link.href))
|
|
60
|
+
.filter((sample) => sample.length > 0)
|
|
61
|
+
.slice(0, 80);
|
|
51
62
|
const context = this.buildRuleContext({
|
|
52
63
|
pageType,
|
|
53
64
|
meta,
|
|
@@ -71,7 +82,7 @@ export class SeoAnalyzer {
|
|
|
71
82
|
const ruleResults = this.rulesEngine.evaluate(context);
|
|
72
83
|
const checks = this.convertToCheckResults(ruleResults);
|
|
73
84
|
const { score, grade } = this.calculateScore(ruleResults);
|
|
74
|
-
const summary = this.buildSummary(
|
|
85
|
+
const summary = this.buildSummary(checks, {
|
|
75
86
|
content,
|
|
76
87
|
imageAnalysis,
|
|
77
88
|
linkAnalysis,
|
|
@@ -124,12 +135,15 @@ export class SeoAnalyzer {
|
|
|
124
135
|
items: jsonLd,
|
|
125
136
|
},
|
|
126
137
|
headings: headings,
|
|
138
|
+
contentSections,
|
|
127
139
|
content,
|
|
128
140
|
keywords,
|
|
129
141
|
links: linkAnalysis,
|
|
130
142
|
images: imageAnalysis,
|
|
131
143
|
social,
|
|
132
144
|
technical,
|
|
145
|
+
linkAnchorTexts,
|
|
146
|
+
linkUrlSamples,
|
|
133
147
|
};
|
|
134
148
|
}
|
|
135
149
|
getMainBody() {
|
|
@@ -181,6 +195,97 @@ export class SeoAnalyzer {
|
|
|
181
195
|
return 'other';
|
|
182
196
|
}
|
|
183
197
|
}
|
|
198
|
+
extractContentSectionSignals(headings) {
|
|
199
|
+
const body = this.getMainBody();
|
|
200
|
+
if (!body)
|
|
201
|
+
return [];
|
|
202
|
+
const candidateTags = 'h1,h2,h3,h4,h5,h6,p,li,ol,ul,article,section,main,figure,figcaption,blockquote,table,th,td';
|
|
203
|
+
const nodes = body.querySelectorAll(candidateTags);
|
|
204
|
+
const normalizeSignalText = (value) => value
|
|
205
|
+
.replace(/\s+/g, ' ')
|
|
206
|
+
.replace(/[,\.;:!?()[\]{}"“”']/g, '')
|
|
207
|
+
.trim()
|
|
208
|
+
.toLowerCase();
|
|
209
|
+
const headingStack = [];
|
|
210
|
+
const seen = new Set();
|
|
211
|
+
const results = [];
|
|
212
|
+
const pushSectionSignal = (entry) => {
|
|
213
|
+
const fingerprint = `${entry.tagName}|${entry.headingPath.join(' > ')}|${entry.text.slice(0, 120)}`;
|
|
214
|
+
if (seen.has(fingerprint))
|
|
215
|
+
return;
|
|
216
|
+
seen.add(fingerprint);
|
|
217
|
+
results.push(entry);
|
|
218
|
+
};
|
|
219
|
+
for (const node of nodes) {
|
|
220
|
+
const tagName = node.tagName.toLowerCase();
|
|
221
|
+
if (/^h[1-6]$/.test(tagName)) {
|
|
222
|
+
const level = Number.parseInt(tagName.slice(1), 10);
|
|
223
|
+
const text = normalizeSignalText(node.text);
|
|
224
|
+
if (!text || text.length < 4) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
while (headingStack.length > 0 && headingStack[headingStack.length - 1].level >= level) {
|
|
228
|
+
headingStack.pop();
|
|
229
|
+
}
|
|
230
|
+
headingStack.push({ level, text });
|
|
231
|
+
const headingPath = headingStack.map((entry) => entry.text);
|
|
232
|
+
const source = {
|
|
233
|
+
headingPath,
|
|
234
|
+
source: 'heading',
|
|
235
|
+
tagName,
|
|
236
|
+
headingLevel: level,
|
|
237
|
+
text,
|
|
238
|
+
wordCount: text.split(/\s+/).length,
|
|
239
|
+
linkDensity: 0,
|
|
240
|
+
weight: 1.45,
|
|
241
|
+
};
|
|
242
|
+
pushSectionSignal(source);
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
if (tagName === 'ul' || tagName === 'ol') {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
const text = normalizeSignalText(node.text);
|
|
249
|
+
if (!text || text.split(/\s+/).length < 4) {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
const words = text.split(/\s+/);
|
|
253
|
+
const wordCount = words.length;
|
|
254
|
+
if (wordCount < 4)
|
|
255
|
+
continue;
|
|
256
|
+
const anchorCount = node.querySelectorAll('a').length;
|
|
257
|
+
const linkDensity = wordCount > 0
|
|
258
|
+
? Math.min(1, anchorCount / Math.max(1, wordCount))
|
|
259
|
+
: 0;
|
|
260
|
+
if (linkDensity > 0.85)
|
|
261
|
+
continue;
|
|
262
|
+
const headingPath = headingStack.length > 0
|
|
263
|
+
? headingStack.map((entry) => entry.text)
|
|
264
|
+
: headings.h1Count > 0
|
|
265
|
+
? ['conteúdo']
|
|
266
|
+
: ['home'];
|
|
267
|
+
const sourceLabel = tagName === 'li' ? 'list-item'
|
|
268
|
+
: tagName === 'figure' || tagName === 'figcaption' ? 'figure'
|
|
269
|
+
: (tagName === 'table' || tagName === 'th' || tagName === 'td') ? 'table'
|
|
270
|
+
: 'paragraph';
|
|
271
|
+
const headingLevel = headingStack.length > 0 ? headingStack[headingStack.length - 1].level : 1;
|
|
272
|
+
const baseWeight = sourceLabel === 'paragraph' ? 1.25 : 1.05;
|
|
273
|
+
const adjustedWeight = baseWeight * (1 - (linkDensity * 0.4));
|
|
274
|
+
pushSectionSignal({
|
|
275
|
+
headingPath,
|
|
276
|
+
source: sourceLabel,
|
|
277
|
+
tagName,
|
|
278
|
+
headingLevel,
|
|
279
|
+
text,
|
|
280
|
+
wordCount,
|
|
281
|
+
linkDensity,
|
|
282
|
+
weight: Math.max(0.5, adjustedWeight),
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
return results
|
|
286
|
+
.filter((entry) => entry.text.length >= 20)
|
|
287
|
+
.slice(0, 160);
|
|
288
|
+
}
|
|
184
289
|
getVisibleText() {
|
|
185
290
|
const body = this.getMainBody();
|
|
186
291
|
if (!body)
|
|
@@ -951,6 +1056,27 @@ export class SeoAnalyzer {
|
|
|
951
1056
|
}
|
|
952
1057
|
return undefined;
|
|
953
1058
|
}
|
|
1059
|
+
classifyInfoCheck(result) {
|
|
1060
|
+
if (result.status !== 'info')
|
|
1061
|
+
return undefined;
|
|
1062
|
+
const message = (result.message || '').toLowerCase();
|
|
1063
|
+
const evidenceIssue = (result.evidence?.issue || '').toLowerCase();
|
|
1064
|
+
const recommendation = (result.recommendation || '').toLowerCase();
|
|
1065
|
+
const context = `${message} ${evidenceIssue} ${recommendation}`;
|
|
1066
|
+
if (/not\s+applicable/.test(context) ||
|
|
1067
|
+
/not\s+available/.test(context) ||
|
|
1068
|
+
/data\s+unavailable/.test(context) ||
|
|
1069
|
+
/context\s+unavailable/.test(context) ||
|
|
1070
|
+
/unable\s+to\s+check/.test(context) ||
|
|
1071
|
+
/cannot\s+verify/.test(context) ||
|
|
1072
|
+
/cannot\s+be\s+determined/.test(context) ||
|
|
1073
|
+
/\bunavailable\b/.test(context) ||
|
|
1074
|
+
/not\s+set/.test(context) ||
|
|
1075
|
+
/not\s+present/.test(context)) {
|
|
1076
|
+
return 'not_applicable';
|
|
1077
|
+
}
|
|
1078
|
+
return 'suggestion';
|
|
1079
|
+
}
|
|
954
1080
|
convertToCheckResults(results) {
|
|
955
1081
|
return results.map((r) => ({
|
|
956
1082
|
id: r.id,
|
|
@@ -959,26 +1085,36 @@ export class SeoAnalyzer {
|
|
|
959
1085
|
severity: r.severity,
|
|
960
1086
|
status: r.status,
|
|
961
1087
|
message: r.message,
|
|
1088
|
+
infoType: this.classifyInfoCheck(r),
|
|
962
1089
|
value: r.value,
|
|
963
1090
|
recommendation: r.recommendation,
|
|
964
1091
|
evidence: r.evidence,
|
|
965
1092
|
}));
|
|
966
1093
|
}
|
|
967
|
-
buildSummary(
|
|
1094
|
+
buildSummary(checks, data) {
|
|
968
1095
|
const pageType = data.pageType;
|
|
969
1096
|
const timings = data.timings;
|
|
970
1097
|
const passed = checks.filter((c) => c.status === 'pass').length;
|
|
971
1098
|
const warnings = checks.filter((c) => c.status === 'warn').length;
|
|
972
1099
|
const errors = checks.filter((c) => c.status === 'fail').length;
|
|
973
1100
|
const infos = checks.filter((c) => c.status === 'info').length;
|
|
1101
|
+
const notApplicable = checks.filter((c) => c.status === 'info' && c.infoType === 'not_applicable').length;
|
|
1102
|
+
const suggestions = checks.filter((c) => c.status === 'info' && c.infoType !== 'not_applicable').length;
|
|
974
1103
|
const totalChecks = checks.length;
|
|
975
1104
|
const scoringChecks = totalChecks - infos;
|
|
976
1105
|
const passRate = scoringChecks > 0 ? Math.round((passed / scoringChecks) * 100) : 100;
|
|
977
1106
|
const issuesByCategory = {};
|
|
978
|
-
for (const result of
|
|
1107
|
+
for (const result of checks) {
|
|
979
1108
|
const cat = result.category;
|
|
980
1109
|
if (!issuesByCategory[cat]) {
|
|
981
|
-
issuesByCategory[cat] = {
|
|
1110
|
+
issuesByCategory[cat] = {
|
|
1111
|
+
passed: 0,
|
|
1112
|
+
warnings: 0,
|
|
1113
|
+
errors: 0,
|
|
1114
|
+
infos: 0,
|
|
1115
|
+
notApplicable: 0,
|
|
1116
|
+
suggestions: 0,
|
|
1117
|
+
};
|
|
982
1118
|
}
|
|
983
1119
|
if (result.status === 'pass')
|
|
984
1120
|
issuesByCategory[cat].passed++;
|
|
@@ -986,6 +1122,15 @@ export class SeoAnalyzer {
|
|
|
986
1122
|
issuesByCategory[cat].warnings++;
|
|
987
1123
|
else if (result.status === 'fail')
|
|
988
1124
|
issuesByCategory[cat].errors++;
|
|
1125
|
+
else if (result.status === 'info') {
|
|
1126
|
+
issuesByCategory[cat].infos++;
|
|
1127
|
+
if (result.infoType === 'not_applicable') {
|
|
1128
|
+
issuesByCategory[cat].notApplicable++;
|
|
1129
|
+
}
|
|
1130
|
+
else {
|
|
1131
|
+
issuesByCategory[cat].suggestions++;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
989
1134
|
}
|
|
990
1135
|
const topIssues = checks
|
|
991
1136
|
.filter((c) => c.status === 'fail' || c.status === 'warn')
|
|
@@ -1052,6 +1197,8 @@ export class SeoAnalyzer {
|
|
|
1052
1197
|
warnings,
|
|
1053
1198
|
errors,
|
|
1054
1199
|
infos,
|
|
1200
|
+
notApplicable,
|
|
1201
|
+
suggestions,
|
|
1055
1202
|
passRate,
|
|
1056
1203
|
issuesByCategory,
|
|
1057
1204
|
pageType: pageType,
|
|
@@ -1179,7 +1326,7 @@ export class SeoAnalyzer {
|
|
|
1179
1326
|
const level = parseInt(tagName.substring(1), 10);
|
|
1180
1327
|
const text = el.text.trim();
|
|
1181
1328
|
counts[level] = (counts[level] || 0) + 1;
|
|
1182
|
-
structure.push({ level, text: text.slice(0,
|
|
1329
|
+
structure.push({ level, text: text.slice(0, 120), count: 1 });
|
|
1183
1330
|
if (level === 2) {
|
|
1184
1331
|
if (inSection) {
|
|
1185
1332
|
sectionWordCounts.push(currentSectionWordCount);
|
|
@@ -1219,8 +1366,24 @@ export class SeoAnalyzer {
|
|
|
1219
1366
|
else if (counts[1] > 1) {
|
|
1220
1367
|
issues.push('Multiple H1 tags');
|
|
1221
1368
|
}
|
|
1369
|
+
const tree = [];
|
|
1370
|
+
const stack = [];
|
|
1371
|
+
for (const heading of structure) {
|
|
1372
|
+
const node = { level: heading.level, text: heading.text, children: [] };
|
|
1373
|
+
while (stack.length > 0 && stack[stack.length - 1].level >= heading.level) {
|
|
1374
|
+
stack.pop();
|
|
1375
|
+
}
|
|
1376
|
+
if (stack.length === 0) {
|
|
1377
|
+
tree.push(node);
|
|
1378
|
+
}
|
|
1379
|
+
else {
|
|
1380
|
+
stack[stack.length - 1].node.children.push(node);
|
|
1381
|
+
}
|
|
1382
|
+
stack.push({ node, level: heading.level });
|
|
1383
|
+
}
|
|
1222
1384
|
return {
|
|
1223
1385
|
structure,
|
|
1386
|
+
tree,
|
|
1224
1387
|
h1Count: counts[1],
|
|
1225
1388
|
hasProperHierarchy,
|
|
1226
1389
|
issues,
|
|
@@ -1453,6 +1616,28 @@ export class SeoAnalyzer {
|
|
|
1453
1616
|
return this.rulesEngine.getCategories();
|
|
1454
1617
|
}
|
|
1455
1618
|
}
|
|
1619
|
+
function extractReadablePathFromUrl(value) {
|
|
1620
|
+
try {
|
|
1621
|
+
const parsed = new URL(value);
|
|
1622
|
+
const fragments = parsed.pathname
|
|
1623
|
+
.split('/')
|
|
1624
|
+
.filter((segment) => segment.length > 0)
|
|
1625
|
+
.slice(0, 3)
|
|
1626
|
+
.map((segment) => segment.replace(/[-_]/g, ' '))
|
|
1627
|
+
.join(' ')
|
|
1628
|
+
.replace(/\d+/g, ' ')
|
|
1629
|
+
.replace(/\s+/g, ' ')
|
|
1630
|
+
.trim();
|
|
1631
|
+
const host = parsed.hostname.replace(/^www\./, '').replace(/\./g, ' ');
|
|
1632
|
+
if (fragments.length > 0) {
|
|
1633
|
+
return `${host} ${fragments}`.trim();
|
|
1634
|
+
}
|
|
1635
|
+
return host;
|
|
1636
|
+
}
|
|
1637
|
+
catch {
|
|
1638
|
+
return '';
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1456
1641
|
export async function analyzeSeo(html, options = {}) {
|
|
1457
1642
|
const analyzer = await SeoAnalyzer.fromHtml(html, options);
|
|
1458
1643
|
return analyzer.analyze();
|
package/dist/seo/formatter.js
CHANGED
|
@@ -29,7 +29,16 @@ export function statusIcon(status) {
|
|
|
29
29
|
export function formatCheck(check, showEvidence = false) {
|
|
30
30
|
const lines = [];
|
|
31
31
|
const icon = statusIcon(check.status);
|
|
32
|
-
|
|
32
|
+
let infoLabel = '';
|
|
33
|
+
if (check.status === 'info') {
|
|
34
|
+
if (check.infoType === 'not_applicable') {
|
|
35
|
+
infoLabel = colors.magenta(' [N/A]');
|
|
36
|
+
}
|
|
37
|
+
else if (check.infoType === 'suggestion') {
|
|
38
|
+
infoLabel = colors.cyan(' [SUGGESTION]');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
lines.push(` ${icon} ${check.message}${infoLabel}`);
|
|
33
42
|
if (check.value !== undefined) {
|
|
34
43
|
lines.push(` ${colors.gray('Value:')} ${check.value}`);
|
|
35
44
|
}
|
|
@@ -239,7 +248,10 @@ export function formatSeoReport(report, url, options = {}) {
|
|
|
239
248
|
lines.push(colors.gray(' ──────────────────────────────────────────────────'));
|
|
240
249
|
lines.push('');
|
|
241
250
|
lines.push(` ${colors.bold('Summary')}`);
|
|
242
|
-
|
|
251
|
+
const infoSummary = (s.notApplicable !== undefined || s.suggestions !== undefined)
|
|
252
|
+
? ` (${s.notApplicable ?? 0} not applicable, ${s.suggestions ?? 0} suggestions)`
|
|
253
|
+
: '';
|
|
254
|
+
lines.push(` ${colors.green('✔ Passed:')} ${s.passed} ${colors.yellow('⚠ Warnings:')} ${s.warnings} ${colors.red('✖ Errors:')} ${s.errors} ${colors.blue('ℹ Info:')} ${s.infos}${infoSummary}`);
|
|
243
255
|
if (s.vitals) {
|
|
244
256
|
lines.push(` ${colors.gray('Words:')} ${s.vitals.wordCount} | ${colors.gray('Images:')} ${s.vitals.imageCount} | ${colors.gray('Links:')} ${s.vitals.linkCount}`);
|
|
245
257
|
}
|
|
@@ -381,12 +393,16 @@ function groupChecksByCategory(checks) {
|
|
|
381
393
|
const warnings = catChecks.filter(c => c.status === 'warn').length;
|
|
382
394
|
const errors = catChecks.filter(c => c.status === 'fail').length;
|
|
383
395
|
const infos = catChecks.filter(c => c.status === 'info').length;
|
|
396
|
+
const notApplicable = catChecks.filter(c => c.status === 'info' && c.infoType === 'not_applicable').length;
|
|
397
|
+
const suggestions = catChecks.filter(c => c.status === 'info' && c.infoType !== 'not_applicable').length;
|
|
384
398
|
const total = catChecks.length - infos;
|
|
385
399
|
result[cat] = {
|
|
386
400
|
passed,
|
|
387
401
|
warnings,
|
|
388
402
|
errors,
|
|
389
403
|
infos,
|
|
404
|
+
notApplicable,
|
|
405
|
+
suggestions,
|
|
390
406
|
passRate: total > 0 ? Math.round((passed / total) * 100) : 100,
|
|
391
407
|
checks: catChecks,
|
|
392
408
|
};
|
|
@@ -410,6 +426,8 @@ export function formatSeoReportJson(report, url, options) {
|
|
|
410
426
|
warnings: report.checks.filter(c => c.status === 'warn').length,
|
|
411
427
|
errors: report.checks.filter(c => c.status === 'fail').length,
|
|
412
428
|
infos: report.checks.filter(c => c.status === 'info').length,
|
|
429
|
+
notApplicable: report.checks.filter(c => c.status === 'info' && c.infoType === 'not_applicable').length,
|
|
430
|
+
suggestions: report.checks.filter(c => c.status === 'info' && c.infoType !== 'not_applicable').length,
|
|
413
431
|
passRate: report.summary.passRate,
|
|
414
432
|
completeness: report.summary.completeness,
|
|
415
433
|
topIssues: report.summary.topIssues,
|
package/dist/seo/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export type { SeoSpiderOptions, SeoPageResult, SiteWideIssue, SeoSpiderResult, }
|
|
|
6
6
|
export { SeoRulesEngine, createRulesEngine, SEO_THRESHOLDS, ALL_SEO_RULES, } from './rules/index.js';
|
|
7
7
|
export { generateSeoFilename, resolveOutputPath, writeReport, formatReportForJson, } from './output.js';
|
|
8
8
|
export type { SeoOutputType, OutputOptions, WriteOptions } from './output.js';
|
|
9
|
-
export type { SeoReport, SeoCheckResult, SeoStatus, SeoPageType, SeoTiming, HeadingAnalysis, HeadingInfo, ContentMetrics, LinkAnalysis, ImageAnalysis, SocialMetaAnalysis, TechnicalSeo, SeoAnalyzerOptions, } from './types.js';
|
|
9
|
+
export type { SeoReport, SeoCheckResult, SeoStatus, SeoPageType, SeoTiming, HeadingAnalysis, HeadingInfo, HeadingNode, ContentMetrics, LinkAnalysis, ImageAnalysis, SocialMetaAnalysis, TechnicalSeo, SeoAnalyzerOptions, } from './types.js';
|
|
10
10
|
export type { SeoRule, RuleContext, RuleResult, RuleEvidence, RuleCategory, RuleSeverity, RulesEngineOptions, } from './rules/index.js';
|
|
11
11
|
export type { SeoAnalyzerFullOptions } from './analyzer.js';
|
|
12
12
|
export { parseRobotsTxt, validateRobotsTxt, isPathAllowed, fetchAndValidateRobotsTxt, } from './validators/robots.js';
|