@rankcli/agent-runtime 0.0.38 → 0.0.39
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/index.d.mts +38 -1
- package/dist/index.d.ts +38 -1
- package/dist/index.js +243 -125
- package/dist/index.mjs +210 -95
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -263,7 +263,7 @@ async function crawlUrl(params) {
|
|
|
263
263
|
async function extractMeta(params) {
|
|
264
264
|
const { html, url } = params;
|
|
265
265
|
try {
|
|
266
|
-
const $ =
|
|
266
|
+
const $ = cheerio43.load(html);
|
|
267
267
|
const meta = {
|
|
268
268
|
title: $("title").text().trim() || void 0,
|
|
269
269
|
description: $('meta[name="description"]').attr("content")?.trim(),
|
|
@@ -308,7 +308,7 @@ async function extractMeta(params) {
|
|
|
308
308
|
async function analyzeHeadings(params) {
|
|
309
309
|
const { html } = params;
|
|
310
310
|
try {
|
|
311
|
-
const $ =
|
|
311
|
+
const $ = cheerio43.load(html);
|
|
312
312
|
const headings = [];
|
|
313
313
|
$("h1, h2, h3, h4, h5, h6").each((_, el) => {
|
|
314
314
|
const tag = el.tagName.toLowerCase();
|
|
@@ -329,7 +329,7 @@ async function analyzeHeadings(params) {
|
|
|
329
329
|
async function extractImages2(params) {
|
|
330
330
|
const { html } = params;
|
|
331
331
|
try {
|
|
332
|
-
const $ =
|
|
332
|
+
const $ = cheerio43.load(html);
|
|
333
333
|
const images = [];
|
|
334
334
|
$("img").each((_, el) => {
|
|
335
335
|
images.push({
|
|
@@ -351,7 +351,7 @@ async function extractImages2(params) {
|
|
|
351
351
|
async function extractLinks2(params) {
|
|
352
352
|
const { html, baseUrl } = params;
|
|
353
353
|
try {
|
|
354
|
-
const $ =
|
|
354
|
+
const $ = cheerio43.load(html);
|
|
355
355
|
const links = [];
|
|
356
356
|
const baseHostname = new URL(baseUrl).hostname;
|
|
357
357
|
$("a[href]").each((_, el) => {
|
|
@@ -386,7 +386,7 @@ async function extractLinks2(params) {
|
|
|
386
386
|
async function extractSchema2(params) {
|
|
387
387
|
const { html } = params;
|
|
388
388
|
try {
|
|
389
|
-
const $ =
|
|
389
|
+
const $ = cheerio43.load(html);
|
|
390
390
|
const schemas = [];
|
|
391
391
|
$('script[type="application/ld+json"]').each((_, el) => {
|
|
392
392
|
try {
|
|
@@ -455,7 +455,7 @@ async function checkSitemap2(params) {
|
|
|
455
455
|
timeout: 1e4
|
|
456
456
|
});
|
|
457
457
|
if (response.status === 200 && response.data.includes("<?xml")) {
|
|
458
|
-
const $ =
|
|
458
|
+
const $ = cheerio43.load(response.data, { xmlMode: true });
|
|
459
459
|
const urlCount = $("url").length || $("sitemap").length;
|
|
460
460
|
return {
|
|
461
461
|
success: true,
|
|
@@ -484,12 +484,12 @@ async function checkSitemap2(params) {
|
|
|
484
484
|
};
|
|
485
485
|
}
|
|
486
486
|
}
|
|
487
|
-
var
|
|
487
|
+
var cheerio43;
|
|
488
488
|
var init_crawl = __esm({
|
|
489
489
|
"src/tools/crawl.ts"() {
|
|
490
490
|
"use strict";
|
|
491
491
|
init_http();
|
|
492
|
-
|
|
492
|
+
cheerio43 = __toESM(require("cheerio"));
|
|
493
493
|
}
|
|
494
494
|
});
|
|
495
495
|
|
|
@@ -886,7 +886,7 @@ function analyzeRobotsTxtForAI(robotsTxt) {
|
|
|
886
886
|
return { allowed, blocked, recommendations };
|
|
887
887
|
}
|
|
888
888
|
function detectRenderingMode(html) {
|
|
889
|
-
const $ =
|
|
889
|
+
const $ = cheerio44.load(html);
|
|
890
890
|
const signals = [];
|
|
891
891
|
const hasNextData = html.includes("__NEXT_DATA__");
|
|
892
892
|
const hasNuxtData = html.includes("__NUXT__");
|
|
@@ -927,7 +927,7 @@ function detectRenderingMode(html) {
|
|
|
927
927
|
};
|
|
928
928
|
}
|
|
929
929
|
function analyzeContentStructure(html) {
|
|
930
|
-
const $ =
|
|
930
|
+
const $ = cheerio44.load(html);
|
|
931
931
|
const jsonLdScripts = $('script[type="application/ld+json"]');
|
|
932
932
|
let hasStructuredData = jsonLdScripts.length > 0;
|
|
933
933
|
let hasFAQSchema = false;
|
|
@@ -977,7 +977,7 @@ function analyzeContentStructure(html) {
|
|
|
977
977
|
};
|
|
978
978
|
}
|
|
979
979
|
function analyzeCitationReadiness(html) {
|
|
980
|
-
const $ =
|
|
980
|
+
const $ = cheerio44.load(html);
|
|
981
981
|
const bodyText = $("body").text();
|
|
982
982
|
const hasCitations = $('cite, blockquote, [class*="citation"], [class*="reference"]').length > 0 || /\[\d+\]|\(\d{4}\)/.test(bodyText);
|
|
983
983
|
const externalLinks = $('a[href^="http"]').filter((_, el) => {
|
|
@@ -1010,7 +1010,7 @@ function analyzeCitationReadiness(html) {
|
|
|
1010
1010
|
};
|
|
1011
1011
|
}
|
|
1012
1012
|
function analyzeEntityExtraction(html) {
|
|
1013
|
-
const $ =
|
|
1013
|
+
const $ = cheerio44.load(html);
|
|
1014
1014
|
const bodyText = $("body").text();
|
|
1015
1015
|
const definedTerms = [];
|
|
1016
1016
|
$("dfn, abbr[title]").each((_, el) => {
|
|
@@ -1046,7 +1046,7 @@ function analyzeEntityExtraction(html) {
|
|
|
1046
1046
|
};
|
|
1047
1047
|
}
|
|
1048
1048
|
function calculateLLMSignals(structure, citation, entity, html) {
|
|
1049
|
-
const $ =
|
|
1049
|
+
const $ = cheerio44.load(html);
|
|
1050
1050
|
let contentClarity = 0;
|
|
1051
1051
|
if (structure.headingHierarchy === "good") contentClarity += 30;
|
|
1052
1052
|
else if (structure.headingHierarchy === "needs-work") contentClarity += 15;
|
|
@@ -1352,11 +1352,11 @@ Allow: /
|
|
|
1352
1352
|
Sitemap: ${siteUrl}/sitemap.xml
|
|
1353
1353
|
`;
|
|
1354
1354
|
}
|
|
1355
|
-
var
|
|
1355
|
+
var cheerio44, AI_CRAWLERS, AI_CRAWLERS_INFO;
|
|
1356
1356
|
var init_geo_analyzer = __esm({
|
|
1357
1357
|
"src/analyzers/geo-analyzer.ts"() {
|
|
1358
1358
|
"use strict";
|
|
1359
|
-
|
|
1359
|
+
cheerio44 = __toESM(require("cheerio"));
|
|
1360
1360
|
AI_CRAWLERS = {
|
|
1361
1361
|
// OpenAI
|
|
1362
1362
|
GPTBot: { userAgent: "GPTBot", company: "OpenAI", purpose: "ChatGPT training & browsing" },
|
|
@@ -1715,7 +1715,7 @@ function generateCWVIssues(lcp, fidInp, cls, ttfb, url) {
|
|
|
1715
1715
|
return issues;
|
|
1716
1716
|
}
|
|
1717
1717
|
function analyzeCoreWebVitals(html, url, headers) {
|
|
1718
|
-
const $ =
|
|
1718
|
+
const $ = cheerio45.load(html);
|
|
1719
1719
|
const lcp = analyzeLCP(html, $);
|
|
1720
1720
|
const fidInp = analyzeFID_INP(html, $);
|
|
1721
1721
|
const cls = analyzeCLS(html, $);
|
|
@@ -1760,11 +1760,11 @@ function analyzeCoreWebVitals(html, url, headers) {
|
|
|
1760
1760
|
issues
|
|
1761
1761
|
};
|
|
1762
1762
|
}
|
|
1763
|
-
var
|
|
1763
|
+
var cheerio45;
|
|
1764
1764
|
var init_core_web_vitals_analyzer = __esm({
|
|
1765
1765
|
"src/analyzers/core-web-vitals-analyzer.ts"() {
|
|
1766
1766
|
"use strict";
|
|
1767
|
-
|
|
1767
|
+
cheerio45 = __toESM(require("cheerio"));
|
|
1768
1768
|
}
|
|
1769
1769
|
});
|
|
1770
1770
|
|
|
@@ -2099,7 +2099,7 @@ function validateSchema(data, type) {
|
|
|
2099
2099
|
};
|
|
2100
2100
|
}
|
|
2101
2101
|
function analyzeStructuredData2(html, url) {
|
|
2102
|
-
const $ =
|
|
2102
|
+
const $ = cheerio46.load(html);
|
|
2103
2103
|
const schemas = [];
|
|
2104
2104
|
const issues = [];
|
|
2105
2105
|
const recommendations = [];
|
|
@@ -2363,11 +2363,11 @@ function generateSchemaTemplate(pageType, options) {
|
|
|
2363
2363
|
}
|
|
2364
2364
|
return JSON.stringify(result, null, 2);
|
|
2365
2365
|
}
|
|
2366
|
-
var
|
|
2366
|
+
var cheerio46, SCHEMA_REQUIREMENTS;
|
|
2367
2367
|
var init_structured_data_analyzer = __esm({
|
|
2368
2368
|
"src/analyzers/structured-data-analyzer.ts"() {
|
|
2369
2369
|
"use strict";
|
|
2370
|
-
|
|
2370
|
+
cheerio46 = __toESM(require("cheerio"));
|
|
2371
2371
|
SCHEMA_REQUIREMENTS = {
|
|
2372
2372
|
"Organization": {
|
|
2373
2373
|
required: ["name", "url"],
|
|
@@ -2483,7 +2483,7 @@ function getImageFormat2(src) {
|
|
|
2483
2483
|
}
|
|
2484
2484
|
}
|
|
2485
2485
|
function analyzeImages3(html, url) {
|
|
2486
|
-
const $ =
|
|
2486
|
+
const $ = cheerio47.load(html);
|
|
2487
2487
|
const issues = [];
|
|
2488
2488
|
const recommendations = [];
|
|
2489
2489
|
let score = 100;
|
|
@@ -2701,11 +2701,11 @@ function generateResponsiveImage(options) {
|
|
|
2701
2701
|
/>
|
|
2702
2702
|
</picture>`;
|
|
2703
2703
|
}
|
|
2704
|
-
var
|
|
2704
|
+
var cheerio47, MODERN_FORMATS3, LEGACY_FORMATS2, MAX_RECOMMENDED_DIMENSION;
|
|
2705
2705
|
var init_image_optimization_analyzer = __esm({
|
|
2706
2706
|
"src/analyzers/image-optimization-analyzer.ts"() {
|
|
2707
2707
|
"use strict";
|
|
2708
|
-
|
|
2708
|
+
cheerio47 = __toESM(require("cheerio"));
|
|
2709
2709
|
MODERN_FORMATS3 = ["webp", "avif"];
|
|
2710
2710
|
LEGACY_FORMATS2 = ["jpg", "jpeg", "png", "gif", "bmp"];
|
|
2711
2711
|
MAX_RECOMMENDED_DIMENSION = 2e3;
|
|
@@ -2746,7 +2746,7 @@ function analyzeAnchorText2(text) {
|
|
|
2746
2746
|
};
|
|
2747
2747
|
}
|
|
2748
2748
|
function analyzeInternalLinking(html, url) {
|
|
2749
|
-
const $ =
|
|
2749
|
+
const $ = cheerio48.load(html);
|
|
2750
2750
|
const issues = [];
|
|
2751
2751
|
const recommendations = [];
|
|
2752
2752
|
let score = 100;
|
|
@@ -2985,11 +2985,11 @@ function suggestInternalLinks(content, availablePages) {
|
|
|
2985
2985
|
}
|
|
2986
2986
|
return suggestions.sort((a, b) => b.relevance - a.relevance).filter((s, i, arr) => arr.findIndex((x) => x.suggestedUrl === s.suggestedUrl) === i).slice(0, 10);
|
|
2987
2987
|
}
|
|
2988
|
-
var
|
|
2988
|
+
var cheerio48, GENERIC_ANCHOR_TEXTS;
|
|
2989
2989
|
var init_internal_linking_analyzer = __esm({
|
|
2990
2990
|
"src/analyzers/internal-linking-analyzer.ts"() {
|
|
2991
2991
|
"use strict";
|
|
2992
|
-
|
|
2992
|
+
cheerio48 = __toESM(require("cheerio"));
|
|
2993
2993
|
GENERIC_ANCHOR_TEXTS = [
|
|
2994
2994
|
"click here",
|
|
2995
2995
|
"read more",
|
|
@@ -3183,7 +3183,7 @@ function analyzeMobileSpecific($) {
|
|
|
3183
3183
|
};
|
|
3184
3184
|
}
|
|
3185
3185
|
function analyzeMobileSEO(html, url) {
|
|
3186
|
-
const $ =
|
|
3186
|
+
const $ = cheerio49.load(html);
|
|
3187
3187
|
const issues = [];
|
|
3188
3188
|
const recommendations = [];
|
|
3189
3189
|
let score = 100;
|
|
@@ -3314,11 +3314,11 @@ function analyzeMobileSEO(html, url) {
|
|
|
3314
3314
|
recommendations
|
|
3315
3315
|
};
|
|
3316
3316
|
}
|
|
3317
|
-
var
|
|
3317
|
+
var cheerio49;
|
|
3318
3318
|
var init_mobile_seo_analyzer = __esm({
|
|
3319
3319
|
"src/analyzers/mobile-seo-analyzer.ts"() {
|
|
3320
3320
|
"use strict";
|
|
3321
|
-
|
|
3321
|
+
cheerio49 = __toESM(require("cheerio"));
|
|
3322
3322
|
}
|
|
3323
3323
|
});
|
|
3324
3324
|
|
|
@@ -3330,6 +3330,7 @@ __export(index_exports, {
|
|
|
3330
3330
|
LOCATION_CODES: () => LOCATION_CODES,
|
|
3331
3331
|
OG_IMAGE_SPECS: () => OG_IMAGE_SPECS,
|
|
3332
3332
|
PRIORITY_WEIGHTS: () => PRIORITY_WEIGHTS,
|
|
3333
|
+
REDUNDANT_WHEN_PRESENT: () => REDUNDANT_WHEN_PRESENT,
|
|
3333
3334
|
RankTracker: () => RankTracker,
|
|
3334
3335
|
SEO_SCOPES: () => SEO_SCOPES,
|
|
3335
3336
|
SITE_PROFILE_QUESTIONS: () => SITE_PROFILE_QUESTIONS,
|
|
@@ -3413,6 +3414,7 @@ __export(index_exports, {
|
|
|
3413
3414
|
createPullRequest: () => createPullRequest,
|
|
3414
3415
|
createSEOCommit: () => createSEOCommit,
|
|
3415
3416
|
createSEOCommits: () => createSEOCommits,
|
|
3417
|
+
dedupeIssues: () => dedupeIssues,
|
|
3416
3418
|
describeConcerns: () => describeConcerns,
|
|
3417
3419
|
detectDuplicates: () => detectDuplicates,
|
|
3418
3420
|
detectFramework: () => detectFramework4,
|
|
@@ -3549,6 +3551,7 @@ __export(index_exports, {
|
|
|
3549
3551
|
loadAgent: () => loadAgent,
|
|
3550
3552
|
loadAgentByName: () => loadAgentByName,
|
|
3551
3553
|
mergePages: () => mergePages,
|
|
3554
|
+
metaRefreshTarget: () => metaRefreshTarget,
|
|
3552
3555
|
openAIKeywordLLM: () => openAIKeywordLLM,
|
|
3553
3556
|
optimizeForAI: () => optimizeForAI,
|
|
3554
3557
|
optimizeReadme: () => optimizeReadme,
|
|
@@ -6371,7 +6374,7 @@ var ISSUE_DEFINITIONS = {
|
|
|
6371
6374
|
severity: "warning",
|
|
6372
6375
|
category: "performance",
|
|
6373
6376
|
title: "HTML document too large",
|
|
6374
|
-
description: "HTML size exceeds
|
|
6377
|
+
description: "HTML size exceeds 300KB.",
|
|
6375
6378
|
impact: "Increases load time and memory usage.",
|
|
6376
6379
|
howToFix: "Reduce inline CSS/JS, remove unnecessary HTML."
|
|
6377
6380
|
},
|
|
@@ -9113,7 +9116,7 @@ async function analyzePerformance(url) {
|
|
|
9113
9116
|
details: { ttfb: `${ttfbTime}ms` }
|
|
9114
9117
|
});
|
|
9115
9118
|
}
|
|
9116
|
-
if (htmlSize >
|
|
9119
|
+
if (htmlSize > 300 * 1024) {
|
|
9117
9120
|
issues.push({
|
|
9118
9121
|
...ISSUE_DEFINITIONS.HTML_TOO_LARGE,
|
|
9119
9122
|
affectedUrls: [url],
|
|
@@ -11277,7 +11280,7 @@ function analyzeLocalSEO(html, url) {
|
|
|
11277
11280
|
const detectedPhones = detectPhones(html);
|
|
11278
11281
|
const detectedAddresses = detectAddresses(html);
|
|
11279
11282
|
const socialProfiles = detectSocialProfiles(html);
|
|
11280
|
-
if (!hasLocalBusiness) {
|
|
11283
|
+
if (!hasLocalBusiness && (detectedPhones.length > 0 || detectedAddresses.length > 0)) {
|
|
11281
11284
|
issues.push({
|
|
11282
11285
|
...ISSUE_DEFINITIONS.LOCAL_BUSINESS_SCHEMA_MISSING,
|
|
11283
11286
|
affectedUrls: [url]
|
|
@@ -12340,7 +12343,17 @@ function analyzeKeywords(html, url) {
|
|
|
12340
12343
|
const sortPhrases = (phrases, n) => {
|
|
12341
12344
|
return Array.from(phrases).filter(([_, count]) => count >= 2).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([phrase, count]) => ({ phrase, count, wordCount: n }));
|
|
12342
12345
|
};
|
|
12343
|
-
const
|
|
12346
|
+
const brandLabels = /* @__PURE__ */ new Set();
|
|
12347
|
+
try {
|
|
12348
|
+
const host = new URL(url).hostname.toLowerCase().replace(/^www\./, "");
|
|
12349
|
+
for (const label of host.split(".").slice(0, -1)) brandLabels.add(label);
|
|
12350
|
+
} catch {
|
|
12351
|
+
}
|
|
12352
|
+
for (const name of [$('meta[property="og:site_name"]').attr("content"), $('meta[name="application-name"]').attr("content")]) {
|
|
12353
|
+
for (const token of tokenize(name || "")) brandLabels.add(token);
|
|
12354
|
+
}
|
|
12355
|
+
const isBrandWord = (word) => [...brandLabels].some((label) => label.includes(word) || word.includes(label));
|
|
12356
|
+
const potentialStuffing = totalWords >= 200 ? topKeywords.filter((k) => k.density > 4 && !isBrandWord(k.word)).map((k) => k.word) : [];
|
|
12344
12357
|
if (potentialStuffing.length > 0) {
|
|
12345
12358
|
issues.push({
|
|
12346
12359
|
...ISSUE_DEFINITIONS.KEYWORD_STUFFING,
|
|
@@ -12567,13 +12580,6 @@ function checkAMP(html, url) {
|
|
|
12567
12580
|
}
|
|
12568
12581
|
};
|
|
12569
12582
|
}
|
|
12570
|
-
const isArticle = $("article").length > 0 || $('meta[property="og:type"][content="article"]').length > 0 || html.toLowerCase().includes("blog") || html.toLowerCase().includes("news");
|
|
12571
|
-
if (isArticle) {
|
|
12572
|
-
issues.push({
|
|
12573
|
-
...ISSUE_DEFINITIONS.AMP_MISSING,
|
|
12574
|
-
affectedUrls: [url]
|
|
12575
|
-
});
|
|
12576
|
-
}
|
|
12577
12583
|
return {
|
|
12578
12584
|
issues,
|
|
12579
12585
|
data: {
|
|
@@ -13285,13 +13291,13 @@ async function analyzeSiteMaturity(html, url) {
|
|
|
13285
13291
|
contentToAuthorityRatio
|
|
13286
13292
|
};
|
|
13287
13293
|
const { strategy, recommendations } = determineStrategy(maturityLevel, authorityIndicators, contentMetrics);
|
|
13288
|
-
if (maturityLevel === "new") {
|
|
13294
|
+
if (maturityLevel === "new" && ageSource === "wayback" && estimatedAgeDays !== null) {
|
|
13289
13295
|
issues.push({
|
|
13290
13296
|
code: "NEW_SITE_STRATEGY",
|
|
13291
13297
|
severity: "notice",
|
|
13292
13298
|
category: "content",
|
|
13293
13299
|
title: "New website detected - Authority-first strategy recommended",
|
|
13294
|
-
description:
|
|
13300
|
+
description: `The Wayback Machine first saw this site ${estimatedAgeDays} days ago. Standard SEO advice won't work well for a site under 90 days old.`,
|
|
13295
13301
|
impact: "Creating lots of content without authority is wasted effort. Content from new sites rarely ranks.",
|
|
13296
13302
|
howToFix: "Focus on building 10+ quality backlinks to ONE great page before expanding content. See strategic recommendations.",
|
|
13297
13303
|
affectedUrls: [url],
|
|
@@ -13302,24 +13308,7 @@ async function analyzeSiteMaturity(html, url) {
|
|
|
13302
13308
|
}
|
|
13303
13309
|
});
|
|
13304
13310
|
}
|
|
13305
|
-
if (
|
|
13306
|
-
issues.push({
|
|
13307
|
-
code: "CONTENT_AUTHORITY_IMBALANCE",
|
|
13308
|
-
severity: "warning",
|
|
13309
|
-
category: "content",
|
|
13310
|
-
title: "Content-to-authority imbalance",
|
|
13311
|
-
description: `Site has ~${indexedPages} pages but limited backlink signals. Content is unlikely to rank.`,
|
|
13312
|
-
impact: "Most of your content may be invisible to search engines due to low domain authority.",
|
|
13313
|
-
howToFix: "Stop publishing new content. Focus 100% on link building until you see ranking improvements.",
|
|
13314
|
-
affectedUrls: [url],
|
|
13315
|
-
details: {
|
|
13316
|
-
indexedPages,
|
|
13317
|
-
contentToAuthorityRatio,
|
|
13318
|
-
maturityLevel
|
|
13319
|
-
}
|
|
13320
|
-
});
|
|
13321
|
-
}
|
|
13322
|
-
if (maturityLevel === "growing" && !htmlSignals.hasBlog) {
|
|
13311
|
+
if (maturityLevel === "growing" && ageSource === "wayback" && !htmlSignals.hasBlog) {
|
|
13323
13312
|
issues.push({
|
|
13324
13313
|
code: "GROWING_SITE_NO_BLOG",
|
|
13325
13314
|
severity: "notice",
|
|
@@ -13813,8 +13802,8 @@ function analyzeRendering(html) {
|
|
|
13813
13802
|
const hasSemanticStructure = hasH1 && (hasMain || hasArticle);
|
|
13814
13803
|
const title = $("title").text().trim();
|
|
13815
13804
|
const h1Text = $("h1").first().text().trim();
|
|
13816
|
-
const
|
|
13817
|
-
const keyContentInHTML = title.length >
|
|
13805
|
+
const hasSubstantialParagraph = $("p").toArray().some((p) => $(p).text().trim().length > 50);
|
|
13806
|
+
const keyContentInHTML = title.length > 0 && h1Text.length > 0 && hasSubstantialParagraph;
|
|
13818
13807
|
const EXPECTED_MIN_CONTENT = 1e3;
|
|
13819
13808
|
const htmlContentRatio = Math.min(100, mainContentCharCount / EXPECTED_MIN_CONTENT * 100);
|
|
13820
13809
|
let aiReadableScore = 50;
|
|
@@ -14034,9 +14023,9 @@ function analyzeClientRendering(html, url) {
|
|
|
14034
14023
|
howToFix: "Ensure the initial HTML contains: meaningful <title>, descriptive <h1>, and a substantial first paragraph with your key message.",
|
|
14035
14024
|
affectedUrls: [url],
|
|
14036
14025
|
details: {
|
|
14037
|
-
hasTitle: $("title").text().trim().length >
|
|
14038
|
-
hasH1: $("h1").text().trim().length >
|
|
14039
|
-
|
|
14026
|
+
hasTitle: $("title").text().trim().length > 0,
|
|
14027
|
+
hasH1: $("h1").text().trim().length > 0,
|
|
14028
|
+
hasSubstantialParagraph: $("p").toArray().some((p) => $(p).text().trim().length > 50)
|
|
14040
14029
|
}
|
|
14041
14030
|
});
|
|
14042
14031
|
}
|
|
@@ -14748,7 +14737,9 @@ async function analyzeBingOptimization(html, url) {
|
|
|
14748
14737
|
detectedIssues.push("No Bing site verification");
|
|
14749
14738
|
issues.push({
|
|
14750
14739
|
code: "BING_NOT_VERIFIED",
|
|
14751
|
-
|
|
14740
|
+
// A notice: verification done through Search Console import or a DNS record this run
|
|
14741
|
+
// could not see leaves no trace in the HTML, so this cannot be a warning.
|
|
14742
|
+
severity: "notice",
|
|
14752
14743
|
category: "ai-readiness",
|
|
14753
14744
|
title: verification.dnsChecked ? "Site not verified with Bing Webmaster Tools" : "Bing Webmaster Tools verification not visible in the HTML",
|
|
14754
14745
|
description: verification.dnsChecked ? "No msvalidate.01 meta tag in the HTML, no MS= TXT record on the domain and no /BingSiteAuth.xml file. ChatGPT Search uses Bing's index. Verifying your site with Bing Webmaster Tools ensures proper indexing and access to Bing-specific SEO tools." : "No msvalidate.01 meta tag in the HTML and no /BingSiteAuth.xml file. The site may be verified through a DNS record instead, which this run could not check. ChatGPT Search uses Bing's index. Verifying your site with Bing Webmaster Tools ensures proper indexing and access to Bing-specific SEO tools.",
|
|
@@ -14799,7 +14790,7 @@ async function analyzeBingOptimization(html, url) {
|
|
|
14799
14790
|
});
|
|
14800
14791
|
}
|
|
14801
14792
|
const hasLocalBusinessSchema = bingPlacesDetected;
|
|
14802
|
-
const hasAddressOnPage = $("address").length > 0 || html
|
|
14793
|
+
const hasAddressOnPage = $("address").text().trim().length > 0 || detectAddresses(html).length > 0;
|
|
14803
14794
|
if (hasAddressOnPage && !hasLocalBusinessSchema) {
|
|
14804
14795
|
detectedIssues.push("Local business may not be on Bing Places");
|
|
14805
14796
|
issues.push({
|
|
@@ -16330,23 +16321,6 @@ function analyzeColorContrast(html, url) {
|
|
|
16330
16321
|
});
|
|
16331
16322
|
}
|
|
16332
16323
|
}
|
|
16333
|
-
if (cannotDetermine > elementsAnalyzed * 0.8) {
|
|
16334
|
-
issues.push({
|
|
16335
|
-
code: "A11Y_CONTRAST_UNDETERMINED",
|
|
16336
|
-
severity: "notice",
|
|
16337
|
-
category: "accessibility",
|
|
16338
|
-
title: "Color contrast could not be fully analyzed",
|
|
16339
|
-
description: "Most text colors are set via CSS stylesheets rather than inline styles. Full contrast checking requires browser rendering.",
|
|
16340
|
-
impact: "Potential contrast issues may exist that were not detected.",
|
|
16341
|
-
howToFix: "Use browser DevTools (Lighthouse accessibility audit) or dedicated tools like axe-core for comprehensive contrast checking.",
|
|
16342
|
-
affectedUrls: [url],
|
|
16343
|
-
details: {
|
|
16344
|
-
elementsAnalyzed,
|
|
16345
|
-
cannotDetermine,
|
|
16346
|
-
recommendation: "Run Lighthouse accessibility audit for complete contrast analysis."
|
|
16347
|
-
}
|
|
16348
|
-
});
|
|
16349
|
-
}
|
|
16350
16324
|
return {
|
|
16351
16325
|
issues,
|
|
16352
16326
|
data: {
|
|
@@ -17671,7 +17645,8 @@ async function analyzeTrackingVerification(html, url) {
|
|
|
17671
17645
|
if (!hasGa4 && !hasGtm && otherAnalytics.length === 0) {
|
|
17672
17646
|
issues.push({
|
|
17673
17647
|
code: "GA4_MISSING",
|
|
17674
|
-
|
|
17648
|
+
// A notice: which analytics a site runs is a choice, not an SEO defect.
|
|
17649
|
+
severity: "notice",
|
|
17675
17650
|
category: "on-page",
|
|
17676
17651
|
title: "Google Analytics 4 not detected",
|
|
17677
17652
|
description: "No GA4 tracking code found. GA4 is essential for measuring organic traffic and conversions.",
|
|
@@ -17684,7 +17659,8 @@ async function analyzeTrackingVerification(html, url) {
|
|
|
17684
17659
|
const dnsChecked = verification.dnsChecked;
|
|
17685
17660
|
issues.push({
|
|
17686
17661
|
code: "GSC_VERIFICATION_MISSING",
|
|
17687
|
-
|
|
17662
|
+
// A notice: a site verified by DNS, Google Analytics or Tag Manager leaves no trace here.
|
|
17663
|
+
severity: "notice",
|
|
17688
17664
|
category: "on-page",
|
|
17689
17665
|
title: dnsChecked ? "Google Search Console verification not found" : "Google Search Console verification not visible in the HTML",
|
|
17690
17666
|
description: dnsChecked ? "No google-site-verification meta tag in the HTML and no google-site-verification TXT record on the domain. GSC provides essential data about your search performance." : "No google-site-verification meta tag in the HTML. The site may be verified through a DNS TXT record instead, which this run could not check. GSC provides essential data about your search performance.",
|
|
@@ -17759,6 +17735,113 @@ async function analyzeTrackingVerification(html, url) {
|
|
|
17759
17735
|
return { issues, data };
|
|
17760
17736
|
}
|
|
17761
17737
|
|
|
17738
|
+
// src/audit/meta-refresh.ts
|
|
17739
|
+
var cheerio37 = __toESM(require("cheerio"));
|
|
17740
|
+
function metaRefreshTarget(html, pageUrl) {
|
|
17741
|
+
if (!/http-equiv\s*=\s*["']?refresh/i.test(html)) return null;
|
|
17742
|
+
const $ = cheerio37.load(html);
|
|
17743
|
+
for (const el of $("meta[http-equiv]").toArray()) {
|
|
17744
|
+
if (($(el).attr("http-equiv") || "").trim().toLowerCase() !== "refresh") continue;
|
|
17745
|
+
const content = $(el).attr("content") || "";
|
|
17746
|
+
const match = content.match(/^\s*(\d+(?:\.\d+)?)\s*[;,]\s*(?:url\s*=\s*)?['"]?\s*([^'"\s>]+)/i);
|
|
17747
|
+
if (!match) continue;
|
|
17748
|
+
if (Number(match[1]) > 5) continue;
|
|
17749
|
+
try {
|
|
17750
|
+
const target = new URL(match[2], pageUrl);
|
|
17751
|
+
if (target.protocol !== "http:" && target.protocol !== "https:") continue;
|
|
17752
|
+
const strip = (u) => u.origin + (u.pathname.replace(/\/+$/, "") || "/") + u.search;
|
|
17753
|
+
if (strip(target) === strip(new URL(pageUrl))) continue;
|
|
17754
|
+
return target.toString();
|
|
17755
|
+
} catch {
|
|
17756
|
+
continue;
|
|
17757
|
+
}
|
|
17758
|
+
}
|
|
17759
|
+
return null;
|
|
17760
|
+
}
|
|
17761
|
+
|
|
17762
|
+
// src/audit/dedupe.ts
|
|
17763
|
+
var REDUNDANT_WHEN_PRESENT = [
|
|
17764
|
+
// The same tag, reported by three checks.
|
|
17765
|
+
{ keep: "VIEWPORT_MISSING", drop: ["HTML_NO_VIEWPORT", "RESPONSIVE_NO_VIEWPORT"] },
|
|
17766
|
+
{ keep: "LANG_ATTR_MISSING", drop: ["HTML_NO_LANG"] },
|
|
17767
|
+
{ keep: "APPLE_TOUCH_ICON_MISSING", drop: ["HTML_NO_APPLE_TOUCH_ICON"] },
|
|
17768
|
+
{ keep: "LLMS_TXT_MISSING", drop: ["AI_NO_LLMS_TXT"] },
|
|
17769
|
+
{ keep: "INDEXNOW_NOT_IMPLEMENTED", drop: ["BING_NO_INDEXNOW"] },
|
|
17770
|
+
{ keep: "BING_VERIFICATION_MISSING", drop: ["BING_NOT_VERIFIED"] },
|
|
17771
|
+
{ keep: "SITEMAP_MISSING", drop: ["BING_SITEMAP_MISSING", "SITEMAP_NOT_IN_ROBOTS"] },
|
|
17772
|
+
{ keep: "ROBOTS_TXT_MISSING", drop: ["SITEMAP_NOT_IN_ROBOTS"] },
|
|
17773
|
+
{ keep: "IMAGES_MISSING_DIMENSIONS", drop: ["IMG_NO_DIMENSIONS"] },
|
|
17774
|
+
{ keep: "NO_MODERN_IMAGE_FORMATS", drop: ["IMAGE_NOT_WEBP_AVIF"] },
|
|
17775
|
+
{ keep: "SOCIAL_PROFILES_NOT_LINKED", drop: ["NO_SOCIAL_PROFILES"] },
|
|
17776
|
+
{ keep: "NO_EXPERIENCE_SIGNALS", drop: ["AI_NO_EXPERIENCE_SIGNALS"] },
|
|
17777
|
+
// No structured data at all makes every "your schema lacks X" finding moot.
|
|
17778
|
+
{
|
|
17779
|
+
keep: "SCHEMA_MISSING",
|
|
17780
|
+
drop: [
|
|
17781
|
+
"SCHEMA_ORG_MISSING",
|
|
17782
|
+
"NO_ORGANIZATION_SCHEMA",
|
|
17783
|
+
"ORGANIZATION_SCHEMA_MISSING",
|
|
17784
|
+
"NO_ENTITY_SCHEMA",
|
|
17785
|
+
"NO_DATE_MODIFIED_SCHEMA",
|
|
17786
|
+
"NO_MAIN_ENTITY_OF_PAGE",
|
|
17787
|
+
"AUTHOR_NO_SCHEMA",
|
|
17788
|
+
"AI_NO_REVIEW_SCHEMA",
|
|
17789
|
+
"NO_SCHEMA_SAMEAS"
|
|
17790
|
+
]
|
|
17791
|
+
},
|
|
17792
|
+
{ keep: "NO_ORGANIZATION_SCHEMA", drop: ["ORGANIZATION_SCHEMA_MISSING"] },
|
|
17793
|
+
// No title, no H1: the findings about their wording are implied.
|
|
17794
|
+
{ keep: "TITLE_MISSING", drop: ["LOW_CTR_POTENTIAL", "NO_KEYWORDS_IN_TITLE", "TITLE_TOO_SHORT"] },
|
|
17795
|
+
{ keep: "META_DESC_MISSING", drop: ["LOW_CTR_POTENTIAL"] },
|
|
17796
|
+
{ keep: "H1_MISSING", drop: ["NO_VISIBLE_HEADLINE", "NO_KEYWORDS_IN_H1", "H1_MISSING_KEYWORD"] },
|
|
17797
|
+
// Client-side rendering, diagnosed once. CLIENT_SIDE_RENDERING carries the per-framework fix.
|
|
17798
|
+
{
|
|
17799
|
+
keep: "CLIENT_SIDE_RENDERING",
|
|
17800
|
+
drop: [
|
|
17801
|
+
"REACT_CSR_DETECTED",
|
|
17802
|
+
"VUE_CSR_DETECTED",
|
|
17803
|
+
"ANGULAR_NO_UNIVERSAL",
|
|
17804
|
+
"SPA_WITHOUT_SSR",
|
|
17805
|
+
"EMPTY_BODY_CONTENT",
|
|
17806
|
+
"HIGH_JS_RENDERING_RATIO",
|
|
17807
|
+
"AI_LOW_HTML_READABILITY"
|
|
17808
|
+
]
|
|
17809
|
+
},
|
|
17810
|
+
// An empty body has no vocabulary to be undiverse.
|
|
17811
|
+
{ keep: "EMPTY_BODY_CONTENT", drop: ["THIN_CONTENT", "LOW_CONTENT_ENTROPY", "HIGH_CONTENT_REPETITION", "LOW_DWELL_TIME_CONTENT", "LOW_TEXT_HTML_RATIO"] },
|
|
17812
|
+
{ keep: "THIN_CONTENT", drop: ["LOW_DWELL_TIME_CONTENT"] },
|
|
17813
|
+
{ keep: "NO_INTERNAL_LINKS", drop: ["NO_CONTENT_INTERNAL_LINKS", "HIGH_ORPHAN_RISK", "LINKS_NO_INTERNAL"] },
|
|
17814
|
+
// No testimonials on the page, said once instead of six times.
|
|
17815
|
+
{ keep: "NO_SOCIAL_PROOF", drop: ["BOFU_NO_SOCIAL_PROOF", "AI_NO_TESTIMONIALS", "AI_NO_REVIEW_INTEGRATION", "AI_NO_REVIEW_PLATFORMS", "AI_WEAK_REVIEW_ECOSYSTEM"] },
|
|
17816
|
+
{ keep: "AI_NO_TESTIMONIALS", drop: ["AI_NO_REVIEW_INTEGRATION", "AI_NO_REVIEW_PLATFORMS", "AI_WEAK_REVIEW_ECOSYSTEM"] },
|
|
17817
|
+
// The brand-score composite restates the brand-definition finding it is built from.
|
|
17818
|
+
{ keep: "AI_NO_BRAND_DEFINITION", drop: ["AI_LOW_BRAND_SCORE", "NO_ENTITY_DEFINITION"] }
|
|
17819
|
+
];
|
|
17820
|
+
function dedupeIssues(issues) {
|
|
17821
|
+
const present = new Set(issues.map((i) => i.code));
|
|
17822
|
+
const redundant = /* @__PURE__ */ new Set();
|
|
17823
|
+
for (const rule of REDUNDANT_WHEN_PRESENT) {
|
|
17824
|
+
const fires = typeof rule.keep === "string" ? present.has(rule.keep) : [...present].some((code) => rule.keep.test(code));
|
|
17825
|
+
if (!fires) continue;
|
|
17826
|
+
for (const code of rule.drop) redundant.add(code);
|
|
17827
|
+
}
|
|
17828
|
+
const kept = /* @__PURE__ */ new Map();
|
|
17829
|
+
const out = [];
|
|
17830
|
+
for (const issue of issues) {
|
|
17831
|
+
if (redundant.has(issue.code)) continue;
|
|
17832
|
+
const earlier = kept.get(issue.code);
|
|
17833
|
+
if (earlier) {
|
|
17834
|
+
const urls = /* @__PURE__ */ new Set([...earlier.affectedUrls || [], ...issue.affectedUrls || []]);
|
|
17835
|
+
earlier.affectedUrls = [...urls];
|
|
17836
|
+
continue;
|
|
17837
|
+
}
|
|
17838
|
+
const copy = { ...issue };
|
|
17839
|
+
kept.set(issue.code, copy);
|
|
17840
|
+
out.push(copy);
|
|
17841
|
+
}
|
|
17842
|
+
return out;
|
|
17843
|
+
}
|
|
17844
|
+
|
|
17762
17845
|
// src/audit/engine.ts
|
|
17763
17846
|
function sameUrl(a, b) {
|
|
17764
17847
|
try {
|
|
@@ -17771,15 +17854,15 @@ function sameUrl(a, b) {
|
|
|
17771
17854
|
}
|
|
17772
17855
|
}
|
|
17773
17856
|
async function runFullAudit(options) {
|
|
17774
|
-
const {
|
|
17857
|
+
const { checkBrokenLinks = false, checkHreflangUrls = false, checkCanonicalChain = false, cruxApiKey, checksLimit = 280, premiumChecks } = options;
|
|
17858
|
+
let url = options.url;
|
|
17775
17859
|
const auditRoot = isSiteRoot(url) || sameUrl(url, options.auditRootUrl ?? url);
|
|
17776
17860
|
const allIssues = [];
|
|
17777
17861
|
const pages = [];
|
|
17778
17862
|
const tier = checksLimit <= 50 ? "free" : checksLimit <= 100 ? "registered" : "paid";
|
|
17779
17863
|
const runPremiumChecks = tier === "paid" && !!premiumChecks;
|
|
17780
17864
|
const runExtendedChecks = tier !== "free";
|
|
17781
|
-
|
|
17782
|
-
const domain = parsedUrl.hostname;
|
|
17865
|
+
let domain = new URL(url).hostname;
|
|
17783
17866
|
console.error(`
|
|
17784
17867
|
\u{1F50D} Running comprehensive SEO audit on ${url}...
|
|
17785
17868
|
`);
|
|
@@ -17806,7 +17889,9 @@ async function runFullAudit(options) {
|
|
|
17806
17889
|
timeout: 3e4,
|
|
17807
17890
|
validateStatus: () => true
|
|
17808
17891
|
}).catch((err) => {
|
|
17809
|
-
|
|
17892
|
+
const cause = err instanceof Error ? err.cause : void 0;
|
|
17893
|
+
const causeText = cause instanceof Error ? ` (${cause.code || cause.message})` : "";
|
|
17894
|
+
console.error(`Main page fetch failed: ${err instanceof Error ? err.message : String(err)}${causeText}`);
|
|
17810
17895
|
return { error: err, data: "", headers: {} };
|
|
17811
17896
|
})
|
|
17812
17897
|
]);
|
|
@@ -17829,7 +17914,7 @@ async function runFullAudit(options) {
|
|
|
17829
17914
|
});
|
|
17830
17915
|
return createReport(url, domain, allIssues, pages, { kind: "unreachable" });
|
|
17831
17916
|
}
|
|
17832
|
-
|
|
17917
|
+
let statusCode = fetchResult.status;
|
|
17833
17918
|
if (statusCode >= 400) {
|
|
17834
17919
|
const blocked = statusCode === 401 || statusCode === 403 || statusCode === 429;
|
|
17835
17920
|
allIssues.push({
|
|
@@ -17842,10 +17927,42 @@ async function runFullAudit(options) {
|
|
|
17842
17927
|
pages.push({ url, statusCode, wordCount: 0, loadTime: 0, issues: allIssues.map((i) => i.code) });
|
|
17843
17928
|
return createReport(url, domain, allIssues, pages, { kind: "http-error", statusCode });
|
|
17844
17929
|
}
|
|
17845
|
-
const
|
|
17930
|
+
const asText = (data) => typeof data === "string" ? data : JSON.stringify(data ?? "");
|
|
17931
|
+
let body = asText(fetchResult.data);
|
|
17932
|
+
let headers = fetchResult.headers;
|
|
17933
|
+
const requestedUrl = url;
|
|
17934
|
+
const refreshHops = [];
|
|
17935
|
+
for (let hop = 0; hop < 3; hop++) {
|
|
17936
|
+
const target = metaRefreshTarget(body, url);
|
|
17937
|
+
if (!target) break;
|
|
17938
|
+
try {
|
|
17939
|
+
const next = await httpGet(target, { timeout: 3e4, validateStatus: () => true });
|
|
17940
|
+
if (next.status >= 400) break;
|
|
17941
|
+
refreshHops.push(target);
|
|
17942
|
+
url = target;
|
|
17943
|
+
body = asText(next.data);
|
|
17944
|
+
headers = next.headers;
|
|
17945
|
+
statusCode = next.status;
|
|
17946
|
+
} catch {
|
|
17947
|
+
break;
|
|
17948
|
+
}
|
|
17949
|
+
}
|
|
17950
|
+
if (refreshHops.length > 0) {
|
|
17951
|
+
domain = new URL(url).hostname;
|
|
17952
|
+
allIssues.push({
|
|
17953
|
+
code: "META_REFRESH_REDIRECT",
|
|
17954
|
+
severity: "notice",
|
|
17955
|
+
category: "crawlability",
|
|
17956
|
+
title: "Page redirects with a meta refresh",
|
|
17957
|
+
description: `${requestedUrl} answers HTTP 200 with a <meta http-equiv="refresh"> to ${url}, so that page was audited. Search engines follow an instant meta refresh, but a server redirect is the documented way to move a URL and the only one every crawler and tool honours.`,
|
|
17958
|
+
impact: "Some crawlers and link-checking tools stop at the stub; link signals to the original URL may not all pass to the target.",
|
|
17959
|
+
howToFix: "Return a 301 (or 308) redirect from the server or CDN for this URL instead of a meta refresh.",
|
|
17960
|
+
affectedUrls: [requestedUrl],
|
|
17961
|
+
details: { from: requestedUrl, to: url, hops: refreshHops }
|
|
17962
|
+
});
|
|
17963
|
+
}
|
|
17846
17964
|
const html = body.replace(/<\/?noscript[^>]*>/gi, "");
|
|
17847
|
-
|
|
17848
|
-
console.error(`\u{1F4DD} Phase 2: Running synchronous HTML checks (tier: ${tier}, limit: ${checksLimit})...`);
|
|
17965
|
+
console.error("\u{1F4DD} Phase 2: Running HTML checks...");
|
|
17849
17966
|
const onPageResult = analyzeOnPage(html, url, headers, { auditRoot });
|
|
17850
17967
|
const structuredDataResult = analyzeStructuredData(html, url);
|
|
17851
17968
|
const mobileResult = analyzeMobile(html, url);
|
|
@@ -18012,10 +18129,10 @@ async function runFullAudit(options) {
|
|
|
18012
18129
|
if (cruxApiKey) {
|
|
18013
18130
|
asyncChecks.push(safeAsyncIssues("Core Web Vitals", async () => {
|
|
18014
18131
|
const cwv = await fetchCoreWebVitals(url, cruxApiKey);
|
|
18015
|
-
const
|
|
18132
|
+
const issues2 = [];
|
|
18016
18133
|
if (cwv) {
|
|
18017
18134
|
if (cwv.lcp?.rating === "poor") {
|
|
18018
|
-
|
|
18135
|
+
issues2.push({
|
|
18019
18136
|
code: "LCP_POOR",
|
|
18020
18137
|
severity: "error",
|
|
18021
18138
|
category: "performance",
|
|
@@ -18028,7 +18145,7 @@ async function runFullAudit(options) {
|
|
|
18028
18145
|
});
|
|
18029
18146
|
}
|
|
18030
18147
|
if (cwv.cls?.rating === "poor") {
|
|
18031
|
-
|
|
18148
|
+
issues2.push({
|
|
18032
18149
|
code: "CLS_POOR",
|
|
18033
18150
|
severity: "error",
|
|
18034
18151
|
category: "performance",
|
|
@@ -18041,14 +18158,15 @@ async function runFullAudit(options) {
|
|
|
18041
18158
|
});
|
|
18042
18159
|
}
|
|
18043
18160
|
}
|
|
18044
|
-
return
|
|
18161
|
+
return issues2;
|
|
18045
18162
|
}));
|
|
18046
18163
|
}
|
|
18047
18164
|
}
|
|
18048
18165
|
const asyncResults = await Promise.all(asyncChecks);
|
|
18049
|
-
for (const
|
|
18050
|
-
allIssues.push(...
|
|
18166
|
+
for (const issues2 of asyncResults) {
|
|
18167
|
+
allIssues.push(...issues2);
|
|
18051
18168
|
}
|
|
18169
|
+
const issues = dedupeIssues(allIssues);
|
|
18052
18170
|
pages.push({
|
|
18053
18171
|
url,
|
|
18054
18172
|
statusCode,
|
|
@@ -18058,10 +18176,10 @@ async function runFullAudit(options) {
|
|
|
18058
18176
|
h1: onPageResult.data.h1s,
|
|
18059
18177
|
wordCount: onPageResult.data.wordCount,
|
|
18060
18178
|
loadTime: perfData.loadTime,
|
|
18061
|
-
issues:
|
|
18179
|
+
issues: issues.map((i) => i.code)
|
|
18062
18180
|
});
|
|
18063
18181
|
console.error("\n\u2705 Audit complete!\n");
|
|
18064
|
-
return createReport(url, domain,
|
|
18182
|
+
return createReport(url, domain, issues, pages);
|
|
18065
18183
|
}
|
|
18066
18184
|
function createReport(url, domain, issues, pages, pageFailure) {
|
|
18067
18185
|
const healthScore = calculateHealthScore(issues);
|
|
@@ -18226,10 +18344,10 @@ function groupIssuesByCategory(issues) {
|
|
|
18226
18344
|
}
|
|
18227
18345
|
|
|
18228
18346
|
// src/audit/checks/duplicate-content.ts
|
|
18229
|
-
var
|
|
18347
|
+
var cheerio38 = __toESM(require("cheerio"));
|
|
18230
18348
|
var import_crypto = require("crypto");
|
|
18231
18349
|
function extractContentHash(html, url) {
|
|
18232
|
-
const $ =
|
|
18350
|
+
const $ = cheerio38.load(html);
|
|
18233
18351
|
$("script, style, nav, header, footer, aside, .nav, .header, .footer, .sidebar").remove();
|
|
18234
18352
|
const title = $("title").text().trim();
|
|
18235
18353
|
const bodyText = $("body").text().replace(/\s+/g, " ").trim();
|
|
@@ -19457,7 +19575,7 @@ var PRIORITY_WEIGHTS = {
|
|
|
19457
19575
|
};
|
|
19458
19576
|
|
|
19459
19577
|
// src/keywords/engine.ts
|
|
19460
|
-
var
|
|
19578
|
+
var cheerio40 = __toESM(require("cheerio"));
|
|
19461
19579
|
init_http();
|
|
19462
19580
|
|
|
19463
19581
|
// src/keywords/prioritizer.ts
|
|
@@ -19754,7 +19872,7 @@ function enrichKeywordsWithEstimates(keywords) {
|
|
|
19754
19872
|
|
|
19755
19873
|
// src/keywords/sources/free-sources.ts
|
|
19756
19874
|
init_http();
|
|
19757
|
-
var
|
|
19875
|
+
var cheerio39 = __toESM(require("cheerio"));
|
|
19758
19876
|
var USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36";
|
|
19759
19877
|
async function getPeopleAlsoAsk(query) {
|
|
19760
19878
|
try {
|
|
@@ -19763,7 +19881,7 @@ async function getPeopleAlsoAsk(query) {
|
|
|
19763
19881
|
headers: { "User-Agent": USER_AGENT },
|
|
19764
19882
|
timeout: 1e4
|
|
19765
19883
|
});
|
|
19766
|
-
const $ =
|
|
19884
|
+
const $ = cheerio39.load(response.data);
|
|
19767
19885
|
const questions = [];
|
|
19768
19886
|
$("[data-sgrd]").each((_, el) => {
|
|
19769
19887
|
const text = $(el).text().trim();
|
|
@@ -19789,7 +19907,7 @@ async function getRelatedSearches(query) {
|
|
|
19789
19907
|
headers: { "User-Agent": USER_AGENT },
|
|
19790
19908
|
timeout: 1e4
|
|
19791
19909
|
});
|
|
19792
|
-
const $ =
|
|
19910
|
+
const $ = cheerio39.load(response.data);
|
|
19793
19911
|
const related = [];
|
|
19794
19912
|
$("div[data-ved] a").each((_, el) => {
|
|
19795
19913
|
const href = $(el).attr("href");
|
|
@@ -19879,7 +19997,7 @@ async function analyzeCompetitorTitles(query) {
|
|
|
19879
19997
|
headers: { "User-Agent": USER_AGENT },
|
|
19880
19998
|
timeout: 1e4
|
|
19881
19999
|
});
|
|
19882
|
-
const $ =
|
|
20000
|
+
const $ = cheerio39.load(response.data);
|
|
19883
20001
|
const titles = [];
|
|
19884
20002
|
const keywords = /* @__PURE__ */ new Set();
|
|
19885
20003
|
$("h3").each((_, el) => {
|
|
@@ -20212,7 +20330,7 @@ async function fetchPageMeta(url) {
|
|
|
20212
20330
|
const response = await httpGet(url, {
|
|
20213
20331
|
timeout: 1e4
|
|
20214
20332
|
});
|
|
20215
|
-
const $ =
|
|
20333
|
+
const $ = cheerio40.load(response.data);
|
|
20216
20334
|
return {
|
|
20217
20335
|
url,
|
|
20218
20336
|
title: $("title").text().trim() || void 0,
|
|
@@ -20343,20 +20461,17 @@ async function extractSeedKeywords(url) {
|
|
|
20343
20461
|
const response = await httpGet(url, {
|
|
20344
20462
|
timeout: 1e4
|
|
20345
20463
|
});
|
|
20346
|
-
const $ =
|
|
20464
|
+
const $ = cheerio40.load(response.data);
|
|
20347
20465
|
const seeds = /* @__PURE__ */ new Set();
|
|
20348
|
-
const
|
|
20349
|
-
|
|
20350
|
-
|
|
20351
|
-
const h1 = $("h1").first().text().toLowerCase();
|
|
20352
|
-
const h1Words = h1.split(/[\s\-|:]+/).filter((w) => w.length > 3);
|
|
20353
|
-
h1Words.forEach((w) => seeds.add(w));
|
|
20466
|
+
const words = (text) => text.toLowerCase().split(/[\s\-|:]+/).map((w) => w.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu, "")).filter((w) => w.length > 3);
|
|
20467
|
+
words($("title").text()).forEach((w) => seeds.add(w));
|
|
20468
|
+
words($("h1").first().text()).forEach((w) => seeds.add(w));
|
|
20354
20469
|
const metaKeywords = $('meta[name="keywords"]').attr("content");
|
|
20355
20470
|
if (metaKeywords) {
|
|
20356
20471
|
metaKeywords.split(",").forEach((k) => seeds.add(k.trim().toLowerCase()));
|
|
20357
20472
|
}
|
|
20358
20473
|
const desc = $('meta[name="description"]').attr("content")?.toLowerCase() || "";
|
|
20359
|
-
const descWords = desc
|
|
20474
|
+
const descWords = words(desc).filter((w) => w.length > 5);
|
|
20360
20475
|
descWords.slice(0, 5).forEach((w) => seeds.add(w));
|
|
20361
20476
|
return Array.from(seeds).slice(0, 10);
|
|
20362
20477
|
} catch {
|
|
@@ -20409,7 +20524,7 @@ function parseJsonReply(text) {
|
|
|
20409
20524
|
}
|
|
20410
20525
|
|
|
20411
20526
|
// src/keywords/site-crawler.ts
|
|
20412
|
-
var
|
|
20527
|
+
var cheerio41 = __toESM(require("cheerio"));
|
|
20413
20528
|
init_http();
|
|
20414
20529
|
var EXCLUDED_PATHS = [
|
|
20415
20530
|
"/cdn-cgi/",
|
|
@@ -20522,7 +20637,7 @@ async function crawlPage(url, timeout) {
|
|
|
20522
20637
|
validateStatus: (status) => status === 200
|
|
20523
20638
|
});
|
|
20524
20639
|
const html = response.data;
|
|
20525
|
-
const $ =
|
|
20640
|
+
const $ = cheerio41.load(html);
|
|
20526
20641
|
$('script, style, noscript, iframe, nav, footer, header, aside, [role="navigation"]').remove();
|
|
20527
20642
|
const title = $("title").text().trim();
|
|
20528
20643
|
const description = $('meta[name="description"]').attr("content")?.trim() || "";
|
|
@@ -23158,7 +23273,7 @@ function getDateRange(days = 28) {
|
|
|
23158
23273
|
|
|
23159
23274
|
// src/keywords/sources/competitor-analysis.ts
|
|
23160
23275
|
init_http();
|
|
23161
|
-
var
|
|
23276
|
+
var cheerio42 = __toESM(require("cheerio"));
|
|
23162
23277
|
var USER_AGENT2 = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36";
|
|
23163
23278
|
async function discoverCompetitorKeywords(yourDomain, seedKeywords, competitors) {
|
|
23164
23279
|
const yourKeywords = /* @__PURE__ */ new Set();
|
|
@@ -23255,7 +23370,7 @@ async function analyzeSERP(query) {
|
|
|
23255
23370
|
headers: { "User-Agent": USER_AGENT2 },
|
|
23256
23371
|
timeout: 1e4
|
|
23257
23372
|
});
|
|
23258
|
-
const $ =
|
|
23373
|
+
const $ = cheerio42.load(response.data);
|
|
23259
23374
|
const results = [];
|
|
23260
23375
|
const relatedSearches = [];
|
|
23261
23376
|
const peopleAlsoAsk = [];
|
|
@@ -23708,7 +23823,7 @@ function formatTopicReport(result) {
|
|
|
23708
23823
|
|
|
23709
23824
|
// src/content/readability.ts
|
|
23710
23825
|
function analyzeReadability(text) {
|
|
23711
|
-
const cleanText = text.replace(/<[^>]*>/g, " ").replace(
|
|
23826
|
+
const cleanText = text.replace(/<[^>]*>/g, " ").replace(/[ \t\r\f\v]+/g, " ").replace(/ ?\n ?/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
23712
23827
|
const words = getWords(cleanText);
|
|
23713
23828
|
const sentences = getSentences(cleanText);
|
|
23714
23829
|
const paragraphs = getParagraphs(cleanText);
|
|
@@ -23777,7 +23892,7 @@ function getSentences(text) {
|
|
|
23777
23892
|
return text.split(/[.!?]+/).filter((s) => s.trim().length > 0);
|
|
23778
23893
|
}
|
|
23779
23894
|
function getParagraphs(text) {
|
|
23780
|
-
return text.split(/\n\n+/).filter((p) => p.trim().length
|
|
23895
|
+
return text.split(/\n\n+/).filter((p) => p.trim().split(/\s+/).length >= 3);
|
|
23781
23896
|
}
|
|
23782
23897
|
function countSyllables(word) {
|
|
23783
23898
|
word = word.toLowerCase().replace(/[^a-z]/g, "");
|
|
@@ -36062,6 +36177,7 @@ var SUPPORTED_FRAMEWORKS = [
|
|
|
36062
36177
|
LOCATION_CODES,
|
|
36063
36178
|
OG_IMAGE_SPECS,
|
|
36064
36179
|
PRIORITY_WEIGHTS,
|
|
36180
|
+
REDUNDANT_WHEN_PRESENT,
|
|
36065
36181
|
RankTracker,
|
|
36066
36182
|
SEO_SCOPES,
|
|
36067
36183
|
SITE_PROFILE_QUESTIONS,
|
|
@@ -36145,6 +36261,7 @@ var SUPPORTED_FRAMEWORKS = [
|
|
|
36145
36261
|
createPullRequest,
|
|
36146
36262
|
createSEOCommit,
|
|
36147
36263
|
createSEOCommits,
|
|
36264
|
+
dedupeIssues,
|
|
36148
36265
|
describeConcerns,
|
|
36149
36266
|
detectDuplicates,
|
|
36150
36267
|
detectFramework,
|
|
@@ -36281,6 +36398,7 @@ var SUPPORTED_FRAMEWORKS = [
|
|
|
36281
36398
|
loadAgent,
|
|
36282
36399
|
loadAgentByName,
|
|
36283
36400
|
mergePages,
|
|
36401
|
+
metaRefreshTarget,
|
|
36284
36402
|
openAIKeywordLLM,
|
|
36285
36403
|
optimizeForAI,
|
|
36286
36404
|
optimizeReadme,
|