seo 0.2.13 → 0.2.14
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/README.md +1 -1
- package/dist/cli.js +2 -2
- package/dist/{dist-BB0yH7qK.js → dist-Bc8MJ1gT.js} +1 -1
- package/dist/{dist-Dwdogl4j.js → dist-sYeCtY3Q.js} +87 -87
- package/dist/dist-sYeCtY3Q.js.map +1 -0
- package/dist/index.d.ts +70 -2
- package/dist/index.js +88 -38
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +88 -38
- package/dist/mcp.js.map +1 -1
- package/dist/{package-B_swz2Xd.js → package-Cp8DwqCy.js} +2 -2
- package/dist/{package-B_swz2Xd.js.map → package-Cp8DwqCy.js.map} +1 -1
- package/package.json +1 -1
- package/dist/dist-Dwdogl4j.js.map +0 -1
package/dist/mcp.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRequire as __seoCreateRequire } from 'node:module'; const require = __seoCreateRequire(import.meta.url);
|
|
2
|
-
import { n as load, t as version } from "./package-
|
|
2
|
+
import { n as load, t as version } from "./package-Cp8DwqCy.js";
|
|
3
3
|
import { Headers, fetch as fetch$1 } from "undici";
|
|
4
4
|
import { chmodSync, createReadStream, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { arch, homedir, platform, totalmem } from "node:os";
|
|
@@ -2778,6 +2778,22 @@ function extractStructuredData($, baseUrl, options = {}) {
|
|
|
2778
2778
|
}
|
|
2779
2779
|
//#endregion
|
|
2780
2780
|
//#region packages/core/dist/extract/page-extractor.js
|
|
2781
|
+
function metaRefresh($, finalUrl) {
|
|
2782
|
+
for (const element of $("meta[http-equiv]").toArray()) {
|
|
2783
|
+
if ($(element).attr("http-equiv")?.trim().toLowerCase() !== "refresh") continue;
|
|
2784
|
+
const match = ($(element).attr("content")?.trim())?.match(/^([0-9]+(?:\.[0-9]+)?)\s*;\s*url\s*=\s*(?:"([^"]*)"|'([^']*)'|(.+))$/iu);
|
|
2785
|
+
if (!match) continue;
|
|
2786
|
+
try {
|
|
2787
|
+
const target = new URL((match[2] ?? match[3] ?? match[4] ?? "").trim(), finalUrl);
|
|
2788
|
+
if (!["http:", "https:"].includes(target.protocol)) continue;
|
|
2789
|
+
target.hash = "";
|
|
2790
|
+
return {
|
|
2791
|
+
targetUrl: target.toString(),
|
|
2792
|
+
delaySeconds: Number(match[1])
|
|
2793
|
+
};
|
|
2794
|
+
} catch {}
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2781
2797
|
const SOCIAL_PROFILE_HOSTS = [
|
|
2782
2798
|
"facebook.com",
|
|
2783
2799
|
"instagram.com",
|
|
@@ -3057,6 +3073,8 @@ async function extractPage(fetchResult, extractor = "defuddle", dependencies = {
|
|
|
3057
3073
|
}, extractor, dependencies);
|
|
3058
3074
|
const { text, excerpt } = content;
|
|
3059
3075
|
const title = safeText$1($("title").first().text());
|
|
3076
|
+
const crawlerMetaRefresh = extractor === "crawler" ? metaRefresh($, fetchResult.finalUrl) : void 0;
|
|
3077
|
+
if (crawlerMetaRefresh && new URL(crawlerMetaRefresh.targetUrl).origin === url.origin) crawlerInternalLinks.add(crawlerMetaRefresh.targetUrl);
|
|
3060
3078
|
return {
|
|
3061
3079
|
url: fetchResult.url,
|
|
3062
3080
|
finalUrl: fetchResult.finalUrl,
|
|
@@ -3082,6 +3100,7 @@ async function extractPage(fetchResult, extractor = "defuddle", dependencies = {
|
|
|
3082
3100
|
externalAnchorSamples,
|
|
3083
3101
|
socialProfileLinks: [...socialProfileLinks],
|
|
3084
3102
|
contentSketch: contentSketch(text),
|
|
3103
|
+
metaRefresh: crawlerMetaRefresh,
|
|
3085
3104
|
softAuthenticationGate: softAuthenticationGate($, fetchResult.finalUrl, title),
|
|
3086
3105
|
tabbedContent: tabbedContent($)
|
|
3087
3106
|
} } : {},
|
|
@@ -7763,6 +7782,9 @@ function failedMarkdownUrls(discovery) {
|
|
|
7763
7782
|
return !negotiatedOk || !explicitOk || page.repeatedHashStable !== true;
|
|
7764
7783
|
}).map((page) => page.htmlUrl);
|
|
7765
7784
|
}
|
|
7785
|
+
function unstableMarkdownUrls(discovery) {
|
|
7786
|
+
return discovery.markdownAlternates.pages.filter((page) => confirmedMarkdown(page) !== void 0 && page.repeatedHashStable !== true).map((page) => page.htmlUrl);
|
|
7787
|
+
}
|
|
7766
7788
|
function qualityFailureUrls(discovery) {
|
|
7767
7789
|
return discovery.markdownAlternates.pages.filter((page) => {
|
|
7768
7790
|
const quality = page.quality;
|
|
@@ -7776,7 +7798,10 @@ function confirmedMarkdown(page) {
|
|
|
7776
7798
|
function markdownChecks(discovery) {
|
|
7777
7799
|
const markdown = discovery.markdownAlternates;
|
|
7778
7800
|
const failedUrls = failedMarkdownUrls(discovery);
|
|
7801
|
+
const unstableUrls = unstableMarkdownUrls(discovery);
|
|
7779
7802
|
const qualityUrls = qualityFailureUrls(discovery);
|
|
7803
|
+
const hasEligibleHtml = markdown.eligibleHtmlPages > 0;
|
|
7804
|
+
const hasEvaluatedMarkdown = markdown.evaluatedPages > 0;
|
|
7780
7805
|
const negotiatedPages = markdown.pages.filter((page) => page.negotiated?.status === 200 && /^\s*text\/markdown\b/iu.test(page.negotiated.contentType ?? "")).length;
|
|
7781
7806
|
const explicitPages = markdown.pages.filter((page) => page.htmlAlternateUnique).length;
|
|
7782
7807
|
const coverageComplete = markdown.eligibleHtmlPages > 0 && markdown.evaluatedPages === markdown.eligibleHtmlPages;
|
|
@@ -7805,25 +7830,28 @@ function markdownChecks(discovery) {
|
|
|
7805
7830
|
check$2("representations", {
|
|
7806
7831
|
id: "markdown-coverage",
|
|
7807
7832
|
status: markdown.eligibleHtmlPages === 0 ? "unknown" : coverageComplete ? "pass" : "fail",
|
|
7808
|
-
title: coverageComplete ? "Every successful HTML page has a Markdown representation" : "Markdown representation coverage is incomplete",
|
|
7809
|
-
plainEnglish: `${markdown.evaluatedPages} of ${markdown.eligibleHtmlPages} successful HTML pages returned Markdown. ${explicitPages} advertised an explicit alternative and ${negotiatedPages} supported content negotiation
|
|
7810
|
-
action: coverageComplete ? "Keep the negotiated or explicit Markdown response stable as routes change." : "Return text/markdown through content negotiation or advertise one working Markdown alternative for each public HTML page.",
|
|
7833
|
+
title: coverageComplete ? "Every successful HTML page has a Markdown representation" : !hasEligibleHtml ? "Markdown representation coverage was not evaluated" : "Markdown representation coverage is incomplete",
|
|
7834
|
+
plainEnglish: hasEligibleHtml ? `${markdown.evaluatedPages} of ${markdown.eligibleHtmlPages} successful HTML pages returned Markdown. ${explicitPages} advertised an explicit alternative and ${negotiatedPages} supported content negotiation.` : "No successful HTML page was available, so Markdown representation coverage could not be evaluated.",
|
|
7835
|
+
action: !hasEligibleHtml ? "Resolve the crawl or page-fetch failure, then run this check again." : coverageComplete ? "Keep the negotiated or explicit Markdown response stable as routes change." : "Return text/markdown through content negotiation or advertise one working Markdown alternative for each public HTML page.",
|
|
7811
7836
|
evidence: {
|
|
7812
7837
|
eligibleHtmlPages: markdown.eligibleHtmlPages,
|
|
7813
7838
|
advertisedPages: markdown.advertisedPages,
|
|
7814
7839
|
negotiatedPages,
|
|
7815
7840
|
evaluatedPages: markdown.evaluatedPages
|
|
7816
7841
|
},
|
|
7817
|
-
urls: failedUrls.slice(0, 25)
|
|
7842
|
+
urls: hasEligibleHtml ? failedUrls.slice(0, 25) : []
|
|
7818
7843
|
}),
|
|
7819
7844
|
check$2("representations", {
|
|
7820
7845
|
id: "markdown-token-estimates",
|
|
7821
7846
|
status: markdown.evaluatedPages === 0 ? "unknown" : tokenHeaders === markdown.evaluatedPages ? "pass" : "info",
|
|
7822
|
-
title: tokenHeaders === markdown.evaluatedPages ? "Every Markdown response includes a token estimate" : "Some Markdown responses are missing a stable token estimate",
|
|
7823
|
-
plainEnglish: `${tokenHeaders} of ${markdown.evaluatedPages} evaluated pages returned X-Markdown-Tokens. Paired explicit and negotiated responses agreed where both existed. The value is an estimate, not an exact model-specific token count
|
|
7824
|
-
action: tokenHeaders === markdown.evaluatedPages ? "Keep the estimate stable when both explicit and negotiated responses exist." : "No change is required. X-Markdown-Tokens is optional; local size estimates are reported separately.",
|
|
7847
|
+
title: !hasEvaluatedMarkdown ? "Markdown token estimates were not evaluated" : tokenHeaders === markdown.evaluatedPages ? "Every Markdown response includes a token estimate" : "Some Markdown responses are missing a stable token estimate",
|
|
7848
|
+
plainEnglish: hasEvaluatedMarkdown ? `${tokenHeaders} of ${markdown.evaluatedPages} evaluated pages returned X-Markdown-Tokens. Paired explicit and negotiated responses agreed where both existed. The value is an estimate, not an exact model-specific token count.` : "No confirmed Markdown response was available, so optional server token estimates could not be evaluated.",
|
|
7849
|
+
action: !hasEvaluatedMarkdown ? "Confirm a successful text/markdown representation, then run this check again." : tokenHeaders === markdown.evaluatedPages ? "Keep the estimate stable when both explicit and negotiated responses exist." : "No change is required. X-Markdown-Tokens is optional; local size estimates are reported separately.",
|
|
7825
7850
|
urls: markdown.pages.filter((page) => {
|
|
7826
|
-
|
|
7851
|
+
const primary = confirmedMarkdown(page);
|
|
7852
|
+
if (!primary) return false;
|
|
7853
|
+
const paired = [page.explicit, page.negotiated].filter((response) => response?.status !== void 0 && response.status >= 200 && response.status < 300 && /^\s*text\/markdown\b/iu.test(response.contentType ?? ""));
|
|
7854
|
+
return primary?.markdownTokens === void 0 || paired.length === 2 && paired[0]?.markdownTokens !== paired[1]?.markdownTokens;
|
|
7827
7855
|
}).map((page) => page.htmlUrl).slice(0, 25)
|
|
7828
7856
|
}),
|
|
7829
7857
|
check$2("representations", {
|
|
@@ -7831,7 +7859,7 @@ function markdownChecks(discovery) {
|
|
|
7831
7859
|
status: sizedPages.length === 0 ? "unknown" : largePages.length === 0 ? "pass" : "warning",
|
|
7832
7860
|
title: sizedPages.length === 0 ? "Markdown size was not measured" : largePages.length === 0 ? "Markdown representations stay within the review threshold" : "Some Markdown representations are large enough to review",
|
|
7833
7861
|
plainEnglish: `${sizedPages.length} Markdown responses were measured locally. ${largePages.length} exceeded 50,000 characters and ${veryLargePages.length} exceeded 100,000 characters. These are processing-risk thresholds, not universal model limits.`,
|
|
7834
|
-
action: largePages.length === 0 ? "Keep important content early and retain stable section structure as pages grow." : "Split genuinely separate topics, keep navigation and repeated chrome out of Markdown, and put the most useful content early. Do not remove necessary detail just to hit a threshold.",
|
|
7862
|
+
action: sizedPages.length === 0 ? "Confirm a successful text/markdown representation, then run this check again." : largePages.length === 0 ? "Keep important content early and retain stable section structure as pages grow." : "Split genuinely separate topics, keep navigation and repeated chrome out of Markdown, and put the most useful content early. Do not remove necessary detail just to hit a threshold.",
|
|
7835
7863
|
evidence: {
|
|
7836
7864
|
measuredPages: sizedPages.length,
|
|
7837
7865
|
reviewThresholdCharacters: 5e4,
|
|
@@ -7849,7 +7877,7 @@ function markdownChecks(discovery) {
|
|
|
7849
7877
|
status: parityPages.length === 0 ? "unknown" : parityFailures.length === 0 ? "pass" : "warning",
|
|
7850
7878
|
title: parityPages.length === 0 ? "HTML and Markdown content parity was not measured" : parityFailures.length === 0 ? "Markdown retains sampled content from the HTML document" : "Some Markdown responses may omit important HTML content",
|
|
7851
7879
|
plainEnglish: `${parityPages.length} pages were compared using bounded hashes sampled across the main HTML content. ${parityFailures.length} retained less than 60% of that sample. This detects likely omissions without storing full page bodies.`,
|
|
7852
|
-
action: parityFailures.length === 0 ? "Keep the shared conversion path covered as templates and components change." : "Compare the affected HTML and Markdown around the missing sections, then fix the shared converter or component serialization.",
|
|
7880
|
+
action: parityPages.length === 0 ? "Confirm a successful text/markdown representation, then run this check again." : parityFailures.length === 0 ? "Keep the shared conversion path covered as templates and components change." : "Compare the affected HTML and Markdown around the missing sections, then fix the shared converter or component serialization.",
|
|
7853
7881
|
evidence: {
|
|
7854
7882
|
evaluatedPages: parityPages.length,
|
|
7855
7883
|
failedPages: parityFailures.length,
|
|
@@ -7874,32 +7902,32 @@ function markdownChecks(discovery) {
|
|
|
7874
7902
|
check$2("representations", {
|
|
7875
7903
|
id: "markdown-negotiation",
|
|
7876
7904
|
status: markdown.eligibleHtmlPages === 0 ? "unknown" : negotiationComplete && discovery.contentNegotiation.qZeroHonoured ? "pass" : "fail",
|
|
7877
|
-
title: negotiationComplete && discovery.contentNegotiation.qZeroHonoured ? "Markdown content negotiation works across the site" : "Markdown content negotiation needs attention",
|
|
7878
|
-
plainEnglish: `${negotiatedPages} of ${markdown.eligibleHtmlPages} pages returned negotiated Markdown. ${markdown.exactByteMatches} of ${explicitPages} paired explicit responses matched byte for byte. A request that refuses Markdown ${discovery.contentNegotiation.qZeroHonoured ? "received HTML" : "did not produce confirmed HTML evidence"}
|
|
7879
|
-
action: "Honour Accept q-values, send Vary: Accept, and keep paired explicit and negotiated responses identical when both exist.",
|
|
7905
|
+
title: !hasEligibleHtml ? "Markdown content negotiation was not evaluated" : negotiationComplete && discovery.contentNegotiation.qZeroHonoured ? "Markdown content negotiation works across the site" : "Markdown content negotiation needs attention",
|
|
7906
|
+
plainEnglish: hasEligibleHtml ? `${negotiatedPages} of ${markdown.eligibleHtmlPages} pages returned negotiated Markdown. ${markdown.exactByteMatches} of ${explicitPages} paired explicit responses matched byte for byte. A request that refuses Markdown ${discovery.contentNegotiation.qZeroHonoured ? "received HTML" : "did not produce confirmed HTML evidence"}.` : "No successful HTML page was available, so content negotiation could not be evaluated.",
|
|
7907
|
+
action: hasEligibleHtml ? "Honour Accept q-values, send Vary: Accept, and keep paired explicit and negotiated responses identical when both exist." : "Resolve the crawl or page-fetch failure, then run this check again.",
|
|
7880
7908
|
evidence: {
|
|
7881
7909
|
negotiatedPages,
|
|
7882
7910
|
explicitPages,
|
|
7883
7911
|
exactByteMatches: markdown.exactByteMatches,
|
|
7884
7912
|
qZero: discovery.contentNegotiation
|
|
7885
7913
|
},
|
|
7886
|
-
urls: failedUrls.slice(0, 25)
|
|
7914
|
+
urls: hasEligibleHtml ? failedUrls.slice(0, 25) : []
|
|
7887
7915
|
}),
|
|
7888
7916
|
check$2("representations", {
|
|
7889
7917
|
id: "markdown-determinism",
|
|
7890
7918
|
status: markdown.evaluatedPages === 0 ? "unknown" : stable ? "pass" : "fail",
|
|
7891
|
-
title: stable ? "Repeated Markdown responses are stable" : "Repeated Markdown responses changed during the audit",
|
|
7892
|
-
plainEnglish: `${markdown.stableResponses} of ${markdown.evaluatedPages} repeated Markdown responses kept the same SHA-256 digest
|
|
7893
|
-
action: "Remove timestamps, random output, or other runtime rewriting that changes the same Markdown response between requests.",
|
|
7919
|
+
title: !hasEvaluatedMarkdown ? "Markdown response stability was not evaluated" : stable ? "Repeated Markdown responses are stable" : "Repeated Markdown responses changed during the audit",
|
|
7920
|
+
plainEnglish: hasEvaluatedMarkdown ? `${markdown.stableResponses} of ${markdown.evaluatedPages} repeated Markdown responses kept the same SHA-256 digest.` : "No confirmed Markdown response was available for a repeated stability check.",
|
|
7921
|
+
action: !hasEvaluatedMarkdown ? "Confirm a successful text/markdown representation, then run this check again." : stable ? "Keep repeated Markdown responses byte-stable as templates and data change." : "Remove timestamps, random output, or other runtime rewriting that changes the same Markdown response between requests.",
|
|
7894
7922
|
evidence: { stableResponses: markdown.stableResponses },
|
|
7895
|
-
urls:
|
|
7923
|
+
urls: unstableUrls.slice(0, 25)
|
|
7896
7924
|
}),
|
|
7897
7925
|
check$2("representations", {
|
|
7898
7926
|
id: "markdown-quality",
|
|
7899
7927
|
status: markdown.evaluatedPages === 0 ? "unknown" : qualityUrls.length === 0 ? "pass" : "warning",
|
|
7900
|
-
title: qualityUrls.length === 0 ? "Markdown keeps the useful document structure cleanly" : "Some Markdown alternatives need an extraction review",
|
|
7901
|
-
plainEnglish: qualityUrls.length === 0 ? "The evaluated alternatives retained one H1, frontmatter titles, balanced code fences, useful copy, and no leaked SVG, script, style, or layout tags." : `${qualityUrls.length} Markdown alternative${qualityUrls.length === 1 ? "" : "s"} lost important structure, looked navigation-only, or leaked presentation markup.`,
|
|
7902
|
-
action: "Open the affected Markdown directly and fix the shared converter or component exclusion rules rather than patching generated files.",
|
|
7928
|
+
title: !hasEvaluatedMarkdown ? "Markdown quality was not evaluated" : qualityUrls.length === 0 ? "Markdown keeps the useful document structure cleanly" : "Some Markdown alternatives need an extraction review",
|
|
7929
|
+
plainEnglish: !hasEvaluatedMarkdown ? "No confirmed Markdown response was available for structure and extraction checks." : qualityUrls.length === 0 ? "The evaluated alternatives retained one H1, frontmatter titles, balanced code fences, useful copy, and no leaked SVG, script, style, or layout tags." : `${qualityUrls.length} Markdown alternative${qualityUrls.length === 1 ? "" : "s"} lost important structure, looked navigation-only, or leaked presentation markup.`,
|
|
7930
|
+
action: !hasEvaluatedMarkdown ? "Confirm a successful text/markdown representation, then run this check again." : qualityUrls.length === 0 ? "Keep the shared Markdown conversion path covered as templates and components change." : "Open the affected Markdown directly and fix the shared converter or component exclusion rules rather than patching generated files.",
|
|
7903
7931
|
urls: qualityUrls.slice(0, 25)
|
|
7904
7932
|
})
|
|
7905
7933
|
];
|
|
@@ -9819,7 +9847,8 @@ const RULE_DEFINITIONS = [
|
|
|
9819
9847
|
evidenceFields: [
|
|
9820
9848
|
"page.url",
|
|
9821
9849
|
"page.finalUrl",
|
|
9822
|
-
"page.status"
|
|
9850
|
+
"page.status",
|
|
9851
|
+
"page.metaRefresh"
|
|
9823
9852
|
],
|
|
9824
9853
|
suggestedCommands: ["seo redirect-trace --url <url> --json"]
|
|
9825
9854
|
}
|
|
@@ -10313,6 +10342,7 @@ function comparableUrl(value) {
|
|
|
10313
10342
|
}
|
|
10314
10343
|
}
|
|
10315
10344
|
function isRedirectedPage(page) {
|
|
10345
|
+
if (page.metaRefresh) return true;
|
|
10316
10346
|
const requested = comparableUrl(page.url);
|
|
10317
10347
|
const final = comparableUrl(page.finalUrl);
|
|
10318
10348
|
return Boolean(requested && final && requested !== final);
|
|
@@ -10587,11 +10617,18 @@ function auditCrawlPages(pages, opts = {}) {
|
|
|
10587
10617
|
}
|
|
10588
10618
|
if (page.status < 200) continue;
|
|
10589
10619
|
const redirected = isRedirectedPage(page);
|
|
10590
|
-
if (redirected)
|
|
10591
|
-
|
|
10592
|
-
|
|
10593
|
-
|
|
10594
|
-
|
|
10620
|
+
if (redirected) {
|
|
10621
|
+
const redirectTarget = page.metaRefresh?.targetUrl ?? page.finalUrl;
|
|
10622
|
+
issues.push(issue("redirected_url", page, `Final URL: ${redirectTarget}`, {
|
|
10623
|
+
requestedUrl: page.url,
|
|
10624
|
+
finalUrl: redirectTarget,
|
|
10625
|
+
status: page.status,
|
|
10626
|
+
...page.metaRefresh ? {
|
|
10627
|
+
redirectType: "meta-refresh",
|
|
10628
|
+
delaySeconds: page.metaRefresh.delaySeconds
|
|
10629
|
+
} : {}
|
|
10630
|
+
}));
|
|
10631
|
+
}
|
|
10595
10632
|
const redirectChain = page.fetchDiagnostics?.redirectChain ?? [];
|
|
10596
10633
|
if (redirectChain.length > 1) issues.push(issue("redirect_chain", page, `${redirectChain.length} hops`, {
|
|
10597
10634
|
redirectChain,
|
|
@@ -11437,7 +11474,7 @@ function latestCrawlReport(site, store = crawlReportStore) {
|
|
|
11437
11474
|
//#region packages/core/dist/analyze/crawler/crawl-caveats.js
|
|
11438
11475
|
function crawlCaveats(input) {
|
|
11439
11476
|
const caveats = input.cancelled ? ["Crawl cancelled before all queued URLs finished."] : input.pageLimitReached ? [`Stopped after reaching maxPages (${input.maxPages}).`] : [];
|
|
11440
|
-
if (input.queueSafetySkippedUrls > 0) caveats.push(`Left ${input.queueSafetySkippedUrls} eligible same-origin URLs unqueued to keep this crawl bounded. Increase --max-pages to inspect more.`);
|
|
11477
|
+
if (input.queueSafetySkippedUrls > 0) caveats.push(input.queueSafetyInventoryCapped ? `Left at least ${input.queueSafetySkippedUrls} unique eligible same-origin URLs unqueued to keep this crawl bounded. The excluded-URL inventory reached its reporting cap. Increase --max-pages to inspect more.` : `Left ${input.queueSafetySkippedUrls} unique eligible same-origin URLs unqueued to keep this crawl bounded. Increase --max-pages to inspect more.`);
|
|
11441
11478
|
if (input.originBackpressureSkippedUrls > 0) caveats.push(`Stopped ${input.originBackpressureSkippedUrls} queued URL${input.originBackpressureSkippedUrls === 1 ? "" : "s"} because the origin stayed slow. This is incomplete crawl evidence, not a failed URL check.`);
|
|
11442
11479
|
if (input.memoryPressureSkippedUrls > 0) caveats.push(`Left ${input.memoryPressureSkippedUrls} eligible URL${input.memoryPressureSkippedUrls === 1 ? "" : "s"} unchecked after the local memory safety limit was reached. Start with --health or lower --max-pages.`);
|
|
11443
11480
|
return caveats;
|
|
@@ -12590,6 +12627,7 @@ async function crawlOne(url, opts = {}) {
|
|
|
12590
12627
|
contentSample: truncate$1(extracted.contentText, 300),
|
|
12591
12628
|
contentSketch: crawlerEvidence.contentSketch,
|
|
12592
12629
|
softAuthenticationGate: crawlerEvidence.softAuthenticationGate,
|
|
12630
|
+
metaRefresh: crawlerEvidence.metaRefresh,
|
|
12593
12631
|
tabbedContent: crawlerEvidence.tabbedContent,
|
|
12594
12632
|
lang: extracted.lang,
|
|
12595
12633
|
hasViewport: extracted.hasViewport,
|
|
@@ -12640,9 +12678,9 @@ async function crawlOne(url, opts = {}) {
|
|
|
12640
12678
|
hasQapageSchema: extracted.schemaTypes.includes("QAPage")
|
|
12641
12679
|
}
|
|
12642
12680
|
};
|
|
12643
|
-
page.indexability = indexabilityReason(page);
|
|
12681
|
+
page.indexability = page.metaRefresh ? `Meta refresh to ${page.metaRefresh.targetUrl}` : indexabilityReason(page);
|
|
12644
12682
|
page.indexable = !page.indexability;
|
|
12645
|
-
page.declaredIndexability = hasMetaRobotsDirective(page.metaRobots, "noindex") || hasXRobotsDirective(page.xRobotsTag, "noindex") ? "noindex" : page.robotsTxt?.allowed === false ? "robots-blocked" : page.canonicalStatus === "conflicting" ? "canonical-conflict" : page.canonical && !sameDocumentUrl(page.canonical, page.finalUrl) ? "canonical-hint-other" : "indexable-candidate";
|
|
12683
|
+
page.declaredIndexability = page.metaRefresh ? "meta-refresh" : hasMetaRobotsDirective(page.metaRobots, "noindex") || hasXRobotsDirective(page.xRobotsTag, "noindex") ? "noindex" : page.robotsTxt?.allowed === false ? "robots-blocked" : page.canonicalStatus === "conflicting" ? "canonical-conflict" : page.canonical && !sameDocumentUrl(page.canonical, page.finalUrl) ? "canonical-hint-other" : "indexable-candidate";
|
|
12646
12684
|
return {
|
|
12647
12685
|
request: responseObservation(url, fetched, "complete"),
|
|
12648
12686
|
page,
|
|
@@ -13436,6 +13474,9 @@ async function crawlSite(input, dependencies) {
|
|
|
13436
13474
|
let robotsDeferredUrls = 0;
|
|
13437
13475
|
let originBackpressureSkippedUrls = 0;
|
|
13438
13476
|
let queueSafetySkippedUrls = 0;
|
|
13477
|
+
const queueSafetyDeferredUrls = /* @__PURE__ */ new Set();
|
|
13478
|
+
const maxTrackedQueueSafetyUrls = Math.min(5e4, Math.max(1e3, config.maxPages * 5));
|
|
13479
|
+
let queueSafetyInventoryCapped = false;
|
|
13439
13480
|
let failedUrls = 0;
|
|
13440
13481
|
let observedInternalLinks = 0;
|
|
13441
13482
|
let memoryLimited = false;
|
|
@@ -13540,13 +13581,16 @@ async function crawlSite(input, dependencies) {
|
|
|
13540
13581
|
return;
|
|
13541
13582
|
}
|
|
13542
13583
|
if (queue.size + visited.size + inFlight.size >= config.maxPages * 5) {
|
|
13543
|
-
|
|
13544
|
-
|
|
13545
|
-
|
|
13546
|
-
|
|
13547
|
-
|
|
13548
|
-
|
|
13549
|
-
|
|
13584
|
+
if (!queueSafetyDeferredUrls.has(normalized)) if (queueSafetyDeferredUrls.size < maxTrackedQueueSafetyUrls) {
|
|
13585
|
+
queueSafetyDeferredUrls.add(normalized);
|
|
13586
|
+
queueSafetySkippedUrls = queueSafetyDeferredUrls.size;
|
|
13587
|
+
recordSkip("queue-safety-limit");
|
|
13588
|
+
emitStatus("url_skipped", {
|
|
13589
|
+
url: normalized,
|
|
13590
|
+
depth,
|
|
13591
|
+
reason: "queue_safety_limit"
|
|
13592
|
+
});
|
|
13593
|
+
} else queueSafetyInventoryCapped = true;
|
|
13550
13594
|
return;
|
|
13551
13595
|
}
|
|
13552
13596
|
discovered.add(normalized);
|
|
@@ -13919,6 +13963,7 @@ async function crawlSite(input, dependencies) {
|
|
|
13919
13963
|
pageLimitReached,
|
|
13920
13964
|
maxPages: config.maxPages,
|
|
13921
13965
|
queueSafetySkippedUrls,
|
|
13966
|
+
queueSafetyInventoryCapped,
|
|
13922
13967
|
originBackpressureSkippedUrls,
|
|
13923
13968
|
memoryPressureSkippedUrls
|
|
13924
13969
|
}),
|
|
@@ -15401,6 +15446,7 @@ const crawlPageSnapshotSchema = z.object({
|
|
|
15401
15446
|
"robots-blocked",
|
|
15402
15447
|
"canonical-conflict",
|
|
15403
15448
|
"canonical-hint-other",
|
|
15449
|
+
"meta-refresh",
|
|
15404
15450
|
"not-html",
|
|
15405
15451
|
"unknown"
|
|
15406
15452
|
]).optional(),
|
|
@@ -15447,6 +15493,10 @@ const crawlPageSnapshotSchema = z.object({
|
|
|
15447
15493
|
indicators: z.array(z.string()),
|
|
15448
15494
|
formActionPath: z.string().optional()
|
|
15449
15495
|
}).optional(),
|
|
15496
|
+
metaRefresh: z.object({
|
|
15497
|
+
targetUrl: z.string().url(),
|
|
15498
|
+
delaySeconds: z.number().nonnegative()
|
|
15499
|
+
}).optional(),
|
|
15450
15500
|
tabbedContent: z.object({
|
|
15451
15501
|
groups: z.number().int().positive(),
|
|
15452
15502
|
panels: z.number().int().positive(),
|