seo 0.2.20 → 0.2.21
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 +24 -4
- package/dist/cli.js +115 -115
- package/dist/cli.js.map +1 -1
- package/dist/{dist-CtDv7OFQ.js → dist-B-zhNcyH.js} +107 -107
- package/dist/dist-B-zhNcyH.js.map +1 -0
- package/dist/{dist-DXAAiZSX.js → dist-BITHJak_.js} +1 -1
- package/dist/{esm-DvJj2-vq.js → esm-CSE-hgHf.js} +2 -2
- package/dist/{esm-DvJj2-vq.js.map → esm-CSE-hgHf.js.map} +1 -1
- package/dist/index.d.ts +558 -99
- package/dist/index.js +1377 -229
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +1435 -251
- package/dist/mcp.js.map +1 -1
- package/evals/link-evidence.json +13 -0
- package/package.json +1 -1
- package/skills/seo/SKILL.md +4 -3
- package/dist/dist-CtDv7OFQ.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 version, t as load } from "./esm-
|
|
2
|
+
import { n as version, t as load } from "./esm-CSE-hgHf.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";
|
|
@@ -1814,7 +1814,7 @@ function aiReferralSourceForValue(value) {
|
|
|
1814
1814
|
function compareText$18(left, right) {
|
|
1815
1815
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
1816
1816
|
}
|
|
1817
|
-
function metric(row, name) {
|
|
1817
|
+
function metric$1(row, name) {
|
|
1818
1818
|
const value = row[name];
|
|
1819
1819
|
const parsed = Number(value);
|
|
1820
1820
|
if (value === void 0 || value.trim() === "" || !Number.isInteger(parsed) || parsed < 0) throw new SeoError("PROVIDER_UNAVAILABLE", `Google Analytics returned an invalid ${name} value.`);
|
|
@@ -1849,8 +1849,8 @@ function analyzeAiReferralRows(input) {
|
|
|
1849
1849
|
...emptyMetrics()
|
|
1850
1850
|
};
|
|
1851
1851
|
addMetrics(total, {
|
|
1852
|
-
sessions: metric(row, "sessions"),
|
|
1853
|
-
eventCount: metric(row, "eventCount")
|
|
1852
|
+
sessions: metric$1(row, "sessions"),
|
|
1853
|
+
eventCount: metric$1(row, "eventCount")
|
|
1854
1854
|
});
|
|
1855
1855
|
total.observedSessionSources.add(row.sessionSource ?? "");
|
|
1856
1856
|
sourceTotals.set(definition.id, total);
|
|
@@ -1862,8 +1862,8 @@ function analyzeAiReferralRows(input) {
|
|
|
1862
1862
|
const definition = aiReferralSourceForValue(row.sessionSource ?? "");
|
|
1863
1863
|
if (!definition || !sourceTotals.has(definition.id)) throw new SeoError("PROVIDER_UNAVAILABLE", "Google Analytics detail rows contained an unexpected session source.");
|
|
1864
1864
|
const metrics = {
|
|
1865
|
-
sessions: metric(row, "sessions"),
|
|
1866
|
-
eventCount: metric(row, "eventCount")
|
|
1865
|
+
sessions: metric$1(row, "sessions"),
|
|
1866
|
+
eventCount: metric$1(row, "eventCount")
|
|
1867
1867
|
};
|
|
1868
1868
|
addMetrics(detailTotal, metrics);
|
|
1869
1869
|
const landingPage = row.landingPagePlusQueryString || "(not set)";
|
|
@@ -5323,7 +5323,7 @@ function estimateSerpTitleWidth(title) {
|
|
|
5323
5323
|
}
|
|
5324
5324
|
//#endregion
|
|
5325
5325
|
//#region packages/core/dist/analyze/audit-page.js
|
|
5326
|
-
const defaultDependencies$
|
|
5326
|
+
const defaultDependencies$16 = {
|
|
5327
5327
|
fetchPage,
|
|
5328
5328
|
queryPageMetrics,
|
|
5329
5329
|
now: () => /* @__PURE__ */ new Date()
|
|
@@ -5354,7 +5354,7 @@ function buildRecommendations(report) {
|
|
|
5354
5354
|
});
|
|
5355
5355
|
return recommendations;
|
|
5356
5356
|
}
|
|
5357
|
-
async function auditPage(input, dependencies = defaultDependencies$
|
|
5357
|
+
async function auditPage(input, dependencies = defaultDependencies$16) {
|
|
5358
5358
|
const fetched = await dependencies.fetchPage(input.url, {
|
|
5359
5359
|
js: input.js,
|
|
5360
5360
|
refresh: input.refresh
|
|
@@ -6928,7 +6928,7 @@ function pageOpportunityRecommendations(analysis) {
|
|
|
6928
6928
|
//#endregion
|
|
6929
6929
|
//#region packages/core/dist/analyze/page-opportunities.js
|
|
6930
6930
|
const MAX_BENCHMARK_ROWS = 1e5;
|
|
6931
|
-
const defaultDependencies$
|
|
6931
|
+
const defaultDependencies$15 = {
|
|
6932
6932
|
searchAnalytics: querySearchAnalytics,
|
|
6933
6933
|
fetch: fetchPage,
|
|
6934
6934
|
extract: extractPage,
|
|
@@ -7000,7 +7000,7 @@ function reportCaveats$1(input) {
|
|
|
7000
7000
|
input.benchmarkTruncated ? `The site-wide benchmark reached its ${MAX_BENCHMARK_ROWS.toLocaleString("en-US")}-row cap, so provenance is complete but the peer sample is truncated.` : ""
|
|
7001
7001
|
].filter(Boolean);
|
|
7002
7002
|
}
|
|
7003
|
-
async function pageOpportunitiesReport(input, dependencies = defaultDependencies$
|
|
7003
|
+
async function pageOpportunitiesReport(input, dependencies = defaultDependencies$15) {
|
|
7004
7004
|
const url = assertUrlMatchesGscProperty(input.site, input.url);
|
|
7005
7005
|
const days = reportDays$1(input.days);
|
|
7006
7006
|
const range = defaultDateRange$1(days);
|
|
@@ -10787,7 +10787,7 @@ function absoluteHttpUrl(value) {
|
|
|
10787
10787
|
return false;
|
|
10788
10788
|
}
|
|
10789
10789
|
}
|
|
10790
|
-
function urlKey(value) {
|
|
10790
|
+
function urlKey$1(value) {
|
|
10791
10791
|
if (!value) return void 0;
|
|
10792
10792
|
try {
|
|
10793
10793
|
const url = new URL(value);
|
|
@@ -10986,7 +10986,7 @@ function auditCrawlPages(pages, opts = {}) {
|
|
|
10986
10986
|
for (const page of pages) {
|
|
10987
10987
|
if (!isAuditableHtmlPage(page)) continue;
|
|
10988
10988
|
for (const value of [page.url, page.finalUrl]) {
|
|
10989
|
-
const key = urlKey(value);
|
|
10989
|
+
const key = urlKey$1(value);
|
|
10990
10990
|
if (key) pageByUrl.set(key, page);
|
|
10991
10991
|
}
|
|
10992
10992
|
}
|
|
@@ -11128,7 +11128,7 @@ function auditCrawlPages(pages, opts = {}) {
|
|
|
11128
11128
|
canonicalRaw: page.canonicalRaw,
|
|
11129
11129
|
canonical: page.canonical
|
|
11130
11130
|
}));
|
|
11131
|
-
const canonicalTarget = pageByUrl.get(urlKey(page.canonical) ?? "");
|
|
11131
|
+
const canonicalTarget = pageByUrl.get(urlKey$1(page.canonical) ?? "");
|
|
11132
11132
|
if (canonicalTarget?.canonical && !sameUrl(canonicalTarget.canonical, page.canonical)) issues.push(issue("canonical_chain", page, page.canonical, {
|
|
11133
11133
|
canonical: page.canonical,
|
|
11134
11134
|
nextCanonical: canonicalTarget.canonical,
|
|
@@ -11302,7 +11302,7 @@ const SENSITIVE_FIELD_NAMES = /* @__PURE__ */ new Set([
|
|
|
11302
11302
|
"refresh-token"
|
|
11303
11303
|
]);
|
|
11304
11304
|
const SENSITIVE_PARAM_PATTERN = /^(access_?token|auth|api_?key|client_?secret|code|jwt|key|password|refresh_?token|secret|session|sig|signature|token)$/i;
|
|
11305
|
-
function uniqueSorted(values = []) {
|
|
11305
|
+
function uniqueSorted$1(values = []) {
|
|
11306
11306
|
return [...new Set(values.filter(Boolean))].sort();
|
|
11307
11307
|
}
|
|
11308
11308
|
function normalizeFetchRate(input, strategy) {
|
|
@@ -11322,13 +11322,13 @@ function normalizeCrawlConfig(input) {
|
|
|
11322
11322
|
mode: input.mode ?? (strategy === "health" ? "sitemap" : "site"),
|
|
11323
11323
|
strategy,
|
|
11324
11324
|
...input.sitemapUrl ? { sitemapUrl: new URL(input.sitemapUrl).toString() } : {},
|
|
11325
|
-
urls: uniqueSorted(input.urls ?? []),
|
|
11325
|
+
urls: uniqueSorted$1(input.urls ?? []),
|
|
11326
11326
|
maxPages: input.maxPages ?? 500,
|
|
11327
11327
|
maxDepth: input.maxDepth ?? 16,
|
|
11328
11328
|
concurrency,
|
|
11329
11329
|
timeoutMs: input.timeoutMs ?? 2e4,
|
|
11330
|
-
include: uniqueSorted(input.include ?? []),
|
|
11331
|
-
exclude: uniqueSorted(input.exclude ?? []),
|
|
11330
|
+
include: uniqueSorted$1(input.include ?? []),
|
|
11331
|
+
exclude: uniqueSorted$1(input.exclude ?? []),
|
|
11332
11332
|
respectRobots: input.respectRobots ?? true,
|
|
11333
11333
|
useSitemap: input.useSitemap ?? true,
|
|
11334
11334
|
checkExternal: strategy === "health" ? false : input.checkExternal ?? true,
|
|
@@ -13573,7 +13573,7 @@ async function fetchSitemapUrls(input) {
|
|
|
13573
13573
|
}
|
|
13574
13574
|
//#endregion
|
|
13575
13575
|
//#region packages/core/dist/analyze/workflows/analytics-value.js
|
|
13576
|
-
const defaultDependencies$
|
|
13576
|
+
const defaultDependencies$14 = { runGa4Report };
|
|
13577
13577
|
function normalizePath(value) {
|
|
13578
13578
|
if (!value || value === "(not set)") return "";
|
|
13579
13579
|
const [path = ""] = value.split("?");
|
|
@@ -13633,7 +13633,7 @@ function landingPageRankingPolicy(input) {
|
|
|
13633
13633
|
warnings
|
|
13634
13634
|
};
|
|
13635
13635
|
}
|
|
13636
|
-
async function fetchLandingPageValues(input, dependencies = defaultDependencies$
|
|
13636
|
+
async function fetchLandingPageValues(input, dependencies = defaultDependencies$14) {
|
|
13637
13637
|
const retainedRowLimit = input.limit ?? 5e3;
|
|
13638
13638
|
if (!input.propertyId) return {
|
|
13639
13639
|
values: /* @__PURE__ */ new Map(),
|
|
@@ -16601,15 +16601,15 @@ function analyzeCtrUnderperformersFromRows(input) {
|
|
|
16601
16601
|
}
|
|
16602
16602
|
//#endregion
|
|
16603
16603
|
//#region packages/core/dist/analyze/ctr-underperformers.js
|
|
16604
|
-
const MAX_GSC_ROWS$
|
|
16604
|
+
const MAX_GSC_ROWS$5 = 1e5;
|
|
16605
16605
|
function plural$3(count, singular, pluralLabel = `${singular}s`) {
|
|
16606
16606
|
return count === 1 ? singular : pluralLabel;
|
|
16607
16607
|
}
|
|
16608
|
-
const defaultDependencies$
|
|
16608
|
+
const defaultDependencies$13 = {
|
|
16609
16609
|
searchAnalytics: querySearchAnalytics,
|
|
16610
16610
|
now: () => /* @__PURE__ */ new Date()
|
|
16611
16611
|
};
|
|
16612
|
-
async function ctrUnderperformersReport(input, dependencies = defaultDependencies$
|
|
16612
|
+
async function ctrUnderperformersReport(input, dependencies = defaultDependencies$13) {
|
|
16613
16613
|
const minImpressions = integerOption$2({
|
|
16614
16614
|
value: input.minImpressions,
|
|
16615
16615
|
fallback: 200,
|
|
@@ -16631,7 +16631,7 @@ async function ctrUnderperformersReport(input, dependencies = defaultDependencie
|
|
|
16631
16631
|
dimensions: ["query", "page"],
|
|
16632
16632
|
type: "web",
|
|
16633
16633
|
dataState: "final",
|
|
16634
|
-
maxRows: MAX_GSC_ROWS$
|
|
16634
|
+
maxRows: MAX_GSC_ROWS$5
|
|
16635
16635
|
}, { refresh: input.refresh });
|
|
16636
16636
|
const { items, totalClickShortfall, returnedClickShortfall, selection } = analyzeCtrUnderperformersFromRows({
|
|
16637
16637
|
rows: source.rows,
|
|
@@ -16642,7 +16642,7 @@ async function ctrUnderperformersReport(input, dependencies = defaultDependencie
|
|
|
16642
16642
|
includeBrand: input.includeBrand
|
|
16643
16643
|
});
|
|
16644
16644
|
const top = items[0];
|
|
16645
|
-
const possiblyTruncated = source.rowsFetched >= MAX_GSC_ROWS$
|
|
16645
|
+
const possiblyTruncated = source.rowsFetched >= MAX_GSC_ROWS$5;
|
|
16646
16646
|
const partialValidation = selection.invalidRows > 0;
|
|
16647
16647
|
const dataStatus = selection.sourceRows === 0 ? "empty" : possiblyTruncated || partialValidation ? "partial" : selection.eligibleUnderperformers === 0 ? "filtered" : "complete";
|
|
16648
16648
|
const completeness = possiblyTruncated && partialValidation ? "partial-and-possibly-truncated" : possiblyTruncated ? "possibly-truncated" : partialValidation ? "partial" : "complete";
|
|
@@ -16659,7 +16659,7 @@ async function ctrUnderperformersReport(input, dependencies = defaultDependencie
|
|
|
16659
16659
|
dataState: "final",
|
|
16660
16660
|
rowsFetched: source.rowsFetched,
|
|
16661
16661
|
calls: source.calls,
|
|
16662
|
-
maxRows: MAX_GSC_ROWS$
|
|
16662
|
+
maxRows: MAX_GSC_ROWS$5,
|
|
16663
16663
|
possiblyTruncated,
|
|
16664
16664
|
completeness,
|
|
16665
16665
|
validation: {
|
|
@@ -16689,7 +16689,7 @@ async function ctrUnderperformersReport(input, dependencies = defaultDependencie
|
|
|
16689
16689
|
"CTR benchmarks and calculated click shortfalls are directional heuristics, not forecasts or proof that clicks are available. Validate the search intent and live SERP before editing.",
|
|
16690
16690
|
...selection.invalidRows ? [`${selection.invalidRows} invalid provider ${plural$3(selection.invalidRows, "row")} ${selection.invalidRows === 1 ? "was" : "were"} excluded, so this report is partial.`] : [],
|
|
16691
16691
|
...selection.duplicateRows ? [`${selection.duplicateRows} repeated query/page ${plural$3(selection.duplicateRows, "row")} ${selection.duplicateRows === 1 ? "was" : "were"} aggregated before CTR and average position were calculated.`] : [],
|
|
16692
|
-
...possiblyTruncated ? [`The retained-row limit of ${MAX_GSC_ROWS$
|
|
16692
|
+
...possiblyTruncated ? [`The retained-row limit of ${MAX_GSC_ROWS$5} was reached, so lower-ranked source rows may be absent.`] : [],
|
|
16693
16693
|
...selection.limitedUnderperformers ? [`${selection.limitedUnderperformers} eligible ${plural$3(selection.limitedUnderperformers, "underperformer")} ${selection.limitedUnderperformers === 1 ? "was" : "were"} omitted by the output limit.`] : []
|
|
16694
16694
|
],
|
|
16695
16695
|
recommendations: top ? [
|
|
@@ -17202,11 +17202,11 @@ function unavailableSegmentImpactReport(input) {
|
|
|
17202
17202
|
//#endregion
|
|
17203
17203
|
//#region packages/core/dist/analyze/segment-impact.js
|
|
17204
17204
|
const DEFAULT_MAX_ROWS = 1e5;
|
|
17205
|
-
const defaultDependencies$
|
|
17205
|
+
const defaultDependencies$12 = {
|
|
17206
17206
|
searchAnalytics: querySearchAnalytics,
|
|
17207
17207
|
now: () => /* @__PURE__ */ new Date()
|
|
17208
17208
|
};
|
|
17209
|
-
async function segmentImpact(input, dependencies = defaultDependencies$
|
|
17209
|
+
async function segmentImpact(input, dependencies = defaultDependencies$12) {
|
|
17210
17210
|
validateSegmentSite(input.site);
|
|
17211
17211
|
const now = dependencies.now();
|
|
17212
17212
|
const ranges = resolveSegmentRanges({
|
|
@@ -17759,8 +17759,8 @@ function analyzeCannibalRows(input) {
|
|
|
17759
17759
|
}
|
|
17760
17760
|
//#endregion
|
|
17761
17761
|
//#region packages/core/dist/analyze/site-diagnostics/cannibal.js
|
|
17762
|
-
const MAX_GSC_ROWS$
|
|
17763
|
-
const defaultDependencies$
|
|
17762
|
+
const MAX_GSC_ROWS$4 = 1e5;
|
|
17763
|
+
const defaultDependencies$11 = {
|
|
17764
17764
|
searchAnalytics: querySearchAnalytics,
|
|
17765
17765
|
now: () => /* @__PURE__ */ new Date()
|
|
17766
17766
|
};
|
|
@@ -17773,7 +17773,7 @@ function verdict$4(input) {
|
|
|
17773
17773
|
const result = `${input.returned} of ${input.eligible} multi-URL query candidate${input.eligible === 1 ? "" : "s"} returned for intent and technical review.`;
|
|
17774
17774
|
return input.status === "partial" ? `${result} Provider evidence is partial.` : result;
|
|
17775
17775
|
}
|
|
17776
|
-
async function cannibalReport(input, dependencies = defaultDependencies$
|
|
17776
|
+
async function cannibalReport(input, dependencies = defaultDependencies$11) {
|
|
17777
17777
|
const days = integerOption$2({
|
|
17778
17778
|
value: input.days,
|
|
17779
17779
|
fallback: 28,
|
|
@@ -17803,7 +17803,7 @@ async function cannibalReport(input, dependencies = defaultDependencies$10) {
|
|
|
17803
17803
|
...range,
|
|
17804
17804
|
type: "web",
|
|
17805
17805
|
dataState: "final",
|
|
17806
|
-
maxRows: MAX_GSC_ROWS$
|
|
17806
|
+
maxRows: MAX_GSC_ROWS$4
|
|
17807
17807
|
};
|
|
17808
17808
|
const [pageExposure, propertyDemand] = await Promise.all([dependencies.searchAnalytics(input.site, {
|
|
17809
17809
|
...requestBase,
|
|
@@ -17826,8 +17826,8 @@ async function cannibalReport(input, dependencies = defaultDependencies$10) {
|
|
|
17826
17826
|
brandTerms: input.brandTerms,
|
|
17827
17827
|
includeBrand: input.includeBrand
|
|
17828
17828
|
});
|
|
17829
|
-
const pageTruncated = pageExposure.rowsFetched >= MAX_GSC_ROWS$
|
|
17830
|
-
const propertyTruncated = propertyDemand.rowsFetched >= MAX_GSC_ROWS$
|
|
17829
|
+
const pageTruncated = pageExposure.rowsFetched >= MAX_GSC_ROWS$4;
|
|
17830
|
+
const propertyTruncated = propertyDemand.rowsFetched >= MAX_GSC_ROWS$4;
|
|
17831
17831
|
const possiblyTruncated = pageTruncated || propertyTruncated;
|
|
17832
17832
|
const partialValidation = analysis.selection.invalidRows + analysis.selection.propertyInvalidRows > 0;
|
|
17833
17833
|
const completeness = possiblyTruncated && partialValidation ? "partial-and-possibly-truncated" : possiblyTruncated ? "possibly-truncated" : partialValidation ? "partial" : "complete";
|
|
@@ -17852,7 +17852,7 @@ async function cannibalReport(input, dependencies = defaultDependencies$10) {
|
|
|
17852
17852
|
invalidRows: analysis.selection.invalidRows
|
|
17853
17853
|
},
|
|
17854
17854
|
calls: pageExposure.calls,
|
|
17855
|
-
maxRows: MAX_GSC_ROWS$
|
|
17855
|
+
maxRows: MAX_GSC_ROWS$4,
|
|
17856
17856
|
possiblyTruncated: pageTruncated
|
|
17857
17857
|
},
|
|
17858
17858
|
propertyDemand: {
|
|
@@ -17864,7 +17864,7 @@ async function cannibalReport(input, dependencies = defaultDependencies$10) {
|
|
|
17864
17864
|
invalidRows: analysis.selection.propertyInvalidRows
|
|
17865
17865
|
},
|
|
17866
17866
|
calls: propertyDemand.calls,
|
|
17867
|
-
maxRows: MAX_GSC_ROWS$
|
|
17867
|
+
maxRows: MAX_GSC_ROWS$4,
|
|
17868
17868
|
possiblyTruncated: propertyTruncated
|
|
17869
17869
|
},
|
|
17870
17870
|
completeness
|
|
@@ -18172,8 +18172,8 @@ function analyzeDecay(input) {
|
|
|
18172
18172
|
}
|
|
18173
18173
|
//#endregion
|
|
18174
18174
|
//#region packages/core/dist/analyze/site-diagnostics/decaying.js
|
|
18175
|
-
const MAX_GSC_ROWS$
|
|
18176
|
-
const defaultDependencies$
|
|
18175
|
+
const MAX_GSC_ROWS$3 = 1e5;
|
|
18176
|
+
const defaultDependencies$10 = {
|
|
18177
18177
|
searchAnalytics: querySearchAnalytics,
|
|
18178
18178
|
now: () => /* @__PURE__ */ new Date()
|
|
18179
18179
|
};
|
|
@@ -18184,7 +18184,7 @@ function verdict$3(report) {
|
|
|
18184
18184
|
if (!report.selection.eligibleRows) return "No observed retained query/page declines matched the report filters.";
|
|
18185
18185
|
return `${report.selection.returnedRows} of ${report.selection.eligibleRows} observed retained query/page declines returned for review.`;
|
|
18186
18186
|
}
|
|
18187
|
-
async function decayingReport(input, dependencies = defaultDependencies$
|
|
18187
|
+
async function decayingReport(input, dependencies = defaultDependencies$10) {
|
|
18188
18188
|
if (input.comparison !== void 0 && input.comparison !== "previous-period" && input.comparison !== "year-over-year") throw new SeoError("INVALID_INPUT", "comparison must be previous-period or year-over-year.");
|
|
18189
18189
|
if (input.brandTerms && (input.brandTerms.length > 20 || input.brandTerms.some((term) => !term.trim() || term.trim().length > 200))) throw new SeoError("INVALID_INPUT", "brandTerms must contain at most 20 non-empty terms of 200 characters or fewer.");
|
|
18190
18190
|
const days = integerOption$2({
|
|
@@ -18237,7 +18237,7 @@ async function decayingReport(input, dependencies = defaultDependencies$9) {
|
|
|
18237
18237
|
aggregationType: "auto",
|
|
18238
18238
|
type: "web",
|
|
18239
18239
|
dataState: "final",
|
|
18240
|
-
maxRows: MAX_GSC_ROWS$
|
|
18240
|
+
maxRows: MAX_GSC_ROWS$3
|
|
18241
18241
|
};
|
|
18242
18242
|
const [current, previous] = await Promise.all([dependencies.searchAnalytics(input.site, {
|
|
18243
18243
|
...request,
|
|
@@ -18260,8 +18260,8 @@ async function decayingReport(input, dependencies = defaultDependencies$9) {
|
|
|
18260
18260
|
brandTerms: input.brandTerms,
|
|
18261
18261
|
includeBrand: input.includeBrand
|
|
18262
18262
|
});
|
|
18263
|
-
const currentTruncated = current.rowsFetched >= MAX_GSC_ROWS$
|
|
18264
|
-
const previousTruncated = previous.rowsFetched >= MAX_GSC_ROWS$
|
|
18263
|
+
const currentTruncated = current.rowsFetched >= MAX_GSC_ROWS$3;
|
|
18264
|
+
const previousTruncated = previous.rowsFetched >= MAX_GSC_ROWS$3;
|
|
18265
18265
|
const possiblyTruncated = currentTruncated || previousTruncated;
|
|
18266
18266
|
const dataStatus = current.rowsFetched === 0 && previous.rowsFetched === 0 ? "empty" : current.rowsFetched === 0 || previous.rowsFetched === 0 || possiblyTruncated || analysis.selection.currentInvalidRows > 0 || analysis.selection.previousInvalidRows > 0 || analysis.selection.currentRowNotRetained > 0 ? "partial" : analysis.selection.eligibleRows === 0 ? "filtered" : "complete";
|
|
18267
18267
|
const reportBase = {
|
|
@@ -18288,13 +18288,13 @@ async function decayingReport(input, dependencies = defaultDependencies$9) {
|
|
|
18288
18288
|
current: {
|
|
18289
18289
|
rowsFetched: current.rowsFetched,
|
|
18290
18290
|
calls: current.calls,
|
|
18291
|
-
maxRows: MAX_GSC_ROWS$
|
|
18291
|
+
maxRows: MAX_GSC_ROWS$3,
|
|
18292
18292
|
possiblyTruncated: currentTruncated
|
|
18293
18293
|
},
|
|
18294
18294
|
previous: {
|
|
18295
18295
|
rowsFetched: previous.rowsFetched,
|
|
18296
18296
|
calls: previous.calls,
|
|
18297
|
-
maxRows: MAX_GSC_ROWS$
|
|
18297
|
+
maxRows: MAX_GSC_ROWS$3,
|
|
18298
18298
|
possiblyTruncated: previousTruncated
|
|
18299
18299
|
},
|
|
18300
18300
|
completeness: possiblyTruncated ? "possibly-truncated" : "retained-query-rows-only"
|
|
@@ -18670,12 +18670,12 @@ function quickWinTemplateSummaries(items) {
|
|
|
18670
18670
|
}
|
|
18671
18671
|
//#endregion
|
|
18672
18672
|
//#region packages/core/dist/analyze/site-diagnostics/quick-wins.js
|
|
18673
|
-
const DEFAULT_DAYS$
|
|
18674
|
-
const MAX_DAYS$
|
|
18673
|
+
const DEFAULT_DAYS$3 = 28;
|
|
18674
|
+
const MAX_DAYS$3 = 548;
|
|
18675
18675
|
const DEFAULT_VERIFY_LIMIT$1 = 5;
|
|
18676
18676
|
const MAX_VERIFY_LIMIT = 100;
|
|
18677
18677
|
const MAX_SOURCE_ROWS$3 = 1e5;
|
|
18678
|
-
const defaultDependencies$
|
|
18678
|
+
const defaultDependencies$9 = {
|
|
18679
18679
|
searchAnalytics: querySearchAnalytics,
|
|
18680
18680
|
fetch: fetchPage,
|
|
18681
18681
|
extract: extractPage,
|
|
@@ -18694,13 +18694,13 @@ function applyVerification$1(item, coverage) {
|
|
|
18694
18694
|
evidenceRef: `${item.recommendation.evidenceRef} ${coverage.summary}`
|
|
18695
18695
|
};
|
|
18696
18696
|
}
|
|
18697
|
-
async function quickWinsReport(input, dependencies = defaultDependencies$
|
|
18698
|
-
const explicitRange = explicitDateRange(input, MAX_DAYS$
|
|
18697
|
+
async function quickWinsReport(input, dependencies = defaultDependencies$9) {
|
|
18698
|
+
const explicitRange = explicitDateRange(input, MAX_DAYS$3);
|
|
18699
18699
|
const days = explicitRange?.days ?? integerOption$2({
|
|
18700
18700
|
value: input.days,
|
|
18701
|
-
fallback: DEFAULT_DAYS$
|
|
18701
|
+
fallback: DEFAULT_DAYS$3,
|
|
18702
18702
|
minimum: 1,
|
|
18703
|
-
maximum: MAX_DAYS$
|
|
18703
|
+
maximum: MAX_DAYS$3,
|
|
18704
18704
|
label: "Days"
|
|
18705
18705
|
});
|
|
18706
18706
|
const verifyLimit = integerOption$2({
|
|
@@ -19067,11 +19067,11 @@ function analyzeStrikingDistanceRows(input) {
|
|
|
19067
19067
|
}
|
|
19068
19068
|
//#endregion
|
|
19069
19069
|
//#region packages/core/dist/analyze/striking-distance.js
|
|
19070
|
-
const DEFAULT_DAYS$
|
|
19071
|
-
const MAX_DAYS$
|
|
19070
|
+
const DEFAULT_DAYS$2 = 28;
|
|
19071
|
+
const MAX_DAYS$2 = 548;
|
|
19072
19072
|
const DEFAULT_VERIFY_LIMIT = 5;
|
|
19073
19073
|
const MAX_SOURCE_ROWS$2 = 1e5;
|
|
19074
|
-
const defaultDependencies$
|
|
19074
|
+
const defaultDependencies$8 = {
|
|
19075
19075
|
searchAnalytics: querySearchAnalytics,
|
|
19076
19076
|
verifyContent: verifyQueryContent,
|
|
19077
19077
|
now: () => /* @__PURE__ */ new Date()
|
|
@@ -19081,8 +19081,8 @@ function boundedInteger$3(value, fallback, minimum, maximum) {
|
|
|
19081
19081
|
return Math.min(maximum, Math.max(minimum, Math.floor(value)));
|
|
19082
19082
|
}
|
|
19083
19083
|
function reportDays(value) {
|
|
19084
|
-
if (value === void 0) return DEFAULT_DAYS$
|
|
19085
|
-
if (!Number.isInteger(value) || value < 1 || value > MAX_DAYS$
|
|
19084
|
+
if (value === void 0) return DEFAULT_DAYS$2;
|
|
19085
|
+
if (!Number.isInteger(value) || value < 1 || value > MAX_DAYS$2) throw new SeoError("INVALID_INPUT", `Days must be a whole number between 1 and ${MAX_DAYS$2}.`);
|
|
19086
19086
|
return value;
|
|
19087
19087
|
}
|
|
19088
19088
|
function dateRange(days, now) {
|
|
@@ -19121,7 +19121,7 @@ function verdict$2(analysis) {
|
|
|
19121
19121
|
if (analysis.dataStatus === "filtered") return "No retained query/page rows met the report filters.";
|
|
19122
19122
|
return `${countLabel(analysis.summary.eligibleRows, "eligible query/page row")} found; ${countLabel(analysis.summary.returnedRows, "row")} returned for review.`;
|
|
19123
19123
|
}
|
|
19124
|
-
async function strikingDistance(input, dependencies = defaultDependencies$
|
|
19124
|
+
async function strikingDistance(input, dependencies = defaultDependencies$8) {
|
|
19125
19125
|
const days = reportDays(input.days);
|
|
19126
19126
|
const generatedAt = dependencies.now().toISOString();
|
|
19127
19127
|
const range = dateRange(days, new Date(generatedAt));
|
|
@@ -19968,7 +19968,7 @@ function emptyQuickWins(input) {
|
|
|
19968
19968
|
warnings: []
|
|
19969
19969
|
};
|
|
19970
19970
|
}
|
|
19971
|
-
const defaultDependencies$
|
|
19971
|
+
const defaultDependencies$7 = {
|
|
19972
19972
|
trafficAnomaly,
|
|
19973
19973
|
updateCorrelation,
|
|
19974
19974
|
segmentImpact,
|
|
@@ -19979,7 +19979,7 @@ const defaultDependencies$6 = {
|
|
|
19979
19979
|
};
|
|
19980
19980
|
async function diagnoseProperty(input, dependencies = {}) {
|
|
19981
19981
|
const providers = {
|
|
19982
|
-
...defaultDependencies$
|
|
19982
|
+
...defaultDependencies$7,
|
|
19983
19983
|
...dependencies
|
|
19984
19984
|
};
|
|
19985
19985
|
const limit = input.limit ?? 10;
|
|
@@ -20843,6 +20843,42 @@ async function providerRequestJson(input) {
|
|
|
20843
20843
|
return parsed.data;
|
|
20844
20844
|
}
|
|
20845
20845
|
//#endregion
|
|
20846
|
+
//#region packages/core/dist/providers/dataforseo/account-pricing.js
|
|
20847
|
+
function unitPrice(components) {
|
|
20848
|
+
const prices = components ?? [];
|
|
20849
|
+
const total = (type) => {
|
|
20850
|
+
const matching = prices.filter((item) => item.cost_type === type);
|
|
20851
|
+
return matching.length ? matching.reduce((sum, item) => sum + Math.round(item.cost * 1e6), 0) : prices.length ? 0 : null;
|
|
20852
|
+
};
|
|
20853
|
+
return {
|
|
20854
|
+
perRequestMicros: total("per_request"),
|
|
20855
|
+
perResultMicros: total("per_result")
|
|
20856
|
+
};
|
|
20857
|
+
}
|
|
20858
|
+
function dataForSeoAccountPrices(account) {
|
|
20859
|
+
return {
|
|
20860
|
+
keywordOverviewPrice: unitPrice(account.price?.dataforseo_labs?.keyword_overview?.live?.priority_normal),
|
|
20861
|
+
keywordDiscoveryPrices: {
|
|
20862
|
+
ideas: unitPrice(account.price?.dataforseo_labs?.keyword_ideas?.live?.priority_normal),
|
|
20863
|
+
related: unitPrice(account.price?.dataforseo_labs?.related_keywords?.live?.priority_normal),
|
|
20864
|
+
suggestions: unitPrice(account.price?.dataforseo_labs?.keyword_suggestions?.live?.priority_normal)
|
|
20865
|
+
},
|
|
20866
|
+
domainResearchPrices: {
|
|
20867
|
+
domainOverview: unitPrice(account.price?.dataforseo_labs?.domain_rank_overview?.live?.priority_normal),
|
|
20868
|
+
rankedKeywords: unitPrice(account.price?.dataforseo_labs?.ranked_keywords?.live?.priority_normal),
|
|
20869
|
+
rankingPages: unitPrice(account.price?.dataforseo_labs?.relevant_pages?.live?.priority_normal),
|
|
20870
|
+
serpCompetitors: unitPrice(account.price?.dataforseo_labs?.serp_competitors?.live?.priority_normal)
|
|
20871
|
+
},
|
|
20872
|
+
linkPrices: {
|
|
20873
|
+
summary: unitPrice(account.price?.backlinks?.summary?.live?.priority_normal),
|
|
20874
|
+
backlinks: unitPrice(account.price?.backlinks?.backlinks?.live?.priority_normal),
|
|
20875
|
+
referringDomains: unitPrice(account.price?.backlinks?.referring_domains?.live?.priority_normal)
|
|
20876
|
+
},
|
|
20877
|
+
serpLiveAdvancedPrice: unitPrice(account.price?.serp?.live?.advanced?.priority_normal),
|
|
20878
|
+
serpTaskPostPrice: unitPrice(account.price?.serp?.task_post?.priority_normal)
|
|
20879
|
+
};
|
|
20880
|
+
}
|
|
20881
|
+
//#endregion
|
|
20846
20882
|
//#region packages/core/dist/providers/dataforseo/account-schema.js
|
|
20847
20883
|
const userDataMoneySchema = z.object({
|
|
20848
20884
|
total: z.number().finite().nonnegative().optional(),
|
|
@@ -20860,6 +20896,11 @@ const priceComponentSchema = z.object({
|
|
|
20860
20896
|
const normalPriorityPriceSchema = z.object({ priority_normal: z.array(priceComponentSchema).max(20).optional() }).passthrough();
|
|
20861
20897
|
const livePriceSchema = z.object({ live: normalPriorityPriceSchema.optional() }).passthrough();
|
|
20862
20898
|
const userDataPriceSchema = z.object({
|
|
20899
|
+
backlinks: z.object({
|
|
20900
|
+
summary: livePriceSchema.optional(),
|
|
20901
|
+
backlinks: livePriceSchema.optional(),
|
|
20902
|
+
referring_domains: livePriceSchema.optional()
|
|
20903
|
+
}).passthrough().optional(),
|
|
20863
20904
|
dataforseo_labs: z.object({
|
|
20864
20905
|
keyword_overview: livePriceSchema.optional(),
|
|
20865
20906
|
keyword_ideas: livePriceSchema.optional(),
|
|
@@ -20917,7 +20958,7 @@ function discoveryNextCursor(response) {
|
|
|
20917
20958
|
//#region packages/core/dist/providers/dataforseo/schema.js
|
|
20918
20959
|
const MAX_PROVIDER_MONTHLY_SEARCH_ROWS = 120;
|
|
20919
20960
|
const nullableNumber$1 = z.number().finite().nullable().optional();
|
|
20920
|
-
const nullableCount$
|
|
20961
|
+
const nullableCount$2 = z.union([z.number().finite(), z.string().trim().min(1)]).nullable().optional();
|
|
20921
20962
|
const monthlySearchSchema$1 = z.object({
|
|
20922
20963
|
year: z.number().int(),
|
|
20923
20964
|
month: z.number().int(),
|
|
@@ -20933,7 +20974,7 @@ const keywordOverviewItemSchema = z.object({
|
|
|
20933
20974
|
last_updated_time: z.string().trim().min(1).nullable().optional()
|
|
20934
20975
|
}).nullish(),
|
|
20935
20976
|
keyword_properties: z.object({ keyword_difficulty: nullableNumber$1 }).nullish(),
|
|
20936
|
-
serp_info: z.object({ se_results_count: nullableCount$
|
|
20977
|
+
serp_info: z.object({ se_results_count: nullableCount$2 }).nullish(),
|
|
20937
20978
|
search_intent_info: z.object({ main_intent: z.enum([
|
|
20938
20979
|
"informational",
|
|
20939
20980
|
"navigational",
|
|
@@ -21006,37 +21047,37 @@ const dataForSeoDiscoveryResponseSchema = z.object({
|
|
|
21006
21047
|
//#region packages/core/dist/providers/dataforseo/domain-schema.js
|
|
21007
21048
|
const nullableNumber = z.number().finite().nullable().optional();
|
|
21008
21049
|
const nullableNonnegativeNumber = z.number().finite().nonnegative().nullable().optional();
|
|
21009
|
-
const nullableCount = z.number().int().nonnegative().nullable().optional();
|
|
21050
|
+
const nullableCount$1 = z.number().int().nonnegative().nullable().optional();
|
|
21010
21051
|
const rankingMetricsSchema = z.object({
|
|
21011
|
-
pos_1: nullableCount,
|
|
21012
|
-
pos_2_3: nullableCount,
|
|
21013
|
-
pos_4_10: nullableCount,
|
|
21014
|
-
pos_11_20: nullableCount,
|
|
21015
|
-
pos_21_30: nullableCount,
|
|
21016
|
-
pos_31_40: nullableCount,
|
|
21017
|
-
pos_41_50: nullableCount,
|
|
21018
|
-
pos_51_60: nullableCount,
|
|
21019
|
-
pos_61_70: nullableCount,
|
|
21020
|
-
pos_71_80: nullableCount,
|
|
21021
|
-
pos_81_90: nullableCount,
|
|
21022
|
-
pos_91_100: nullableCount,
|
|
21052
|
+
pos_1: nullableCount$1,
|
|
21053
|
+
pos_2_3: nullableCount$1,
|
|
21054
|
+
pos_4_10: nullableCount$1,
|
|
21055
|
+
pos_11_20: nullableCount$1,
|
|
21056
|
+
pos_21_30: nullableCount$1,
|
|
21057
|
+
pos_31_40: nullableCount$1,
|
|
21058
|
+
pos_41_50: nullableCount$1,
|
|
21059
|
+
pos_51_60: nullableCount$1,
|
|
21060
|
+
pos_61_70: nullableCount$1,
|
|
21061
|
+
pos_71_80: nullableCount$1,
|
|
21062
|
+
pos_81_90: nullableCount$1,
|
|
21063
|
+
pos_91_100: nullableCount$1,
|
|
21023
21064
|
etv: nullableNonnegativeNumber,
|
|
21024
|
-
count: nullableCount,
|
|
21065
|
+
count: nullableCount$1,
|
|
21025
21066
|
estimated_paid_traffic_cost: nullableNonnegativeNumber,
|
|
21026
|
-
is_new: nullableCount,
|
|
21027
|
-
is_up: nullableCount,
|
|
21028
|
-
is_down: nullableCount,
|
|
21029
|
-
is_lost: nullableCount
|
|
21067
|
+
is_new: nullableCount$1,
|
|
21068
|
+
is_up: nullableCount$1,
|
|
21069
|
+
is_down: nullableCount$1,
|
|
21070
|
+
is_lost: nullableCount$1
|
|
21030
21071
|
}).passthrough();
|
|
21031
21072
|
const metricsSchema = z.object({ organic: rankingMetricsSchema.nullable().optional() }).passthrough();
|
|
21032
|
-
const paidResponseSchema = z.object({
|
|
21073
|
+
const paidResponseSchema$1 = z.object({
|
|
21033
21074
|
status_code: z.number().int(),
|
|
21034
21075
|
status_message: z.string().max(1e3),
|
|
21035
21076
|
cost: z.number().finite().nonnegative().optional(),
|
|
21036
21077
|
tasks_count: z.number().int().nonnegative(),
|
|
21037
21078
|
tasks_error: z.number().int().nonnegative()
|
|
21038
21079
|
}).passthrough();
|
|
21039
|
-
const taskFields = {
|
|
21080
|
+
const taskFields$1 = {
|
|
21040
21081
|
id: z.string().trim().min(1).max(100).optional(),
|
|
21041
21082
|
status_code: z.number().int(),
|
|
21042
21083
|
status_message: z.string().max(1e3),
|
|
@@ -21056,8 +21097,8 @@ const overviewResultSchema = z.object({
|
|
|
21056
21097
|
items_count: z.number().int().nonnegative().optional(),
|
|
21057
21098
|
items: z.array(overviewItemSchema).max(1e3).nullable().optional()
|
|
21058
21099
|
}).passthrough();
|
|
21059
|
-
const dataForSeoDomainOverviewResponseSchema = paidResponseSchema.extend({ tasks: z.array(z.object({
|
|
21060
|
-
...taskFields,
|
|
21100
|
+
const dataForSeoDomainOverviewResponseSchema = paidResponseSchema$1.extend({ tasks: z.array(z.object({
|
|
21101
|
+
...taskFields$1,
|
|
21061
21102
|
result: z.array(overviewResultSchema).max(10).nullable().optional()
|
|
21062
21103
|
}).passthrough()).max(10) });
|
|
21063
21104
|
const rankedSerpItemSchema = z.object({
|
|
@@ -21077,8 +21118,8 @@ const rankedKeywordsResultSchema = z.object({
|
|
|
21077
21118
|
items_count: z.number().int().nonnegative().optional(),
|
|
21078
21119
|
items: z.array(rankedKeywordItemSchema).max(1e3).nullable().optional()
|
|
21079
21120
|
}).passthrough();
|
|
21080
|
-
const dataForSeoRankedKeywordsResponseSchema = paidResponseSchema.extend({ tasks: z.array(z.object({
|
|
21081
|
-
...taskFields,
|
|
21121
|
+
const dataForSeoRankedKeywordsResponseSchema = paidResponseSchema$1.extend({ tasks: z.array(z.object({
|
|
21122
|
+
...taskFields$1,
|
|
21082
21123
|
result: z.array(rankedKeywordsResultSchema).max(10).nullable().optional()
|
|
21083
21124
|
}).passthrough()).max(10) });
|
|
21084
21125
|
const rankingPageItemSchema = z.object({
|
|
@@ -21091,8 +21132,8 @@ const rankingPagesResultSchema = z.object({
|
|
|
21091
21132
|
items_count: z.number().int().nonnegative().optional(),
|
|
21092
21133
|
items: z.array(rankingPageItemSchema).max(1e3).nullable().optional()
|
|
21093
21134
|
}).passthrough();
|
|
21094
|
-
const dataForSeoRankingPagesResponseSchema = paidResponseSchema.extend({ tasks: z.array(z.object({
|
|
21095
|
-
...taskFields,
|
|
21135
|
+
const dataForSeoRankingPagesResponseSchema = paidResponseSchema$1.extend({ tasks: z.array(z.object({
|
|
21136
|
+
...taskFields$1,
|
|
21096
21137
|
result: z.array(rankingPagesResultSchema).max(10).nullable().optional()
|
|
21097
21138
|
}).passthrough()).max(10) });
|
|
21098
21139
|
const competitorItemSchema = z.object({
|
|
@@ -21100,9 +21141,9 @@ const competitorItemSchema = z.object({
|
|
|
21100
21141
|
avg_position: nullableNumber,
|
|
21101
21142
|
median_position: nullableNumber,
|
|
21102
21143
|
etv: nullableNonnegativeNumber,
|
|
21103
|
-
keywords_count: nullableCount,
|
|
21144
|
+
keywords_count: nullableCount$1,
|
|
21104
21145
|
visibility: nullableNonnegativeNumber,
|
|
21105
|
-
relevant_serp_items: nullableCount,
|
|
21146
|
+
relevant_serp_items: nullableCount$1,
|
|
21106
21147
|
keywords_positions: z.record(z.string().trim().min(1).max(80), z.array(z.number().int().positive()).max(100)).nullable().optional()
|
|
21107
21148
|
}).passthrough();
|
|
21108
21149
|
const competitorsResultSchema = z.object({
|
|
@@ -21111,8 +21152,8 @@ const competitorsResultSchema = z.object({
|
|
|
21111
21152
|
items_count: z.number().int().nonnegative().optional(),
|
|
21112
21153
|
items: z.array(competitorItemSchema).max(1e3).nullable().optional()
|
|
21113
21154
|
}).passthrough();
|
|
21114
|
-
const dataForSeoSerpCompetitorsResponseSchema = paidResponseSchema.extend({ tasks: z.array(z.object({
|
|
21115
|
-
...taskFields,
|
|
21155
|
+
const dataForSeoSerpCompetitorsResponseSchema = paidResponseSchema$1.extend({ tasks: z.array(z.object({
|
|
21156
|
+
...taskFields$1,
|
|
21116
21157
|
result: z.array(competitorsResultSchema).max(10).nullable().optional()
|
|
21117
21158
|
}).passthrough()).max(10) });
|
|
21118
21159
|
const DOMAIN_OVERVIEW_PATH = "v3/dataforseo_labs/google/domain_rank_overview/live";
|
|
@@ -21302,6 +21343,209 @@ function serpCompetitorsPaidRequest(input, ttlMs) {
|
|
|
21302
21343
|
};
|
|
21303
21344
|
}
|
|
21304
21345
|
//#endregion
|
|
21346
|
+
//#region packages/core/dist/providers/dataforseo/link-schema.js
|
|
21347
|
+
const nullableCount = z.number().int().nonnegative().nullable().optional();
|
|
21348
|
+
const nullableScore = z.number().finite().nonnegative().nullable().optional();
|
|
21349
|
+
const nullableText = z.string().max(2e4).nullable().optional();
|
|
21350
|
+
const paidResponseSchema = z.object({
|
|
21351
|
+
status_code: z.number().int(),
|
|
21352
|
+
status_message: z.string().max(1e3),
|
|
21353
|
+
cost: z.number().finite().nonnegative().optional(),
|
|
21354
|
+
tasks_count: z.number().int().nonnegative(),
|
|
21355
|
+
tasks_error: z.number().int().nonnegative()
|
|
21356
|
+
}).passthrough();
|
|
21357
|
+
const taskFields = {
|
|
21358
|
+
id: z.string().trim().min(1).max(100).optional(),
|
|
21359
|
+
status_code: z.number().int(),
|
|
21360
|
+
status_message: z.string().max(1e3),
|
|
21361
|
+
cost: z.number().finite().nonnegative().optional(),
|
|
21362
|
+
result_count: z.number().int().nonnegative().optional()
|
|
21363
|
+
};
|
|
21364
|
+
const dataForSeoLinkSummaryItemSchema = z.object({
|
|
21365
|
+
target: z.string().trim().min(1).max(2048).optional(),
|
|
21366
|
+
rank: nullableScore,
|
|
21367
|
+
backlinks: nullableCount,
|
|
21368
|
+
referring_pages: nullableCount,
|
|
21369
|
+
referring_domains: nullableCount,
|
|
21370
|
+
broken_backlinks: nullableCount,
|
|
21371
|
+
broken_pages: nullableCount,
|
|
21372
|
+
backlinks_spam_score: nullableScore,
|
|
21373
|
+
info: z.object({ target_spam_score: nullableScore }).passthrough().nullable().optional()
|
|
21374
|
+
}).passthrough();
|
|
21375
|
+
const dataForSeoLinkSummaryResponseSchema = paidResponseSchema.extend({ tasks: z.array(z.object({
|
|
21376
|
+
...taskFields,
|
|
21377
|
+
result: z.array(dataForSeoLinkSummaryItemSchema).max(10).nullable().optional()
|
|
21378
|
+
}).passthrough()).max(10) });
|
|
21379
|
+
const dataForSeoBacklinkItemSchema = z.object({
|
|
21380
|
+
domain_from: nullableText,
|
|
21381
|
+
url_from: nullableText,
|
|
21382
|
+
url_to: nullableText,
|
|
21383
|
+
anchor: nullableText,
|
|
21384
|
+
item_type: z.string().max(100).nullable().optional(),
|
|
21385
|
+
dofollow: z.boolean().nullable().optional(),
|
|
21386
|
+
rank: nullableScore,
|
|
21387
|
+
domain_from_rank: nullableScore,
|
|
21388
|
+
page_from_rank: nullableScore,
|
|
21389
|
+
backlink_spam_score: nullableScore,
|
|
21390
|
+
backlinks_spam_score: nullableScore,
|
|
21391
|
+
first_seen: z.string().max(100).nullable().optional(),
|
|
21392
|
+
last_visited: z.string().max(100).nullable().optional(),
|
|
21393
|
+
lost_date: z.string().max(100).nullable().optional(),
|
|
21394
|
+
is_lost: z.boolean().nullable().optional(),
|
|
21395
|
+
is_indirect_link: z.boolean().nullable().optional(),
|
|
21396
|
+
links_count: nullableCount,
|
|
21397
|
+
group_count: nullableCount,
|
|
21398
|
+
rel_attributes: z.array(z.string().trim().min(1).max(100)).max(100).nullable().optional(),
|
|
21399
|
+
attributes: z.array(z.string().trim().min(1).max(100)).max(100).nullable().optional()
|
|
21400
|
+
}).passthrough();
|
|
21401
|
+
const backlinkResultSchema = z.object({
|
|
21402
|
+
target: z.string().trim().min(1).max(2048).optional(),
|
|
21403
|
+
total_count: nullableCount,
|
|
21404
|
+
items_count: nullableCount,
|
|
21405
|
+
search_after_token: z.string().max(2e4).nullable().optional(),
|
|
21406
|
+
items: z.array(dataForSeoBacklinkItemSchema).max(1e3).nullable().optional()
|
|
21407
|
+
}).passthrough();
|
|
21408
|
+
const dataForSeoBacklinksResponseSchema = paidResponseSchema.extend({ tasks: z.array(z.object({
|
|
21409
|
+
...taskFields,
|
|
21410
|
+
result: z.array(backlinkResultSchema).max(10).nullable().optional()
|
|
21411
|
+
}).passthrough()).max(10) });
|
|
21412
|
+
const dataForSeoReferringDomainItemSchema = z.object({
|
|
21413
|
+
domain: z.string().trim().min(1).max(2048).nullable().optional(),
|
|
21414
|
+
backlinks: nullableCount,
|
|
21415
|
+
referring_pages: nullableCount,
|
|
21416
|
+
broken_backlinks: nullableCount,
|
|
21417
|
+
broken_pages: nullableCount,
|
|
21418
|
+
rank: nullableScore,
|
|
21419
|
+
backlinks_spam_score: nullableScore,
|
|
21420
|
+
first_seen: z.string().max(100).nullable().optional()
|
|
21421
|
+
}).passthrough();
|
|
21422
|
+
const referringDomainsResultSchema = z.object({
|
|
21423
|
+
target: z.string().trim().min(1).max(2048).optional(),
|
|
21424
|
+
total_count: nullableCount,
|
|
21425
|
+
items_count: nullableCount,
|
|
21426
|
+
items: z.array(dataForSeoReferringDomainItemSchema).max(1e3).nullable().optional()
|
|
21427
|
+
}).passthrough();
|
|
21428
|
+
const dataForSeoReferringDomainsResponseSchema = paidResponseSchema.extend({ tasks: z.array(z.object({
|
|
21429
|
+
...taskFields,
|
|
21430
|
+
result: z.array(referringDomainsResultSchema).max(10).nullable().optional()
|
|
21431
|
+
}).passthrough()).max(10) });
|
|
21432
|
+
const MAX_LINK_ROWS$1 = 1e3;
|
|
21433
|
+
const MAX_LINK_OFFSET = 2e4;
|
|
21434
|
+
const LINK_ENDPOINTS = {
|
|
21435
|
+
summary: "v3/backlinks/summary/live",
|
|
21436
|
+
backlinks: "v3/backlinks/backlinks/live",
|
|
21437
|
+
referringDomains: "v3/backlinks/referring_domains/live"
|
|
21438
|
+
};
|
|
21439
|
+
function resultRows(response) {
|
|
21440
|
+
return response.tasks.reduce((taskTotal, task) => taskTotal + (task.result ?? []).reduce((resultTotal, result) => resultTotal + (result.items?.length ?? 0), 0), 0);
|
|
21441
|
+
}
|
|
21442
|
+
function summaryRows(response) {
|
|
21443
|
+
return response.tasks.reduce((total, task) => total + (task.result?.length ?? 0), 0);
|
|
21444
|
+
}
|
|
21445
|
+
function commonRequest(input) {
|
|
21446
|
+
return {
|
|
21447
|
+
target: input.target,
|
|
21448
|
+
include_subdomains: input.scope === "domain" ? input.includeSubdomains : false,
|
|
21449
|
+
include_indirect_links: true,
|
|
21450
|
+
exclude_internal_backlinks: true,
|
|
21451
|
+
backlinks_status_type: "live",
|
|
21452
|
+
rank_scale: "one_hundred"
|
|
21453
|
+
};
|
|
21454
|
+
}
|
|
21455
|
+
function validatePage(input) {
|
|
21456
|
+
if (!Number.isSafeInteger(input.limit) || input.limit < 1 || input.limit > 1e3) throw new ProviderError({
|
|
21457
|
+
provider: "dataforseo",
|
|
21458
|
+
operation: input.operation,
|
|
21459
|
+
code: "configuration",
|
|
21460
|
+
message: `Link row limit must be from 1 to ${MAX_LINK_ROWS$1}.`
|
|
21461
|
+
});
|
|
21462
|
+
if (!Number.isSafeInteger(input.offset) || input.offset < 0 || input.offset > 2e4) throw new ProviderError({
|
|
21463
|
+
provider: "dataforseo",
|
|
21464
|
+
operation: input.operation,
|
|
21465
|
+
code: "configuration",
|
|
21466
|
+
message: `Link row offset must be from 0 to ${MAX_LINK_OFFSET}.`
|
|
21467
|
+
});
|
|
21468
|
+
}
|
|
21469
|
+
function linkSummaryPaidRequest(input, ttlMs) {
|
|
21470
|
+
return {
|
|
21471
|
+
operation: "link-summary",
|
|
21472
|
+
capability: "link-summary",
|
|
21473
|
+
endpoint: LINK_ENDPOINTS.summary,
|
|
21474
|
+
request: commonRequest(input),
|
|
21475
|
+
schema: dataForSeoLinkSummaryResponseSchema,
|
|
21476
|
+
requestedRows: 1,
|
|
21477
|
+
price: (account) => account.linkPrices.summary,
|
|
21478
|
+
context: input.context,
|
|
21479
|
+
ttlMs,
|
|
21480
|
+
refresh: input.refresh,
|
|
21481
|
+
rowCount: summaryRows
|
|
21482
|
+
};
|
|
21483
|
+
}
|
|
21484
|
+
function backlinksPaidRequest(input, ttlMs) {
|
|
21485
|
+
validatePage({
|
|
21486
|
+
limit: input.limit,
|
|
21487
|
+
offset: input.offset,
|
|
21488
|
+
operation: "backlinks"
|
|
21489
|
+
});
|
|
21490
|
+
if (input.orderBy.length < 1 || input.orderBy.length > 3) throw new ProviderError({
|
|
21491
|
+
provider: "dataforseo",
|
|
21492
|
+
operation: "backlinks",
|
|
21493
|
+
code: "configuration",
|
|
21494
|
+
message: "Backlink rows need from 1 to 3 sort rules."
|
|
21495
|
+
});
|
|
21496
|
+
return {
|
|
21497
|
+
operation: "backlinks",
|
|
21498
|
+
capability: "backlinks",
|
|
21499
|
+
endpoint: LINK_ENDPOINTS.backlinks,
|
|
21500
|
+
request: {
|
|
21501
|
+
...commonRequest(input),
|
|
21502
|
+
mode: input.mode,
|
|
21503
|
+
backlinks_status_type: input.status,
|
|
21504
|
+
limit: input.limit,
|
|
21505
|
+
offset: input.offset,
|
|
21506
|
+
order_by: input.orderBy
|
|
21507
|
+
},
|
|
21508
|
+
schema: dataForSeoBacklinksResponseSchema,
|
|
21509
|
+
requestedRows: input.limit,
|
|
21510
|
+
price: (account) => account.linkPrices.backlinks,
|
|
21511
|
+
context: input.context,
|
|
21512
|
+
ttlMs,
|
|
21513
|
+
refresh: input.refresh,
|
|
21514
|
+
rowCount: resultRows
|
|
21515
|
+
};
|
|
21516
|
+
}
|
|
21517
|
+
function referringDomainsPaidRequest(input, ttlMs) {
|
|
21518
|
+
validatePage({
|
|
21519
|
+
limit: input.limit,
|
|
21520
|
+
offset: input.offset,
|
|
21521
|
+
operation: "referring-domains"
|
|
21522
|
+
});
|
|
21523
|
+
if (input.orderBy.length < 1 || input.orderBy.length > 3) throw new ProviderError({
|
|
21524
|
+
provider: "dataforseo",
|
|
21525
|
+
operation: "referring-domains",
|
|
21526
|
+
code: "configuration",
|
|
21527
|
+
message: "Referring-domain rows need from 1 to 3 sort rules."
|
|
21528
|
+
});
|
|
21529
|
+
return {
|
|
21530
|
+
operation: "referring-domains",
|
|
21531
|
+
capability: "referring-domains",
|
|
21532
|
+
endpoint: LINK_ENDPOINTS.referringDomains,
|
|
21533
|
+
request: {
|
|
21534
|
+
...commonRequest(input),
|
|
21535
|
+
limit: input.limit,
|
|
21536
|
+
offset: input.offset,
|
|
21537
|
+
order_by: input.orderBy
|
|
21538
|
+
},
|
|
21539
|
+
schema: dataForSeoReferringDomainsResponseSchema,
|
|
21540
|
+
requestedRows: input.limit,
|
|
21541
|
+
price: (account) => account.linkPrices.referringDomains,
|
|
21542
|
+
context: input.context,
|
|
21543
|
+
ttlMs,
|
|
21544
|
+
refresh: input.refresh,
|
|
21545
|
+
rowCount: resultRows
|
|
21546
|
+
};
|
|
21547
|
+
}
|
|
21548
|
+
//#endregion
|
|
21305
21549
|
//#region packages/core/dist/providers/dataforseo/paid-request.js
|
|
21306
21550
|
function usdToMicros(value) {
|
|
21307
21551
|
if (value === void 0) return null;
|
|
@@ -21825,41 +22069,6 @@ const MAX_KEYWORD_CHARACTERS = 80;
|
|
|
21825
22069
|
const MAX_KEYWORD_WORDS = 10;
|
|
21826
22070
|
const MAX_DISCOVERY_SEEDS$1 = 5;
|
|
21827
22071
|
const MAX_DISCOVERY_ROWS$1 = 100;
|
|
21828
|
-
function unitPrice(components) {
|
|
21829
|
-
const prices = components ?? [];
|
|
21830
|
-
const total = (type) => {
|
|
21831
|
-
const matching = prices.filter((item) => item.cost_type === type);
|
|
21832
|
-
return matching.length ? matching.reduce((sum, item) => sum + Math.round(item.cost * 1e6), 0) : prices.length ? 0 : null;
|
|
21833
|
-
};
|
|
21834
|
-
return {
|
|
21835
|
-
perRequestMicros: total("per_request"),
|
|
21836
|
-
perResultMicros: total("per_result")
|
|
21837
|
-
};
|
|
21838
|
-
}
|
|
21839
|
-
function keywordOverviewPrice(account) {
|
|
21840
|
-
return unitPrice(account.price?.dataforseo_labs?.keyword_overview?.live?.priority_normal);
|
|
21841
|
-
}
|
|
21842
|
-
function keywordDiscoveryPrices(account) {
|
|
21843
|
-
return {
|
|
21844
|
-
ideas: unitPrice(account.price?.dataforseo_labs?.keyword_ideas?.live?.priority_normal),
|
|
21845
|
-
related: unitPrice(account.price?.dataforseo_labs?.related_keywords?.live?.priority_normal),
|
|
21846
|
-
suggestions: unitPrice(account.price?.dataforseo_labs?.keyword_suggestions?.live?.priority_normal)
|
|
21847
|
-
};
|
|
21848
|
-
}
|
|
21849
|
-
function domainResearchPrices(account) {
|
|
21850
|
-
return {
|
|
21851
|
-
domainOverview: unitPrice(account.price?.dataforseo_labs?.domain_rank_overview?.live?.priority_normal),
|
|
21852
|
-
rankedKeywords: unitPrice(account.price?.dataforseo_labs?.ranked_keywords?.live?.priority_normal),
|
|
21853
|
-
rankingPages: unitPrice(account.price?.dataforseo_labs?.relevant_pages?.live?.priority_normal),
|
|
21854
|
-
serpCompetitors: unitPrice(account.price?.dataforseo_labs?.serp_competitors?.live?.priority_normal)
|
|
21855
|
-
};
|
|
21856
|
-
}
|
|
21857
|
-
function serpLiveAdvancedPrice(account) {
|
|
21858
|
-
return unitPrice(account.price?.serp?.live?.advanced?.priority_normal);
|
|
21859
|
-
}
|
|
21860
|
-
function serpTaskPostPrice(account) {
|
|
21861
|
-
return unitPrice(account.price?.serp?.task_post?.priority_normal);
|
|
21862
|
-
}
|
|
21863
22072
|
function responseTaskIds(response) {
|
|
21864
22073
|
return [...new Set(response.tasks.flatMap((task) => task.id ? [task.id] : []))].sort((left, right) => left < right ? -1 : left > right ? 1 : 0);
|
|
21865
22074
|
}
|
|
@@ -21879,6 +22088,7 @@ var DataForSeoClient = class {
|
|
|
21879
22088
|
serpTtlMs;
|
|
21880
22089
|
accountPricingTtlMs;
|
|
21881
22090
|
domainResearchTtlMs;
|
|
22091
|
+
linkTtlMs;
|
|
21882
22092
|
spendLimits;
|
|
21883
22093
|
accountPricing;
|
|
21884
22094
|
accountPricingRequest;
|
|
@@ -21895,6 +22105,7 @@ var DataForSeoClient = class {
|
|
|
21895
22105
|
this.serpTtlMs = options.serpTtlMs ?? DEFAULT_SERP_TTL_MS;
|
|
21896
22106
|
this.accountPricingTtlMs = options.accountPricingTtlMs ?? DEFAULT_ACCOUNT_PRICING_TTL_MS;
|
|
21897
22107
|
this.domainResearchTtlMs = options.domainResearchTtlMs ?? 6048e5;
|
|
22108
|
+
this.linkTtlMs = options.linkTtlMs ?? 864e5;
|
|
21898
22109
|
this.spendLimits = options.spendLimits;
|
|
21899
22110
|
}
|
|
21900
22111
|
async getCredentials(operation) {
|
|
@@ -21977,11 +22188,7 @@ var DataForSeoClient = class {
|
|
|
21977
22188
|
accountDailySpendMicros: dataForSeoUsdToMicros(account.money?.statistics?.day?.total),
|
|
21978
22189
|
accountDailySpendPeriod: account.money?.statistics?.day?.value ?? null,
|
|
21979
22190
|
accountDailyLimitMicros: dataForSeoUsdToMicros(account.money?.limits?.day?.total),
|
|
21980
|
-
|
|
21981
|
-
keywordDiscoveryPrices: keywordDiscoveryPrices(account),
|
|
21982
|
-
domainResearchPrices: domainResearchPrices(account),
|
|
21983
|
-
serpLiveAdvancedPrice: serpLiveAdvancedPrice(account),
|
|
21984
|
-
serpTaskPostPrice: serpTaskPostPrice(account),
|
|
22191
|
+
...dataForSeoAccountPrices(account),
|
|
21985
22192
|
backlinksSubscriptionExpiresAt: account.backlinks_subscription_expiry_date ?? null,
|
|
21986
22193
|
aiMentionsSubscriptionExpiresAt: account.llm_mentions_subscription_expiry_date ?? null,
|
|
21987
22194
|
apiVersion: response.version ?? null,
|
|
@@ -22044,6 +22251,15 @@ var DataForSeoClient = class {
|
|
|
22044
22251
|
async serpCompetitors(input) {
|
|
22045
22252
|
return this.paidPost(serpCompetitorsPaidRequest(input, this.domainResearchTtlMs));
|
|
22046
22253
|
}
|
|
22254
|
+
async linkSummary(input) {
|
|
22255
|
+
return this.paidPost(linkSummaryPaidRequest(input, this.linkTtlMs));
|
|
22256
|
+
}
|
|
22257
|
+
async backlinks(input) {
|
|
22258
|
+
return this.paidPost(backlinksPaidRequest(input, this.linkTtlMs));
|
|
22259
|
+
}
|
|
22260
|
+
async referringDomains(input) {
|
|
22261
|
+
return this.paidPost(referringDomainsPaidRequest(input, this.linkTtlMs));
|
|
22262
|
+
}
|
|
22047
22263
|
async keywordOverview(input) {
|
|
22048
22264
|
const keywords = input.keywords.map((keyword) => keyword.trim());
|
|
22049
22265
|
if (keywords.length < 1 || keywords.length > MAX_KEYWORDS_PER_OVERVIEW_REQUEST || keywords.some((keyword) => keyword.length < 1 || keyword.length > MAX_KEYWORD_CHARACTERS || keyword.split(/\s+/u).length > MAX_KEYWORD_WORDS)) throw new ProviderError({
|
|
@@ -22387,20 +22603,20 @@ function compareCodepoints$3(left, right) {
|
|
|
22387
22603
|
function normalizedKeyword$2(keyword) {
|
|
22388
22604
|
return keyword.trim().replace(/\s+/gu, " ").toLowerCase();
|
|
22389
22605
|
}
|
|
22390
|
-
function missing$
|
|
22606
|
+
function missing$2(field) {
|
|
22391
22607
|
return unavailableValue("missing", `DataForSEO omitted ${field}.`);
|
|
22392
22608
|
}
|
|
22393
22609
|
function numericValue(values, field, valid) {
|
|
22394
22610
|
const present = values.filter((value) => value !== null && value !== void 0);
|
|
22395
|
-
if (present.length === 0) return missing$
|
|
22611
|
+
if (present.length === 0) return missing$2(field);
|
|
22396
22612
|
const numbers = present.filter((value) => typeof value === "number" && valid(value));
|
|
22397
22613
|
if (numbers.length !== present.length) return unavailableValue("invalid", `DataForSEO returned an invalid ${field}.`);
|
|
22398
22614
|
const unique = [...new Set(numbers)];
|
|
22399
22615
|
return unique.length === 1 ? observedValue(unique[0]) : unavailableValue("invalid", `DataForSEO returned conflicting ${field} values.`);
|
|
22400
22616
|
}
|
|
22401
|
-
function stringValue(values, field, normalize) {
|
|
22617
|
+
function stringValue$1(values, field, normalize) {
|
|
22402
22618
|
const present = values.filter((value) => value !== null && value !== void 0);
|
|
22403
|
-
if (present.length === 0) return missing$
|
|
22619
|
+
if (present.length === 0) return missing$2(field);
|
|
22404
22620
|
const strings = present.flatMap((value) => {
|
|
22405
22621
|
if (typeof value !== "string") return [];
|
|
22406
22622
|
const normalized = normalize(value);
|
|
@@ -22412,7 +22628,7 @@ function stringValue(values, field, normalize) {
|
|
|
22412
22628
|
}
|
|
22413
22629
|
function monthlySearchesValue(rows) {
|
|
22414
22630
|
const histories = rows.map((row) => row.keyword_info?.monthly_searches).filter((value) => value !== null && value !== void 0);
|
|
22415
|
-
if (histories.length === 0) return missing$
|
|
22631
|
+
if (histories.length === 0) return missing$2("monthlySearches");
|
|
22416
22632
|
const byMonth = /* @__PURE__ */ new Map();
|
|
22417
22633
|
let invalid = false;
|
|
22418
22634
|
for (const history of histories) for (const item of history) {
|
|
@@ -22445,27 +22661,27 @@ function resultCountValues(rows) {
|
|
|
22445
22661
|
function metricForKeyword(keyword, rows) {
|
|
22446
22662
|
if (rows.length === 0) return {
|
|
22447
22663
|
keyword,
|
|
22448
|
-
monthlySearchVolume: missing$
|
|
22449
|
-
monthlySearches: missing$
|
|
22450
|
-
searchVolumeUpdatedAt: missing$
|
|
22451
|
-
cpcUsd: missing$
|
|
22452
|
-
paidCompetition: missing$
|
|
22453
|
-
keywordDifficulty: missing$
|
|
22454
|
-
intent: missing$
|
|
22455
|
-
resultCount: missing$
|
|
22664
|
+
monthlySearchVolume: missing$2("monthlySearchVolume"),
|
|
22665
|
+
monthlySearches: missing$2("monthlySearches"),
|
|
22666
|
+
searchVolumeUpdatedAt: missing$2("searchVolumeUpdatedAt"),
|
|
22667
|
+
cpcUsd: missing$2("cpcUsd"),
|
|
22668
|
+
paidCompetition: missing$2("paidCompetition"),
|
|
22669
|
+
keywordDifficulty: missing$2("keywordDifficulty"),
|
|
22670
|
+
intent: missing$2("intent"),
|
|
22671
|
+
resultCount: missing$2("resultCount")
|
|
22456
22672
|
};
|
|
22457
22673
|
return {
|
|
22458
22674
|
keyword,
|
|
22459
22675
|
monthlySearchVolume: numericValue(rows.map((row) => row.keyword_info?.search_volume), "monthlySearchVolume", (value) => Number.isSafeInteger(value) && value >= 0),
|
|
22460
22676
|
monthlySearches: monthlySearchesValue(rows),
|
|
22461
|
-
searchVolumeUpdatedAt: stringValue(rows.map((row) => row.keyword_info?.last_updated_time), "searchVolumeUpdatedAt", (value) => {
|
|
22677
|
+
searchVolumeUpdatedAt: stringValue$1(rows.map((row) => row.keyword_info?.last_updated_time), "searchVolumeUpdatedAt", (value) => {
|
|
22462
22678
|
const timestamp = Date.parse(value);
|
|
22463
22679
|
return Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : null;
|
|
22464
22680
|
}),
|
|
22465
22681
|
cpcUsd: numericValue(rows.map((row) => row.keyword_info?.cpc), "cpcUsd", (value) => Number.isFinite(value) && value >= 0),
|
|
22466
22682
|
paidCompetition: numericValue(rows.map((row) => row.keyword_info?.competition), "paidCompetition", (value) => Number.isFinite(value) && value >= 0 && value <= 1),
|
|
22467
22683
|
keywordDifficulty: numericValue(rows.map((row) => row.keyword_properties?.keyword_difficulty), "keywordDifficulty", (value) => Number.isFinite(value) && value >= 0 && value <= 100),
|
|
22468
|
-
intent: stringValue(rows.map((row) => row.search_intent_info?.main_intent), "intent", (value) => value.trim().toLowerCase() || null),
|
|
22684
|
+
intent: stringValue$1(rows.map((row) => row.search_intent_info?.main_intent), "intent", (value) => value.trim().toLowerCase() || null),
|
|
22469
22685
|
resultCount: numericValue(resultCountValues(rows), "resultCount", (value) => Number.isSafeInteger(value) && value >= 0)
|
|
22470
22686
|
};
|
|
22471
22687
|
}
|
|
@@ -22518,21 +22734,21 @@ const COMPETITOR_RESULT_TYPES = /* @__PURE__ */ new Set([
|
|
|
22518
22734
|
"featured_snippet",
|
|
22519
22735
|
"local_pack"
|
|
22520
22736
|
]);
|
|
22521
|
-
function missing(field) {
|
|
22737
|
+
function missing$1(field) {
|
|
22522
22738
|
return unavailableValue("missing", `DataForSEO omitted ${field}.`);
|
|
22523
22739
|
}
|
|
22524
|
-
function numberValue(value, field) {
|
|
22525
|
-
return value === null || value === void 0 ? missing(field) : observedValue(value);
|
|
22740
|
+
function numberValue$1(value, field) {
|
|
22741
|
+
return value === null || value === void 0 ? missing$1(field) : observedValue(value);
|
|
22526
22742
|
}
|
|
22527
22743
|
function countValue(value, field) {
|
|
22528
|
-
return numberValue(value, field);
|
|
22744
|
+
return numberValue$1(value, field);
|
|
22529
22745
|
}
|
|
22530
22746
|
function sumBuckets(metrics, fields, label) {
|
|
22531
22747
|
const values = fields.map((field) => metrics[field]);
|
|
22532
|
-
return values.some((value) => value === null || value === void 0) ? missing(label) : observedValue(values.reduce((sum, value) => sum + (typeof value === "number" ? value : 0), 0));
|
|
22748
|
+
return values.some((value) => value === null || value === void 0) ? missing$1(label) : observedValue(values.reduce((sum, value) => sum + (typeof value === "number" ? value : 0), 0));
|
|
22533
22749
|
}
|
|
22534
22750
|
function rankingDistribution(metrics) {
|
|
22535
|
-
if (!metrics) return missing("organic ranking distribution");
|
|
22751
|
+
if (!metrics) return missing$1("organic ranking distribution");
|
|
22536
22752
|
const buckets = {
|
|
22537
22753
|
first: sumBuckets(metrics, ["pos_1"], "position 1 rankings"),
|
|
22538
22754
|
top3: sumBuckets(metrics, ["pos_1", "pos_2_3"], "top 3 rankings"),
|
|
@@ -22583,9 +22799,9 @@ function rankingDistribution(metrics) {
|
|
|
22583
22799
|
}
|
|
22584
22800
|
function organicFootprint(metrics) {
|
|
22585
22801
|
return {
|
|
22586
|
-
estimatedMonthlyTraffic: numberValue(metrics?.etv, "estimated organic monthly traffic"),
|
|
22802
|
+
estimatedMonthlyTraffic: numberValue$1(metrics?.etv, "estimated organic monthly traffic"),
|
|
22587
22803
|
rankedKeywords: countValue(metrics?.count, "ranked organic keywords"),
|
|
22588
|
-
estimatedMonthlyTrafficCostUsd: numberValue(metrics?.estimated_paid_traffic_cost, "estimated organic traffic cost"),
|
|
22804
|
+
estimatedMonthlyTrafficCostUsd: numberValue$1(metrics?.estimated_paid_traffic_cost, "estimated organic traffic cost"),
|
|
22589
22805
|
rankings: rankingDistribution(metrics),
|
|
22590
22806
|
newRankings: countValue(metrics?.is_new, "new rankings"),
|
|
22591
22807
|
improvedRankings: countValue(metrics?.is_up, "improved rankings"),
|
|
@@ -22640,7 +22856,7 @@ function researchMarket(input, operation) {
|
|
|
22640
22856
|
});
|
|
22641
22857
|
return parsed;
|
|
22642
22858
|
}
|
|
22643
|
-
function rowLimit(limit, offset = 0) {
|
|
22859
|
+
function rowLimit$1(limit, offset = 0) {
|
|
22644
22860
|
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1e3) throw new ProviderError({
|
|
22645
22861
|
provider: "dataforseo",
|
|
22646
22862
|
operation: "domain-research",
|
|
@@ -22664,7 +22880,7 @@ function resultTypes(input, allowed) {
|
|
|
22664
22880
|
});
|
|
22665
22881
|
return normalized;
|
|
22666
22882
|
}
|
|
22667
|
-
function safeUrl$
|
|
22883
|
+
function safeUrl$2(value) {
|
|
22668
22884
|
if (!value) return null;
|
|
22669
22885
|
try {
|
|
22670
22886
|
const url = new URL(value);
|
|
@@ -22684,7 +22900,7 @@ function dedupeBy(rows, key) {
|
|
|
22684
22900
|
}
|
|
22685
22901
|
return [...grouped.entries()].sort(([left], [right]) => compareCodepoints$3(left, right)).map(([, matches]) => [...matches].sort((left, right) => compareCodepoints$3(JSON.stringify(left), JSON.stringify(right)))[0]);
|
|
22686
22902
|
}
|
|
22687
|
-
function observedNumber(value) {
|
|
22903
|
+
function observedNumber$1(value) {
|
|
22688
22904
|
return value.state === "observed" ? value.value : -1;
|
|
22689
22905
|
}
|
|
22690
22906
|
function duplicateWarning(available, retained, label) {
|
|
@@ -22695,7 +22911,7 @@ function duplicateWarning(available, retained, label) {
|
|
|
22695
22911
|
message: `${duplicates} duplicate ${label} row${duplicates === 1 ? "" : "s"} were collapsed deterministically.`
|
|
22696
22912
|
}] : [];
|
|
22697
22913
|
}
|
|
22698
|
-
function totalRows(values) {
|
|
22914
|
+
function totalRows$1(values) {
|
|
22699
22915
|
const present = values.filter((value) => value !== null && value !== void 0);
|
|
22700
22916
|
return present.length ? Math.max(...present) : null;
|
|
22701
22917
|
}
|
|
@@ -22711,7 +22927,7 @@ function coverage(input) {
|
|
|
22711
22927
|
nextCursor: hasMore ? String(input.offset + input.returnedRows) : null
|
|
22712
22928
|
};
|
|
22713
22929
|
}
|
|
22714
|
-
function requestContext(reportId, supplied) {
|
|
22930
|
+
function requestContext$1(reportId, supplied) {
|
|
22715
22931
|
return supplied ?? {
|
|
22716
22932
|
reportId,
|
|
22717
22933
|
reportRunId: randomUUID()
|
|
@@ -22760,7 +22976,7 @@ async function dataForSeoDomainOverview(client, input) {
|
|
|
22760
22976
|
languageCode: market.languageCode.split("-")[0],
|
|
22761
22977
|
...location,
|
|
22762
22978
|
refresh: input.refresh,
|
|
22763
|
-
context: requestContext("domain-overview", input.context)
|
|
22979
|
+
context: requestContext$1("domain-overview", input.context)
|
|
22764
22980
|
});
|
|
22765
22981
|
const results = snapshot.response.tasks.flatMap((task) => task.result ?? []);
|
|
22766
22982
|
const matchingResult = results.find((row) => !row.target || domain(row.target) === target);
|
|
@@ -22852,7 +23068,7 @@ async function dataForSeoRankedKeywords(client, input) {
|
|
|
22852
23068
|
const market = researchMarket(input.market, "ranked-keywords");
|
|
22853
23069
|
const target = rankedTarget(input.target);
|
|
22854
23070
|
const offset = input.offset ?? 0;
|
|
22855
|
-
rowLimit(input.limit, offset);
|
|
23071
|
+
rowLimit$1(input.limit, offset);
|
|
22856
23072
|
const types = resultTypes(input.resultTypes, RANKED_RESULT_TYPES);
|
|
22857
23073
|
const excluded = excludedTerms(input);
|
|
22858
23074
|
const requestedFilters = filters$1(input, excluded);
|
|
@@ -22872,7 +23088,7 @@ async function dataForSeoRankedKeywords(client, input) {
|
|
|
22872
23088
|
limit: input.limit,
|
|
22873
23089
|
offset,
|
|
22874
23090
|
refresh: input.refresh,
|
|
22875
|
-
context: requestContext("ranked-keywords", input.context)
|
|
23091
|
+
context: requestContext$1("ranked-keywords", input.context)
|
|
22876
23092
|
});
|
|
22877
23093
|
const responseResults = snapshot.response.tasks.flatMap((task) => task.result ?? []);
|
|
22878
23094
|
const rawRows = responseResults.flatMap((result) => result.items ?? []);
|
|
@@ -22881,7 +23097,7 @@ async function dataForSeoRankedKeywords(client, input) {
|
|
|
22881
23097
|
const keywordData = row.keyword_data;
|
|
22882
23098
|
const serp = row.ranked_serp_element?.serp_item;
|
|
22883
23099
|
const keyword = keywordData?.keyword ? normalizedKeyword$2(keywordData.keyword) : "";
|
|
22884
|
-
const url = safeUrl$
|
|
23100
|
+
const url = safeUrl$2(serp?.url);
|
|
22885
23101
|
if (!keywordData || !keyword || !url || !serp?.rank_group || !serp.rank_absolute || !serp.type) {
|
|
22886
23102
|
invalidRows += 1;
|
|
22887
23103
|
return [];
|
|
@@ -22892,11 +23108,11 @@ async function dataForSeoRankedKeywords(client, input) {
|
|
|
22892
23108
|
rankGroup: serp.rank_group,
|
|
22893
23109
|
rankAbsolute: serp.rank_absolute,
|
|
22894
23110
|
resultType: serp.type,
|
|
22895
|
-
estimatedMonthlyTraffic: numberValue(serp.etv, "estimated keyword traffic")
|
|
23111
|
+
estimatedMonthlyTraffic: numberValue$1(serp.etv, "estimated keyword traffic")
|
|
22896
23112
|
}];
|
|
22897
23113
|
});
|
|
22898
|
-
const rows = dedupeBy(mappedRows, (row) => `${row.keyword}\0${row.url}\0${row.resultType}`).sort((left, right) => observedNumber(right.monthlySearchVolume) - observedNumber(left.monthlySearchVolume) || left.rankGroup - right.rankGroup || compareCodepoints$3(left.keyword, right.keyword) || compareCodepoints$3(left.url, right.url));
|
|
22899
|
-
const providerTotalRows = totalRows(responseResults.map((result) => result.total_count));
|
|
23114
|
+
const rows = dedupeBy(mappedRows, (row) => `${row.keyword}\0${row.url}\0${row.resultType}`).sort((left, right) => observedNumber$1(right.monthlySearchVolume) - observedNumber$1(left.monthlySearchVolume) || left.rankGroup - right.rankGroup || compareCodepoints$3(left.keyword, right.keyword) || compareCodepoints$3(left.url, right.url));
|
|
23115
|
+
const providerTotalRows = totalRows$1(responseResults.map((result) => result.total_count));
|
|
22900
23116
|
return domainEvidence({
|
|
22901
23117
|
capability: "ranked-keywords",
|
|
22902
23118
|
data: {
|
|
@@ -22969,7 +23185,7 @@ async function dataForSeoRankingPages(client, input) {
|
|
|
22969
23185
|
const market = researchMarket(input.market, "ranking-pages");
|
|
22970
23186
|
const target = domain(input.domain);
|
|
22971
23187
|
const offset = input.offset ?? 0;
|
|
22972
|
-
rowLimit(input.limit, offset);
|
|
23188
|
+
rowLimit$1(input.limit, offset);
|
|
22973
23189
|
const requestedFilters = filters(input);
|
|
22974
23190
|
const orderBy = ["metrics.organic.etv,desc", "page_address,asc"];
|
|
22975
23191
|
const snapshot = await client.rankingPages({
|
|
@@ -22981,13 +23197,13 @@ async function dataForSeoRankingPages(client, input) {
|
|
|
22981
23197
|
limit: input.limit,
|
|
22982
23198
|
offset,
|
|
22983
23199
|
refresh: input.refresh,
|
|
22984
|
-
context: requestContext("ranking-pages", input.context)
|
|
23200
|
+
context: requestContext$1("ranking-pages", input.context)
|
|
22985
23201
|
});
|
|
22986
23202
|
const responseResults = snapshot.response.tasks.flatMap((task) => task.result ?? []);
|
|
22987
23203
|
const rawRows = responseResults.flatMap((result) => result.items ?? []);
|
|
22988
23204
|
let invalidRows = 0;
|
|
22989
23205
|
const mappedRows = rawRows.flatMap((row) => {
|
|
22990
|
-
const url = safeUrl$
|
|
23206
|
+
const url = safeUrl$2(row.page_address);
|
|
22991
23207
|
if (!url) {
|
|
22992
23208
|
invalidRows += 1;
|
|
22993
23209
|
return [];
|
|
@@ -22997,8 +23213,8 @@ async function dataForSeoRankingPages(client, input) {
|
|
|
22997
23213
|
organic: organicFootprint(row.metrics?.organic)
|
|
22998
23214
|
}];
|
|
22999
23215
|
});
|
|
23000
|
-
const rows = dedupeBy(mappedRows, (row) => row.url).sort((left, right) => observedNumber(right.organic.estimatedMonthlyTraffic) - observedNumber(left.organic.estimatedMonthlyTraffic) || compareCodepoints$3(left.url, right.url));
|
|
23001
|
-
const providerTotalRows = totalRows(responseResults.map((result) => result.total_count));
|
|
23216
|
+
const rows = dedupeBy(mappedRows, (row) => row.url).sort((left, right) => observedNumber$1(right.organic.estimatedMonthlyTraffic) - observedNumber$1(left.organic.estimatedMonthlyTraffic) || compareCodepoints$3(left.url, right.url));
|
|
23217
|
+
const providerTotalRows = totalRows$1(responseResults.map((result) => result.total_count));
|
|
23002
23218
|
return domainEvidence({
|
|
23003
23219
|
capability: "relevant-pages",
|
|
23004
23220
|
data: {
|
|
@@ -23043,7 +23259,7 @@ function keywords(input) {
|
|
|
23043
23259
|
async function dataForSeoSerpCompetitors(client, input) {
|
|
23044
23260
|
const market = researchMarket(input.market, "serp-competitors");
|
|
23045
23261
|
const offset = input.offset ?? 0;
|
|
23046
|
-
rowLimit(input.limit, offset);
|
|
23262
|
+
rowLimit$1(input.limit, offset);
|
|
23047
23263
|
const requestedKeywords = keywords(input.keywords);
|
|
23048
23264
|
const types = resultTypes(input.resultTypes, COMPETITOR_RESULT_TYPES);
|
|
23049
23265
|
const orderBy = [
|
|
@@ -23061,7 +23277,7 @@ async function dataForSeoSerpCompetitors(client, input) {
|
|
|
23061
23277
|
limit: input.limit,
|
|
23062
23278
|
offset,
|
|
23063
23279
|
refresh: input.refresh,
|
|
23064
|
-
context: requestContext("serp-competitors", input.context)
|
|
23280
|
+
context: requestContext$1("serp-competitors", input.context)
|
|
23065
23281
|
});
|
|
23066
23282
|
const responseResults = snapshot.response.tasks.flatMap((task) => task.result ?? []);
|
|
23067
23283
|
const rawRows = responseResults.flatMap((result) => result.items ?? []);
|
|
@@ -23084,16 +23300,16 @@ async function dataForSeoSerpCompetitors(client, input) {
|
|
|
23084
23300
|
return [{
|
|
23085
23301
|
domain: normalizedDomain,
|
|
23086
23302
|
matchedKeywords: row.keywords_count,
|
|
23087
|
-
averagePosition: numberValue(row.avg_position, "average position"),
|
|
23088
|
-
medianPosition: numberValue(row.median_position, "median position"),
|
|
23089
|
-
visibility: numberValue(row.visibility, "visibility"),
|
|
23090
|
-
estimatedMonthlyTraffic: numberValue(row.etv, "estimated monthly traffic"),
|
|
23303
|
+
averagePosition: numberValue$1(row.avg_position, "average position"),
|
|
23304
|
+
medianPosition: numberValue$1(row.median_position, "median position"),
|
|
23305
|
+
visibility: numberValue$1(row.visibility, "visibility"),
|
|
23306
|
+
estimatedMonthlyTraffic: numberValue$1(row.etv, "estimated monthly traffic"),
|
|
23091
23307
|
relevantResults: countValue(row.relevant_serp_items, "relevant result count"),
|
|
23092
23308
|
keywordPositions
|
|
23093
23309
|
}];
|
|
23094
23310
|
});
|
|
23095
|
-
const rows = dedupeBy(mappedRows, (row) => row.domain).sort((left, right) => observedNumber(right.visibility) - observedNumber(left.visibility) || right.matchedKeywords - left.matchedKeywords || compareCodepoints$3(left.domain, right.domain));
|
|
23096
|
-
const providerTotalRows = totalRows(responseResults.map((result) => result.total_count));
|
|
23311
|
+
const rows = dedupeBy(mappedRows, (row) => row.domain).sort((left, right) => observedNumber$1(right.visibility) - observedNumber$1(left.visibility) || right.matchedKeywords - left.matchedKeywords || compareCodepoints$3(left.domain, right.domain));
|
|
23312
|
+
const providerTotalRows = totalRows$1(responseResults.map((result) => result.total_count));
|
|
23097
23313
|
return domainEvidence({
|
|
23098
23314
|
capability: "serp-competitors",
|
|
23099
23315
|
data: {
|
|
@@ -24008,7 +24224,7 @@ function analyzeCompetitorGap(acquisition) {
|
|
|
24008
24224
|
}
|
|
24009
24225
|
//#endregion
|
|
24010
24226
|
//#region packages/core/dist/analyze/domain-research/gap-report.js
|
|
24011
|
-
const MAX_FINDINGS = 10;
|
|
24227
|
+
const MAX_FINDINGS$2 = 10;
|
|
24012
24228
|
const MAX_SOURCE_BRIEFS = 5;
|
|
24013
24229
|
function count(candidates, classification) {
|
|
24014
24230
|
return candidates.filter((item) => item.classification === classification).length;
|
|
@@ -24023,7 +24239,7 @@ function dataStatus(input) {
|
|
|
24023
24239
|
function findings$2(input) {
|
|
24024
24240
|
const result = [];
|
|
24025
24241
|
for (const [index, candidate] of input.candidates.entries()) {
|
|
24026
|
-
if (result.length >= MAX_FINDINGS) break;
|
|
24242
|
+
if (result.length >= MAX_FINDINGS$2) break;
|
|
24027
24243
|
if (candidate.classification === "relevant-gap-candidate") result.push({
|
|
24028
24244
|
code: "relevant-competitor-gap",
|
|
24029
24245
|
evidenceRefs: [`candidates[${index}]`, ...candidate.evidenceRefs],
|
|
@@ -24038,7 +24254,7 @@ function findings$2(input) {
|
|
|
24038
24254
|
});
|
|
24039
24255
|
}
|
|
24040
24256
|
for (const [index, pattern] of input.patterns.entries()) {
|
|
24041
|
-
if (result.length >= MAX_FINDINGS) break;
|
|
24257
|
+
if (result.length >= MAX_FINDINGS$2) break;
|
|
24042
24258
|
result.push({
|
|
24043
24259
|
code: "competitor-template-pattern",
|
|
24044
24260
|
evidenceRefs: [`repeatedCompetitorPatterns[${index}]`, ...pattern.evidenceRefs],
|
|
@@ -25085,7 +25301,7 @@ function measurementWindow(input) {
|
|
|
25085
25301
|
}
|
|
25086
25302
|
//#endregion
|
|
25087
25303
|
//#region packages/core/dist/analyze/experiments/measure-change.js
|
|
25088
|
-
const defaultDependencies$
|
|
25304
|
+
const defaultDependencies$6 = {
|
|
25089
25305
|
searchAnalytics: querySearchAnalytics,
|
|
25090
25306
|
googleAnalyticsReport: runGa4Report,
|
|
25091
25307
|
contentGroup: getContentGroup,
|
|
@@ -25103,7 +25319,7 @@ function counterfactualDelta(input) {
|
|
|
25103
25319
|
const expectedAfter = input.treatmentBefore * (input.controlAfter / input.controlBefore);
|
|
25104
25320
|
return fixed(input.treatmentAfter - expectedAfter);
|
|
25105
25321
|
}
|
|
25106
|
-
async function measureChange(input, dependencies = defaultDependencies$
|
|
25322
|
+
async function measureChange(input, dependencies = defaultDependencies$6) {
|
|
25107
25323
|
const now = dependencies.now();
|
|
25108
25324
|
const change = (input.id ? getChange(input.id) : void 0) ?? {
|
|
25109
25325
|
id: "adhoc",
|
|
@@ -25826,14 +26042,14 @@ async function verifyInternalLinkCandidate(input) {
|
|
|
25826
26042
|
}
|
|
25827
26043
|
//#endregion
|
|
25828
26044
|
//#region packages/core/dist/analyze/internal-links.js
|
|
25829
|
-
const MAX_GSC_ROWS$
|
|
25830
|
-
const defaultDependencies$
|
|
26045
|
+
const MAX_GSC_ROWS$2 = 1e5;
|
|
26046
|
+
const defaultDependencies$5 = {
|
|
25831
26047
|
searchAnalytics: querySearchAnalytics,
|
|
25832
26048
|
fetch: fetchPage,
|
|
25833
26049
|
extract: extractPage,
|
|
25834
26050
|
now: () => /* @__PURE__ */ new Date()
|
|
25835
26051
|
};
|
|
25836
|
-
async function internalLinksReport(input, dependencies = defaultDependencies$
|
|
26052
|
+
async function internalLinksReport(input, dependencies = defaultDependencies$5) {
|
|
25837
26053
|
const targetPage = new URL(assertUrlMatchesGscProperty(input.site, input.targetUrl));
|
|
25838
26054
|
targetPage.hash = "";
|
|
25839
26055
|
const targetUrl = targetPage.toString();
|
|
@@ -25882,7 +26098,7 @@ async function internalLinksReport(input, dependencies = defaultDependencies$4)
|
|
|
25882
26098
|
dimensions: ["query", "page"],
|
|
25883
26099
|
type: "web",
|
|
25884
26100
|
dataState: "final",
|
|
25885
|
-
maxRows: MAX_GSC_ROWS$
|
|
26101
|
+
maxRows: MAX_GSC_ROWS$2,
|
|
25886
26102
|
dimensionFilterGroups: [{
|
|
25887
26103
|
groupType: "and",
|
|
25888
26104
|
filters: [{
|
|
@@ -25914,7 +26130,7 @@ async function internalLinksReport(input, dependencies = defaultDependencies$4)
|
|
|
25914
26130
|
dimensions: ["query", "page"],
|
|
25915
26131
|
type: "web",
|
|
25916
26132
|
dataState: "final",
|
|
25917
|
-
maxRows: MAX_GSC_ROWS$
|
|
26133
|
+
maxRows: MAX_GSC_ROWS$2
|
|
25918
26134
|
}, { refresh: input.refresh }) : {
|
|
25919
26135
|
rows: [],
|
|
25920
26136
|
calls: 0,
|
|
@@ -25959,8 +26175,8 @@ async function internalLinksReport(input, dependencies = defaultDependencies$4)
|
|
|
25959
26175
|
else checkedSources += 1;
|
|
25960
26176
|
}
|
|
25961
26177
|
const uncheckedCandidates = Math.max(0, analysis.candidates.length - attemptedSources);
|
|
25962
|
-
const targetTruncated = targetRequests.some(({ result }) => result.rowsFetched >= MAX_GSC_ROWS$
|
|
25963
|
-
const sourceTruncated = sourceResult.rowsFetched >= MAX_GSC_ROWS$
|
|
26178
|
+
const targetTruncated = targetRequests.some(({ result }) => result.rowsFetched >= MAX_GSC_ROWS$2);
|
|
26179
|
+
const sourceTruncated = sourceResult.rowsFetched >= MAX_GSC_ROWS$2;
|
|
25964
26180
|
const invalidRows = analysis.selection.targetInvalidRows + analysis.selection.sourceInvalidRows;
|
|
25965
26181
|
const structuredWarnings = uniqueInternalLinksWarnings(warnings);
|
|
25966
26182
|
const dataStatus = target.verification === "technical-issue" ? "target-technical-issue" : target.verification === "failed" ? "partial" : targetRows.length === 0 ? "empty" : targetTruncated || sourceTruncated || invalidRows > 0 ? "partial" : queryCandidates && sourceResult.rows.length === 0 ? "source-empty" : analysis.selection.targetEligibleQueries === 0 || analysis.candidates.length === 0 ? "filtered" : failedChecks > 0 || uncheckedCandidates > 0 ? "partial" : "complete";
|
|
@@ -26001,18 +26217,18 @@ async function internalLinksReport(input, dependencies = defaultDependencies$4)
|
|
|
26001
26217
|
pageFilter: alias,
|
|
26002
26218
|
rowsFetched: result.rowsFetched,
|
|
26003
26219
|
calls: result.calls,
|
|
26004
|
-
possiblyTruncated: result.rowsFetched >= MAX_GSC_ROWS$
|
|
26220
|
+
possiblyTruncated: result.rowsFetched >= MAX_GSC_ROWS$2
|
|
26005
26221
|
})),
|
|
26006
26222
|
rowsFetched: targetRequests.reduce((sum, { result }) => sum + result.rowsFetched, 0),
|
|
26007
26223
|
calls: targetRequests.reduce((sum, { result }) => sum + result.calls, 0),
|
|
26008
|
-
maxRowsPerRequest: MAX_GSC_ROWS$
|
|
26224
|
+
maxRowsPerRequest: MAX_GSC_ROWS$2,
|
|
26009
26225
|
possiblyTruncated: targetTruncated
|
|
26010
26226
|
},
|
|
26011
26227
|
candidates: {
|
|
26012
26228
|
queried: queryCandidates,
|
|
26013
26229
|
rowsFetched: sourceResult.rowsFetched,
|
|
26014
26230
|
calls: sourceResult.calls,
|
|
26015
|
-
maxRows: MAX_GSC_ROWS$
|
|
26231
|
+
maxRows: MAX_GSC_ROWS$2,
|
|
26016
26232
|
possiblyTruncated: sourceTruncated
|
|
26017
26233
|
},
|
|
26018
26234
|
completeness: !queryCandidates ? "not-queried" : targetTruncated || sourceTruncated ? "possibly-truncated" : "retained-rows-only"
|
|
@@ -26031,7 +26247,7 @@ async function internalLinksReport(input, dependencies = defaultDependencies$4)
|
|
|
26031
26247
|
minImpressions,
|
|
26032
26248
|
resultLimit: limit,
|
|
26033
26249
|
checkLimit,
|
|
26034
|
-
maxGscRowsPerRequest: MAX_GSC_ROWS$
|
|
26250
|
+
maxGscRowsPerRequest: MAX_GSC_ROWS$2
|
|
26035
26251
|
},
|
|
26036
26252
|
target: {
|
|
26037
26253
|
requestedUrl: target.requestedUrl,
|
|
@@ -26968,8 +27184,8 @@ function analyzeSecondPageRows(input) {
|
|
|
26968
27184
|
}
|
|
26969
27185
|
//#endregion
|
|
26970
27186
|
//#region packages/core/dist/analyze/keyword-opportunities.js
|
|
26971
|
-
const DEFAULT_DAYS = 28;
|
|
26972
|
-
const MAX_DAYS = 548;
|
|
27187
|
+
const DEFAULT_DAYS$1 = 28;
|
|
27188
|
+
const MAX_DAYS$1 = 548;
|
|
26973
27189
|
const DEFAULT_REPORT_LIMIT = 10;
|
|
26974
27190
|
const MAX_REPORT_LIMIT = 25;
|
|
26975
27191
|
const DEFAULT_KEYWORD_LIMIT = 30;
|
|
@@ -27016,9 +27232,9 @@ function validateInput$2(input) {
|
|
|
27016
27232
|
site,
|
|
27017
27233
|
days: integerOption$1({
|
|
27018
27234
|
value: input.days,
|
|
27019
|
-
fallback: DEFAULT_DAYS,
|
|
27235
|
+
fallback: DEFAULT_DAYS$1,
|
|
27020
27236
|
minimum: 1,
|
|
27021
|
-
maximum: MAX_DAYS,
|
|
27237
|
+
maximum: MAX_DAYS$1,
|
|
27022
27238
|
label: "Days"
|
|
27023
27239
|
}),
|
|
27024
27240
|
limit: integerOption$1({
|
|
@@ -28553,7 +28769,7 @@ function analyzeIndexCoverageSignals(input) {
|
|
|
28553
28769
|
const MAX_SITEMAP_DOCUMENTS = 20;
|
|
28554
28770
|
const MAX_SEARCH_ROWS = 25e4;
|
|
28555
28771
|
const MAX_SITEMAP_URLS = 25e4;
|
|
28556
|
-
const defaultDependencies$
|
|
28772
|
+
const defaultDependencies$4 = {
|
|
28557
28773
|
loadCrawl: loadCrawlReport,
|
|
28558
28774
|
latestCrawl: latestCrawlReport,
|
|
28559
28775
|
searchAnalytics: querySearchAnalytics,
|
|
@@ -28585,7 +28801,7 @@ function sitemapCompleteness(input) {
|
|
|
28585
28801
|
if (input.inventoryTruncated) return "truncated";
|
|
28586
28802
|
return input.results.every((result) => result.dataStatus === "complete") ? "complete" : "partial";
|
|
28587
28803
|
}
|
|
28588
|
-
async function indexCoverageSignals(input, dependencies = defaultDependencies$
|
|
28804
|
+
async function indexCoverageSignals(input, dependencies = defaultDependencies$4) {
|
|
28589
28805
|
const days = integerOption$2({
|
|
28590
28806
|
value: input.days,
|
|
28591
28807
|
fallback: 90,
|
|
@@ -29068,7 +29284,7 @@ function pruneIndexWatchSnapshots(rootSite, url, database = getDb()) {
|
|
|
29068
29284
|
WHERE attempts.attempt_rank > ? AND latest_success.id IS NULL
|
|
29069
29285
|
)`).run(rootSite, url, rootSite, url, 20).changes;
|
|
29070
29286
|
}
|
|
29071
|
-
const defaultDependencies$
|
|
29287
|
+
const defaultDependencies$3 = {
|
|
29072
29288
|
inspectUrl,
|
|
29073
29289
|
now: () => /* @__PURE__ */ new Date(),
|
|
29074
29290
|
store: {
|
|
@@ -29113,7 +29329,7 @@ function isFatalBatchError(error) {
|
|
|
29113
29329
|
"AUTH_REQUIRED"
|
|
29114
29330
|
].includes(error.code);
|
|
29115
29331
|
}
|
|
29116
|
-
async function indexWatch(input, dependencies = defaultDependencies$
|
|
29332
|
+
async function indexWatch(input, dependencies = defaultDependencies$3) {
|
|
29117
29333
|
const languageCode = validateLanguageCode(input.languageCode);
|
|
29118
29334
|
const dailyLimit = integerOption$2({
|
|
29119
29335
|
value: input.dailyLimit,
|
|
@@ -31208,9 +31424,9 @@ function pseoSampleUrls(report) {
|
|
|
31208
31424
|
}
|
|
31209
31425
|
//#endregion
|
|
31210
31426
|
//#region packages/core/dist/analyze/pseo/audit.js
|
|
31211
|
-
const MAX_GSC_ROWS = 5e4;
|
|
31427
|
+
const MAX_GSC_ROWS$1 = 5e4;
|
|
31212
31428
|
const DEFAULT_SITEMAP_URLS = 5e4;
|
|
31213
|
-
const defaultDependencies$
|
|
31429
|
+
const defaultDependencies$2 = {
|
|
31214
31430
|
searchAnalytics: querySearchAnalytics,
|
|
31215
31431
|
fetchPage,
|
|
31216
31432
|
inspectUrl,
|
|
@@ -31412,7 +31628,7 @@ async function inspectSample(input) {
|
|
|
31412
31628
|
};
|
|
31413
31629
|
}
|
|
31414
31630
|
}
|
|
31415
|
-
async function acquirePseoAuditEvidence(input, dependencies = defaultDependencies$
|
|
31631
|
+
async function acquirePseoAuditEvidence(input, dependencies = defaultDependencies$2) {
|
|
31416
31632
|
const options = pseoAuditOptions(input);
|
|
31417
31633
|
const now = dependencies.now();
|
|
31418
31634
|
const generatedAt = now.toISOString();
|
|
@@ -31431,7 +31647,7 @@ async function acquirePseoAuditEvidence(input, dependencies = defaultDependencie
|
|
|
31431
31647
|
type: "web",
|
|
31432
31648
|
dataState: "final",
|
|
31433
31649
|
aggregationType: "auto",
|
|
31434
|
-
maxRows: MAX_GSC_ROWS
|
|
31650
|
+
maxRows: MAX_GSC_ROWS$1
|
|
31435
31651
|
};
|
|
31436
31652
|
input.progress?.(`Fetching ${options.days} days of GSC page and query/page rows`);
|
|
31437
31653
|
const [pageResponse, queryPageResponse] = await Promise.all([dependencies.searchAnalytics(input.site, {
|
|
@@ -31482,7 +31698,7 @@ function buildPseoAuditFromAcquisition(input, acquisition, evidence) {
|
|
|
31482
31698
|
minimumTemplateImpressions: options.minimumTemplateImpressions,
|
|
31483
31699
|
crawlSamplesPerTemplate: options.crawlSamples,
|
|
31484
31700
|
inspectionSamplesPerTemplate: options.inspectSamples,
|
|
31485
|
-
maxRowsPerRequest: MAX_GSC_ROWS,
|
|
31701
|
+
maxRowsPerRequest: MAX_GSC_ROWS$1,
|
|
31486
31702
|
pageRowsFetched: acquisition.pageRowsFetched,
|
|
31487
31703
|
queryPageRowsFetched: acquisition.queryPageRowsFetched,
|
|
31488
31704
|
sitemapsRequested: input.sitemaps?.length ?? 0,
|
|
@@ -31493,7 +31709,7 @@ function buildPseoAuditFromAcquisition(input, acquisition, evidence) {
|
|
|
31493
31709
|
caveats: [`Data freshness: ${input.refresh ? "fresh fetch requested; caches bypassed where supported" : "local cache allowed; use --refresh to bypass cached provider and HTTP data"}.`, input.sitemaps?.length ? `Sitemaps: ${countLabel(input.sitemaps.length, "sitemap URL")} requested.` : "Sitemaps: none provided; template discovery used retained GSC page rows."]
|
|
31494
31710
|
});
|
|
31495
31711
|
}
|
|
31496
|
-
async function pseoAuditReport(input, dependencies = defaultDependencies$
|
|
31712
|
+
async function pseoAuditReport(input, dependencies = defaultDependencies$2) {
|
|
31497
31713
|
const acquisition = await acquirePseoAuditEvidence(input, dependencies);
|
|
31498
31714
|
const sampleUrls = pseoSampleUrls(buildPseoAuditFromAcquisition(input, acquisition));
|
|
31499
31715
|
const topQueries = topQueryByUrl(acquisition.queryPageRows, input);
|
|
@@ -32335,7 +32551,7 @@ function pseoOpportunityVerdict(input) {
|
|
|
32335
32551
|
//#region packages/core/dist/providers/dataforseo/serp-snapshot.js
|
|
32336
32552
|
const MAX_SERP_DEPTH = 100;
|
|
32337
32553
|
const SERP_ENDPOINT = "v3/serp/google/organic/live/advanced";
|
|
32338
|
-
function safeUrl(value) {
|
|
32554
|
+
function safeUrl$1(value) {
|
|
32339
32555
|
if (!value) return null;
|
|
32340
32556
|
try {
|
|
32341
32557
|
const parsed = new URL(value);
|
|
@@ -32348,11 +32564,11 @@ function safeUrl(value) {
|
|
|
32348
32564
|
}
|
|
32349
32565
|
}
|
|
32350
32566
|
function validOrganic(item) {
|
|
32351
|
-
return item.type === "organic" && Number.isSafeInteger(item.rank_group) && (item.rank_group ?? 0) > 0 && Number.isSafeInteger(item.rank_absolute) && (item.rank_absolute ?? 0) > 0 && Number.isSafeInteger(item.page) && (item.page ?? 0) > 0 && Boolean(item.domain?.trim()) && safeUrl(item.url) !== null;
|
|
32567
|
+
return item.type === "organic" && Number.isSafeInteger(item.rank_group) && (item.rank_group ?? 0) > 0 && Number.isSafeInteger(item.rank_absolute) && (item.rank_absolute ?? 0) > 0 && Number.isSafeInteger(item.page) && (item.page ?? 0) > 0 && Boolean(item.domain?.trim()) && safeUrl$1(item.url) !== null;
|
|
32352
32568
|
}
|
|
32353
32569
|
function organicResult(item) {
|
|
32354
32570
|
if (!validOrganic(item)) return null;
|
|
32355
|
-
const url = safeUrl(item.url);
|
|
32571
|
+
const url = safeUrl$1(item.url);
|
|
32356
32572
|
if (!url) return null;
|
|
32357
32573
|
return {
|
|
32358
32574
|
rankGroup: item.rank_group,
|
|
@@ -32401,7 +32617,7 @@ function mapDataForSeoSerpSnapshot(input, snapshot, endpoint = SERP_ENDPOINT) {
|
|
|
32401
32617
|
field: "checkedAt",
|
|
32402
32618
|
message: "DataForSEO returned an invalid SERP observation time; the local response time was retained."
|
|
32403
32619
|
});
|
|
32404
|
-
const checkUrl = safeUrl(result.check_url);
|
|
32620
|
+
const checkUrl = safeUrl$1(result.check_url);
|
|
32405
32621
|
if (result.check_url && !checkUrl) warnings.push({
|
|
32406
32622
|
code: "invalid-serp-check-url",
|
|
32407
32623
|
field: "checkUrl",
|
|
@@ -34544,7 +34760,7 @@ function groupedMappings(evidence) {
|
|
|
34544
34760
|
//#endregion
|
|
34545
34761
|
//#region packages/core/dist/analyze/second-page.js
|
|
34546
34762
|
const MAX_SOURCE_ROWS = 1e5;
|
|
34547
|
-
const defaultDependencies = {
|
|
34763
|
+
const defaultDependencies$1 = {
|
|
34548
34764
|
searchAnalytics: querySearchAnalytics,
|
|
34549
34765
|
fetch: fetchPage,
|
|
34550
34766
|
extract: extractPage,
|
|
@@ -34621,7 +34837,7 @@ function reportRecommendations(items) {
|
|
|
34621
34837
|
"Treat the priority score as a triage heuristic, not an estimated traffic lift."
|
|
34622
34838
|
]);
|
|
34623
34839
|
}
|
|
34624
|
-
async function secondPage(input, dependencies = defaultDependencies) {
|
|
34840
|
+
async function secondPage(input, dependencies = defaultDependencies$1) {
|
|
34625
34841
|
const days = integerOption({
|
|
34626
34842
|
value: input.range,
|
|
34627
34843
|
fallback: 28,
|
|
@@ -36812,10 +37028,10 @@ const DEFAULT_TARGET_LIMIT = 20;
|
|
|
36812
37028
|
const MAX_TARGET_LIMIT = 50;
|
|
36813
37029
|
const DEFAULT_DETAIL_PAGES = 1;
|
|
36814
37030
|
const MAX_DETAIL_PAGES = 3;
|
|
36815
|
-
const DEFAULT_ROW_LIMIT$
|
|
36816
|
-
const MAX_ROW_LIMIT$
|
|
37031
|
+
const DEFAULT_ROW_LIMIT$3 = 500;
|
|
37032
|
+
const MAX_ROW_LIMIT$3 = 1e3;
|
|
36817
37033
|
const MAX_CONCURRENCY = 3;
|
|
36818
|
-
function integer(value, fallback, max, name) {
|
|
37034
|
+
function integer$1(value, fallback, max, name) {
|
|
36819
37035
|
const resolved = value ?? fallback;
|
|
36820
37036
|
if (!Number.isInteger(resolved) || resolved < 1 || resolved > max) throw new SeoError("INVALID_INPUT", `${name} must be between 1 and ${max}.`);
|
|
36821
37037
|
return resolved;
|
|
@@ -36876,9 +37092,9 @@ async function targetDetails(input) {
|
|
|
36876
37092
|
}
|
|
36877
37093
|
async function collectBingLinkEvidence(input) {
|
|
36878
37094
|
if (!input.site.trim()) throw new SeoError("INVALID_INPUT", "Pass a Bing Webmaster site URL.");
|
|
36879
|
-
const targetLimit = integer(input.targetLimit, DEFAULT_TARGET_LIMIT, MAX_TARGET_LIMIT, "Bing link target limit");
|
|
36880
|
-
const detailPages = integer(input.detailPagesPerTarget, DEFAULT_DETAIL_PAGES, MAX_DETAIL_PAGES, "Bing detail page limit");
|
|
36881
|
-
const rowLimit = integer(input.rowLimit, DEFAULT_ROW_LIMIT$
|
|
37095
|
+
const targetLimit = integer$1(input.targetLimit, DEFAULT_TARGET_LIMIT, MAX_TARGET_LIMIT, "Bing link target limit");
|
|
37096
|
+
const detailPages = integer$1(input.detailPagesPerTarget, DEFAULT_DETAIL_PAGES, MAX_DETAIL_PAGES, "Bing detail page limit");
|
|
37097
|
+
const rowLimit = integer$1(input.rowLimit, DEFAULT_ROW_LIMIT$3, MAX_ROW_LIMIT$3, "Bing link row limit");
|
|
36882
37098
|
const client = input.client ?? (await createBingWebmasterClient()).client;
|
|
36883
37099
|
const countRows = [];
|
|
36884
37100
|
let countPagesRequested = 0;
|
|
@@ -36999,6 +37215,884 @@ async function collectBingLinkEvidence(input) {
|
|
|
36999
37215
|
};
|
|
37000
37216
|
}
|
|
37001
37217
|
//#endregion
|
|
37218
|
+
//#region packages/core/dist/links/context-sources.js
|
|
37219
|
+
function urlKey(value) {
|
|
37220
|
+
try {
|
|
37221
|
+
const url = new URL(value);
|
|
37222
|
+
if (!["http:", "https:"].includes(url.protocol)) return null;
|
|
37223
|
+
url.hash = "";
|
|
37224
|
+
return url.toString();
|
|
37225
|
+
} catch {
|
|
37226
|
+
return null;
|
|
37227
|
+
}
|
|
37228
|
+
}
|
|
37229
|
+
function compareTargets(left, right) {
|
|
37230
|
+
return right.observedLinks - left.observedLinks || (left.targetUrl < right.targetUrl ? -1 : left.targetUrl > right.targetUrl ? 1 : 0);
|
|
37231
|
+
}
|
|
37232
|
+
function linkTargetCounts(evidence) {
|
|
37233
|
+
if (evidence.targetCounts.length) return [...evidence.targetCounts].sort(compareTargets);
|
|
37234
|
+
const counts = /* @__PURE__ */ new Map();
|
|
37235
|
+
for (const row of evidence.rows) counts.set(row.targetUrl, (counts.get(row.targetUrl) ?? 0) + 1);
|
|
37236
|
+
return [...counts].map(([targetUrl, observedLinks]) => ({
|
|
37237
|
+
targetUrl,
|
|
37238
|
+
observedLinks
|
|
37239
|
+
})).sort(compareTargets);
|
|
37240
|
+
}
|
|
37241
|
+
function crawlForLinkTargets(input) {
|
|
37242
|
+
const values = /* @__PURE__ */ new Map();
|
|
37243
|
+
if (!input.report) {
|
|
37244
|
+
for (const target of input.targets) values.set(target.targetUrl, {
|
|
37245
|
+
state: "unavailable",
|
|
37246
|
+
reason: "No matching saved crawl was available."
|
|
37247
|
+
});
|
|
37248
|
+
return {
|
|
37249
|
+
values,
|
|
37250
|
+
matched: 0
|
|
37251
|
+
};
|
|
37252
|
+
}
|
|
37253
|
+
const pages = /* @__PURE__ */ new Map();
|
|
37254
|
+
for (const page of input.report.pages) for (const value of [page.url, page.finalUrl]) {
|
|
37255
|
+
const key = urlKey(value);
|
|
37256
|
+
if (key && !pages.has(key)) pages.set(key, page);
|
|
37257
|
+
}
|
|
37258
|
+
const issues = /* @__PURE__ */ new Map();
|
|
37259
|
+
for (const issue of input.report.issues) {
|
|
37260
|
+
const key = urlKey(issue.url);
|
|
37261
|
+
if (!key) continue;
|
|
37262
|
+
const ids = issues.get(key) ?? /* @__PURE__ */ new Set();
|
|
37263
|
+
ids.add(issue.ruleId);
|
|
37264
|
+
issues.set(key, ids);
|
|
37265
|
+
}
|
|
37266
|
+
let matched = 0;
|
|
37267
|
+
for (const target of input.targets) {
|
|
37268
|
+
const key = urlKey(target.targetUrl);
|
|
37269
|
+
const page = key ? pages.get(key) : void 0;
|
|
37270
|
+
if (!page) {
|
|
37271
|
+
values.set(target.targetUrl, {
|
|
37272
|
+
state: "not-observed",
|
|
37273
|
+
reason: "The target URL was not present in the retained crawl pages."
|
|
37274
|
+
});
|
|
37275
|
+
continue;
|
|
37276
|
+
}
|
|
37277
|
+
matched += 1;
|
|
37278
|
+
values.set(target.targetUrl, {
|
|
37279
|
+
state: "observed",
|
|
37280
|
+
reportId: input.report.id,
|
|
37281
|
+
observedAt: input.report.generatedAt,
|
|
37282
|
+
status: page.status,
|
|
37283
|
+
finalUrl: page.finalUrl,
|
|
37284
|
+
indexable: page.indexable,
|
|
37285
|
+
canonical: page.canonical ?? null,
|
|
37286
|
+
issueIds: [...issues.get(key) ?? []].sort()
|
|
37287
|
+
});
|
|
37288
|
+
}
|
|
37289
|
+
return {
|
|
37290
|
+
values,
|
|
37291
|
+
matched
|
|
37292
|
+
};
|
|
37293
|
+
}
|
|
37294
|
+
function aggregateLinkSearchRows(rows) {
|
|
37295
|
+
const values = /* @__PURE__ */ new Map();
|
|
37296
|
+
for (const row of rows) {
|
|
37297
|
+
const page = row.keys[0];
|
|
37298
|
+
const key = page ? urlKey(page) : null;
|
|
37299
|
+
if (!key) continue;
|
|
37300
|
+
const current = values.get(key) ?? {
|
|
37301
|
+
clicks: 0,
|
|
37302
|
+
impressions: 0,
|
|
37303
|
+
positionWeight: 0
|
|
37304
|
+
};
|
|
37305
|
+
current.clicks += row.clicks;
|
|
37306
|
+
current.impressions += row.impressions;
|
|
37307
|
+
current.positionWeight += row.position * row.impressions;
|
|
37308
|
+
values.set(key, current);
|
|
37309
|
+
}
|
|
37310
|
+
return values;
|
|
37311
|
+
}
|
|
37312
|
+
//#endregion
|
|
37313
|
+
//#region packages/core/dist/links/context-findings.js
|
|
37314
|
+
const MAX_FINDINGS$1 = 50;
|
|
37315
|
+
function findingPriority(input) {
|
|
37316
|
+
const searchValue = input.search.state === "observed" && (input.search.clicks > 0 || input.search.impressions >= 100);
|
|
37317
|
+
if (input.code === "linked-broken-target" || searchValue) return "high";
|
|
37318
|
+
if (input.observedLinks >= 5) return "medium";
|
|
37319
|
+
return "low";
|
|
37320
|
+
}
|
|
37321
|
+
function finding(input) {
|
|
37322
|
+
const crawl = input.row.crawl;
|
|
37323
|
+
if (crawl.state !== "observed") throw new Error("A link finding requires observed crawl evidence.");
|
|
37324
|
+
const searchEvidence = input.row.searchConsole.state === "observed" ? ` Search Console retained ${input.row.searchConsole.clicks.toFixed(0)} clicks and ${input.row.searchConsole.impressions.toFixed(0)} impressions for this page in the selected window.` : "";
|
|
37325
|
+
const common = {
|
|
37326
|
+
priority: findingPriority({
|
|
37327
|
+
code: input.code,
|
|
37328
|
+
observedLinks: input.row.observedLinks,
|
|
37329
|
+
crawl,
|
|
37330
|
+
search: input.row.searchConsole
|
|
37331
|
+
}),
|
|
37332
|
+
heuristic: true,
|
|
37333
|
+
targetUrl: input.row.targetUrl,
|
|
37334
|
+
observedLinks: input.row.observedLinks,
|
|
37335
|
+
evidenceRefs: [input.row.targetUrl, crawl.reportId]
|
|
37336
|
+
};
|
|
37337
|
+
if (input.code === "linked-broken-target") return {
|
|
37338
|
+
...common,
|
|
37339
|
+
code: input.code,
|
|
37340
|
+
principle: "Linked target pages should resolve intentionally.",
|
|
37341
|
+
evidence: `The saved crawl observed status ${crawl.status} for a target with ${input.row.observedLinks} retained representative links.${searchEvidence}`,
|
|
37342
|
+
action: "Confirm whether the page should exist. Restore it or add one direct redirect to a genuinely equivalent live page when the missing URL is unintended.",
|
|
37343
|
+
verify: "Run redirect-trace for the target, then recrawl it and confirm a stable intended response."
|
|
37344
|
+
};
|
|
37345
|
+
if (input.code === "linked-redirect-target") return {
|
|
37346
|
+
...common,
|
|
37347
|
+
code: input.code,
|
|
37348
|
+
principle: "External links should reach a stable final destination.",
|
|
37349
|
+
evidence: `The saved crawl observed ${input.row.targetUrl} resolving to ${crawl.finalUrl}.${searchEvidence}`,
|
|
37350
|
+
action: "Check that the redirect is intentional, direct and semantically equivalent. Update links you control to the final URL and ask important external sources to update only when the change is worth the effort.",
|
|
37351
|
+
verify: "Run redirect-trace and confirm one stable hop to the intended indexable page."
|
|
37352
|
+
};
|
|
37353
|
+
if (input.code === "linked-canonical-conflict") return {
|
|
37354
|
+
...common,
|
|
37355
|
+
code: input.code,
|
|
37356
|
+
principle: "Canonical hints should agree with the intended linked page.",
|
|
37357
|
+
evidence: `The saved crawl observed canonical ${crawl.canonical} on a linked target that resolved to ${crawl.finalUrl}.${searchEvidence}`,
|
|
37358
|
+
action: "Confirm the preferred URL. Align the canonical and redirect only if the current disagreement is unintended.",
|
|
37359
|
+
verify: "Audit the final page and inspect the declared and Google-selected canonical before changing it."
|
|
37360
|
+
};
|
|
37361
|
+
return {
|
|
37362
|
+
...common,
|
|
37363
|
+
code: input.code,
|
|
37364
|
+
principle: "A linked page should be indexable when search visibility is intended.",
|
|
37365
|
+
evidence: `The saved crawl marked a linked target as non-indexable.${searchEvidence}`,
|
|
37366
|
+
action: "Confirm whether non-indexing is intentional. Remove the blocking signal or redirect to an equivalent indexable page only when this URL should appear in search.",
|
|
37367
|
+
verify: "Audit the page, then use URL Inspection when the intended state is indexable."
|
|
37368
|
+
};
|
|
37369
|
+
}
|
|
37370
|
+
function linkTargetFindings(rows) {
|
|
37371
|
+
return rows.flatMap((row) => {
|
|
37372
|
+
const crawl = row.crawl;
|
|
37373
|
+
if (crawl.state !== "observed") return [];
|
|
37374
|
+
if (crawl.status >= 400) return [finding({
|
|
37375
|
+
row,
|
|
37376
|
+
code: "linked-broken-target"
|
|
37377
|
+
})];
|
|
37378
|
+
if (urlKey(crawl.finalUrl) !== urlKey(row.targetUrl)) return [finding({
|
|
37379
|
+
row,
|
|
37380
|
+
code: "linked-redirect-target"
|
|
37381
|
+
})];
|
|
37382
|
+
if (crawl.canonical && urlKey(crawl.canonical) !== urlKey(crawl.finalUrl)) return [finding({
|
|
37383
|
+
row,
|
|
37384
|
+
code: "linked-canonical-conflict"
|
|
37385
|
+
})];
|
|
37386
|
+
if (!crawl.indexable) return [finding({
|
|
37387
|
+
row,
|
|
37388
|
+
code: "linked-non-indexable-target"
|
|
37389
|
+
})];
|
|
37390
|
+
return [];
|
|
37391
|
+
}).sort((left, right) => ({
|
|
37392
|
+
high: 0,
|
|
37393
|
+
medium: 1,
|
|
37394
|
+
low: 2
|
|
37395
|
+
})[left.priority] - {
|
|
37396
|
+
high: 0,
|
|
37397
|
+
medium: 1,
|
|
37398
|
+
low: 2
|
|
37399
|
+
}[right.priority] || right.observedLinks - left.observedLinks || (left.targetUrl < right.targetUrl ? -1 : left.targetUrl > right.targetUrl ? 1 : 0)).slice(0, MAX_FINDINGS$1);
|
|
37400
|
+
}
|
|
37401
|
+
//#endregion
|
|
37402
|
+
//#region packages/core/dist/links/context.js
|
|
37403
|
+
const DEFAULT_DAYS = 90;
|
|
37404
|
+
const MAX_DAYS = 548;
|
|
37405
|
+
const DEFAULT_CONTEXT_LIMIT = 100;
|
|
37406
|
+
const MAX_CONTEXT_LIMIT = 100;
|
|
37407
|
+
const MAX_GSC_ROWS = 25e3;
|
|
37408
|
+
const defaultDependencies = {
|
|
37409
|
+
latestCrawl: latestCrawlReport,
|
|
37410
|
+
searchAnalytics: querySearchAnalytics,
|
|
37411
|
+
now: () => /* @__PURE__ */ new Date()
|
|
37412
|
+
};
|
|
37413
|
+
function integer(input) {
|
|
37414
|
+
const value = input.value ?? input.fallback;
|
|
37415
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > input.maximum) throw new SeoError("INVALID_INPUT", `${input.label} must be between 1 and ${input.maximum}.`);
|
|
37416
|
+
return value;
|
|
37417
|
+
}
|
|
37418
|
+
function searchEvidence(input) {
|
|
37419
|
+
if (!input.site) return {
|
|
37420
|
+
state: "unavailable",
|
|
37421
|
+
reason: "No Search Console property was selected for this report."
|
|
37422
|
+
};
|
|
37423
|
+
if (input.error) return {
|
|
37424
|
+
state: "unavailable",
|
|
37425
|
+
reason: input.error
|
|
37426
|
+
};
|
|
37427
|
+
if (!input.aggregate) return {
|
|
37428
|
+
state: "not-retained",
|
|
37429
|
+
reason: input.retainedLimitReached ? `The bounded Search Console response reached ${MAX_GSC_ROWS} rows and did not retain this page.` : "Search Console did not retain a page row for this URL in the selected window."
|
|
37430
|
+
};
|
|
37431
|
+
return {
|
|
37432
|
+
state: "observed",
|
|
37433
|
+
clicks: input.aggregate.clicks,
|
|
37434
|
+
impressions: input.aggregate.impressions,
|
|
37435
|
+
ctr: input.aggregate.impressions > 0 ? input.aggregate.clicks / input.aggregate.impressions : 0,
|
|
37436
|
+
position: input.aggregate.impressions > 0 ? input.aggregate.positionWeight / input.aggregate.impressions : 0
|
|
37437
|
+
};
|
|
37438
|
+
}
|
|
37439
|
+
async function linkTargetContext(input, dependencies = defaultDependencies) {
|
|
37440
|
+
const days = integer({
|
|
37441
|
+
value: input.days,
|
|
37442
|
+
fallback: DEFAULT_DAYS,
|
|
37443
|
+
maximum: MAX_DAYS,
|
|
37444
|
+
label: "Link context days"
|
|
37445
|
+
});
|
|
37446
|
+
const limit = integer({
|
|
37447
|
+
value: input.limit,
|
|
37448
|
+
fallback: DEFAULT_CONTEXT_LIMIT,
|
|
37449
|
+
maximum: MAX_CONTEXT_LIMIT,
|
|
37450
|
+
label: "Link context limit"
|
|
37451
|
+
});
|
|
37452
|
+
const available = linkTargetCounts(input.evidence);
|
|
37453
|
+
const selected = available.slice(0, limit);
|
|
37454
|
+
const crawlLookup = input.crawlSite ?? input.searchConsoleSite;
|
|
37455
|
+
const crawl = crawlLookup ? dependencies.latestCrawl(crawlLookup) : void 0;
|
|
37456
|
+
const crawlValues = crawlForLinkTargets({
|
|
37457
|
+
report: crawl,
|
|
37458
|
+
targets: selected
|
|
37459
|
+
});
|
|
37460
|
+
const warnings = [];
|
|
37461
|
+
const range = finalGscDateRange(days, dependencies.now());
|
|
37462
|
+
let searchRows = /* @__PURE__ */ new Map();
|
|
37463
|
+
let searchCalls = 0;
|
|
37464
|
+
let searchRowsFetched = 0;
|
|
37465
|
+
let searchError = null;
|
|
37466
|
+
if (input.searchConsoleSite) try {
|
|
37467
|
+
const result = await dependencies.searchAnalytics(input.searchConsoleSite, {
|
|
37468
|
+
...range,
|
|
37469
|
+
dimensions: ["page"],
|
|
37470
|
+
type: "web",
|
|
37471
|
+
dataState: "final",
|
|
37472
|
+
rowLimit: MAX_GSC_ROWS,
|
|
37473
|
+
maxRows: MAX_GSC_ROWS
|
|
37474
|
+
}, { refresh: input.refresh });
|
|
37475
|
+
searchRows = aggregateLinkSearchRows(result.rows);
|
|
37476
|
+
searchCalls = result.calls;
|
|
37477
|
+
searchRowsFetched = result.rowsFetched;
|
|
37478
|
+
} catch (error) {
|
|
37479
|
+
searchError = error instanceof Error ? error.message : "Search Console evidence could not be loaded.";
|
|
37480
|
+
warnings.push(`Search Console context was unavailable. ${searchError}`);
|
|
37481
|
+
}
|
|
37482
|
+
let searchMatches = 0;
|
|
37483
|
+
const retainedLimitReached = searchRowsFetched >= MAX_GSC_ROWS;
|
|
37484
|
+
const rows = selected.map((target) => {
|
|
37485
|
+
const aggregate = searchRows.get(urlKey(target.targetUrl) ?? "");
|
|
37486
|
+
if (aggregate) searchMatches += 1;
|
|
37487
|
+
return {
|
|
37488
|
+
...target,
|
|
37489
|
+
crawl: crawlValues.values.get(target.targetUrl) ?? {
|
|
37490
|
+
state: "not-observed",
|
|
37491
|
+
reason: "The target URL was not present in the retained crawl pages."
|
|
37492
|
+
},
|
|
37493
|
+
searchConsole: searchEvidence({
|
|
37494
|
+
site: input.searchConsoleSite,
|
|
37495
|
+
error: searchError,
|
|
37496
|
+
retainedLimitReached,
|
|
37497
|
+
aggregate
|
|
37498
|
+
})
|
|
37499
|
+
};
|
|
37500
|
+
});
|
|
37501
|
+
if (!crawl) warnings.push("No matching saved crawl was available, so target response and indexability checks were skipped.");
|
|
37502
|
+
if (retainedLimitReached) warnings.push(`Search Console reached the ${MAX_GSC_ROWS}-row acquisition limit. Missing target rows remain not retained rather than zero.`);
|
|
37503
|
+
const unavailableSources = Number(!crawl) + Number(!input.searchConsoleSite);
|
|
37504
|
+
return {
|
|
37505
|
+
schemaVersion: 1,
|
|
37506
|
+
dataStatus: unavailableSources === 2 ? "unavailable" : unavailableSources > 0 || searchError || retainedLimitReached ? "partial" : "complete",
|
|
37507
|
+
selection: {
|
|
37508
|
+
availableTargets: available.length,
|
|
37509
|
+
returnedTargets: rows.length,
|
|
37510
|
+
omittedTargets: Math.max(0, available.length - rows.length),
|
|
37511
|
+
limit
|
|
37512
|
+
},
|
|
37513
|
+
provenance: {
|
|
37514
|
+
crawl: {
|
|
37515
|
+
status: crawl ? "joined" : "unavailable",
|
|
37516
|
+
reportId: crawl?.id ?? null,
|
|
37517
|
+
observedAt: crawl?.generatedAt ?? null,
|
|
37518
|
+
availablePages: crawl?.pages.length ?? 0,
|
|
37519
|
+
matchedTargets: crawlValues.matched
|
|
37520
|
+
},
|
|
37521
|
+
searchConsole: {
|
|
37522
|
+
status: !input.searchConsoleSite || searchError ? "unavailable" : retainedLimitReached ? "partial" : "joined",
|
|
37523
|
+
site: input.searchConsoleSite ?? null,
|
|
37524
|
+
range: input.searchConsoleSite ? {
|
|
37525
|
+
...range,
|
|
37526
|
+
days
|
|
37527
|
+
} : null,
|
|
37528
|
+
calls: searchCalls,
|
|
37529
|
+
rowsFetched: searchRowsFetched,
|
|
37530
|
+
retainedRowLimit: MAX_GSC_ROWS,
|
|
37531
|
+
retainedRowLimitReached: retainedLimitReached,
|
|
37532
|
+
matchedTargets: searchMatches
|
|
37533
|
+
}
|
|
37534
|
+
},
|
|
37535
|
+
rows,
|
|
37536
|
+
findings: linkTargetFindings(rows),
|
|
37537
|
+
warnings
|
|
37538
|
+
};
|
|
37539
|
+
}
|
|
37540
|
+
//#endregion
|
|
37541
|
+
//#region packages/core/dist/providers/dataforseo/link-research-shared.js
|
|
37542
|
+
function linkTarget(value, requestedScope = "domain") {
|
|
37543
|
+
const raw = value.trim();
|
|
37544
|
+
if (!raw || raw.length > 2048) throw invalidTarget();
|
|
37545
|
+
if (requestedScope === "page") try {
|
|
37546
|
+
const url = new URL(raw);
|
|
37547
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password) throw new Error();
|
|
37548
|
+
url.hash = "";
|
|
37549
|
+
return {
|
|
37550
|
+
target: url.toString(),
|
|
37551
|
+
scope: "page"
|
|
37552
|
+
};
|
|
37553
|
+
} catch {
|
|
37554
|
+
throw invalidTarget();
|
|
37555
|
+
}
|
|
37556
|
+
try {
|
|
37557
|
+
const hostname = new URL(raw.includes("://") ? raw : `https://${raw}`).hostname.toLowerCase().replace(/^www\./u, "").replace(/\.$/u, "");
|
|
37558
|
+
if (!hostname || hostname.length > 253 || hostname.includes("..") || !hostname.includes(".") || !/^[a-z0-9.-]+$/u.test(hostname)) throw new Error();
|
|
37559
|
+
return {
|
|
37560
|
+
target: hostname,
|
|
37561
|
+
scope: "domain"
|
|
37562
|
+
};
|
|
37563
|
+
} catch {
|
|
37564
|
+
throw invalidTarget();
|
|
37565
|
+
}
|
|
37566
|
+
}
|
|
37567
|
+
function invalidTarget() {
|
|
37568
|
+
return new ProviderError({
|
|
37569
|
+
provider: "dataforseo",
|
|
37570
|
+
operation: "link-evidence",
|
|
37571
|
+
code: "configuration",
|
|
37572
|
+
message: "Use a valid domain, or choose page scope and pass an absolute URL."
|
|
37573
|
+
});
|
|
37574
|
+
}
|
|
37575
|
+
function linkRowLimit(limit, offset = 0) {
|
|
37576
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1e3) throw new ProviderError({
|
|
37577
|
+
provider: "dataforseo",
|
|
37578
|
+
operation: "link-evidence",
|
|
37579
|
+
code: "configuration",
|
|
37580
|
+
message: `Link row limit must be from 1 to ${MAX_LINK_ROWS$1}.`
|
|
37581
|
+
});
|
|
37582
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || offset > 2e4) throw new ProviderError({
|
|
37583
|
+
provider: "dataforseo",
|
|
37584
|
+
operation: "link-evidence",
|
|
37585
|
+
code: "configuration",
|
|
37586
|
+
message: `Link row offset must be from 0 to ${MAX_LINK_OFFSET}.`
|
|
37587
|
+
});
|
|
37588
|
+
}
|
|
37589
|
+
function requestContext(reportId, supplied) {
|
|
37590
|
+
return supplied ?? {
|
|
37591
|
+
reportId,
|
|
37592
|
+
reportRunId: randomUUID()
|
|
37593
|
+
};
|
|
37594
|
+
}
|
|
37595
|
+
function missing(field) {
|
|
37596
|
+
return unavailableValue("missing", `DataForSEO omitted ${field}.`);
|
|
37597
|
+
}
|
|
37598
|
+
function numberValue(value, field) {
|
|
37599
|
+
return value === null || value === void 0 ? missing(field) : observedValue(value);
|
|
37600
|
+
}
|
|
37601
|
+
function stringValue(value, field) {
|
|
37602
|
+
return value ? observedValue(value) : missing(field);
|
|
37603
|
+
}
|
|
37604
|
+
function metric(id, label, value) {
|
|
37605
|
+
return value === null || value === void 0 ? [] : [{
|
|
37606
|
+
provider: "dataforseo",
|
|
37607
|
+
id,
|
|
37608
|
+
label,
|
|
37609
|
+
value,
|
|
37610
|
+
scale: {
|
|
37611
|
+
minimum: 0,
|
|
37612
|
+
maximum: 100
|
|
37613
|
+
}
|
|
37614
|
+
}];
|
|
37615
|
+
}
|
|
37616
|
+
function safeUrl(value) {
|
|
37617
|
+
if (!value) return null;
|
|
37618
|
+
try {
|
|
37619
|
+
const url = new URL(value);
|
|
37620
|
+
if (!["http:", "https:"].includes(url.protocol)) return null;
|
|
37621
|
+
url.username = "";
|
|
37622
|
+
url.password = "";
|
|
37623
|
+
url.hash = "";
|
|
37624
|
+
return url.toString();
|
|
37625
|
+
} catch {
|
|
37626
|
+
return null;
|
|
37627
|
+
}
|
|
37628
|
+
}
|
|
37629
|
+
function normalizedDate(value) {
|
|
37630
|
+
if (!value) return null;
|
|
37631
|
+
const parsed = Date.parse(value);
|
|
37632
|
+
return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;
|
|
37633
|
+
}
|
|
37634
|
+
function uniqueSorted(values = []) {
|
|
37635
|
+
return [...new Set(values.map((value) => value.trim()).filter(Boolean))].sort(compareCodepoints$3);
|
|
37636
|
+
}
|
|
37637
|
+
function totalRows(values) {
|
|
37638
|
+
const present = values.filter((value) => value !== null && value !== void 0);
|
|
37639
|
+
return present.length ? Math.max(...present) : null;
|
|
37640
|
+
}
|
|
37641
|
+
function linkCoverage(input) {
|
|
37642
|
+
const hasMore = input.providerTotalComparable !== false && input.providerTotalRows !== null ? input.offset + input.returnedRows < input.providerTotalRows : input.returnedRows >= input.requestedRows;
|
|
37643
|
+
return {
|
|
37644
|
+
requestedRows: input.requestedRows,
|
|
37645
|
+
returnedRows: input.returnedRows,
|
|
37646
|
+
retainedRows: input.retainedRows,
|
|
37647
|
+
invalidRows: input.invalidRows,
|
|
37648
|
+
providerTotalRows: input.providerTotalRows,
|
|
37649
|
+
completeness: input.invalidRows > 0 ? "partial" : hasMore ? "capped" : input.filtered ? "filtered" : "complete",
|
|
37650
|
+
nextCursor: hasMore ? String(input.offset + input.returnedRows) : null
|
|
37651
|
+
};
|
|
37652
|
+
}
|
|
37653
|
+
function linkEvidence(input) {
|
|
37654
|
+
return {
|
|
37655
|
+
schemaVersion: 1,
|
|
37656
|
+
provider: "dataforseo",
|
|
37657
|
+
capability: input.capability,
|
|
37658
|
+
data: input.data,
|
|
37659
|
+
observedAt: input.snapshot.observedAt,
|
|
37660
|
+
market: null,
|
|
37661
|
+
coverage: input.coverage,
|
|
37662
|
+
cache: input.snapshot.cache,
|
|
37663
|
+
cost: input.snapshot.cost,
|
|
37664
|
+
request: {
|
|
37665
|
+
operation: input.capability,
|
|
37666
|
+
endpoint: input.endpoint,
|
|
37667
|
+
limit: input.limit,
|
|
37668
|
+
filters: input.filters,
|
|
37669
|
+
sort: input.sort
|
|
37670
|
+
},
|
|
37671
|
+
warnings: input.warnings
|
|
37672
|
+
};
|
|
37673
|
+
}
|
|
37674
|
+
function mappingWarnings(input) {
|
|
37675
|
+
return [
|
|
37676
|
+
...input.snapshotWarnings,
|
|
37677
|
+
...input.invalidRows ? [{
|
|
37678
|
+
code: `invalid-${input.label}-rows`,
|
|
37679
|
+
field: "data.rows",
|
|
37680
|
+
message: `${input.invalidRows} ${input.label} row${input.invalidRows === 1 ? "" : "s"} lacked required fields and were omitted.`
|
|
37681
|
+
}] : [],
|
|
37682
|
+
...input.duplicateRows ? [{
|
|
37683
|
+
code: `duplicate-${input.label}-rows`,
|
|
37684
|
+
field: "data.rows",
|
|
37685
|
+
message: `${input.duplicateRows} duplicate ${input.label} row${input.duplicateRows === 1 ? "" : "s"} were collapsed deterministically.`
|
|
37686
|
+
}] : []
|
|
37687
|
+
];
|
|
37688
|
+
}
|
|
37689
|
+
//#endregion
|
|
37690
|
+
//#region packages/core/dist/providers/dataforseo/backlinks.js
|
|
37691
|
+
function metricValue(row, id) {
|
|
37692
|
+
return row.metrics.find((item) => item.id === id)?.value ?? -1;
|
|
37693
|
+
}
|
|
37694
|
+
function compareRows(left, right) {
|
|
37695
|
+
return metricValue(right, "backlink-rank") - metricValue(left, "backlink-rank") || metricValue(right, "source-domain-rank") - metricValue(left, "source-domain-rank") || compareCodepoints$3(left.sourceDomain, right.sourceDomain) || compareCodepoints$3(left.sourceUrl, right.sourceUrl) || compareCodepoints$3(left.targetUrl, right.targetUrl) || compareCodepoints$3(left.anchorText ?? "", right.anchorText ?? "");
|
|
37696
|
+
}
|
|
37697
|
+
function dedupe(rows) {
|
|
37698
|
+
const sorted = [...rows].sort(compareRows);
|
|
37699
|
+
const seen = /* @__PURE__ */ new Set();
|
|
37700
|
+
return sorted.filter((row) => {
|
|
37701
|
+
const key = `${row.sourceUrl}\0${row.targetUrl}\0${row.anchorText ?? ""}`;
|
|
37702
|
+
if (seen.has(key)) return false;
|
|
37703
|
+
seen.add(key);
|
|
37704
|
+
return true;
|
|
37705
|
+
});
|
|
37706
|
+
}
|
|
37707
|
+
async function dataForSeoBacklinks(client, input) {
|
|
37708
|
+
const normalized = linkTarget(input.target, input.scope);
|
|
37709
|
+
const includeSubdomains = normalized.scope === "domain" ? input.includeSubdomains ?? true : false;
|
|
37710
|
+
const offset = input.offset ?? 0;
|
|
37711
|
+
linkRowLimit(input.limit, offset);
|
|
37712
|
+
const mode = input.mode ?? "representative";
|
|
37713
|
+
const status = input.status ?? "live";
|
|
37714
|
+
const providerMode = mode === "representative" ? "one_per_domain" : "as_is";
|
|
37715
|
+
const orderBy = [
|
|
37716
|
+
"rank,desc",
|
|
37717
|
+
"domain_from_rank,desc",
|
|
37718
|
+
"url_from,asc"
|
|
37719
|
+
];
|
|
37720
|
+
const snapshot = await client.backlinks({
|
|
37721
|
+
...normalized,
|
|
37722
|
+
includeSubdomains,
|
|
37723
|
+
mode: providerMode,
|
|
37724
|
+
status,
|
|
37725
|
+
limit: input.limit,
|
|
37726
|
+
offset,
|
|
37727
|
+
orderBy,
|
|
37728
|
+
refresh: input.refresh,
|
|
37729
|
+
context: requestContext("link-evidence", input.context)
|
|
37730
|
+
});
|
|
37731
|
+
const results = snapshot.response.tasks.flatMap((task) => task.result ?? []);
|
|
37732
|
+
const rawRows = results.flatMap((result) => result.items ?? []);
|
|
37733
|
+
let invalidRows = 0;
|
|
37734
|
+
const mapped = rawRows.flatMap((row) => {
|
|
37735
|
+
const sourceUrl = safeUrl(row.url_from);
|
|
37736
|
+
const targetUrl = safeUrl(row.url_to);
|
|
37737
|
+
if (!sourceUrl || !targetUrl) {
|
|
37738
|
+
invalidRows += 1;
|
|
37739
|
+
return [];
|
|
37740
|
+
}
|
|
37741
|
+
return [{
|
|
37742
|
+
sourceUrl,
|
|
37743
|
+
sourceDomain: new URL(sourceUrl).hostname.toLowerCase(),
|
|
37744
|
+
targetUrl,
|
|
37745
|
+
anchorText: row.anchor ?? null,
|
|
37746
|
+
linkType: row.item_type ?? null,
|
|
37747
|
+
dofollow: row.dofollow ?? null,
|
|
37748
|
+
attributes: uniqueSorted(row.rel_attributes ?? row.attributes ?? []),
|
|
37749
|
+
firstSeenAt: normalizedDate(row.first_seen),
|
|
37750
|
+
lastSeenAt: normalizedDate(row.lost_date ?? row.last_visited),
|
|
37751
|
+
state: row.is_lost || row.lost_date ? "lost" : "live",
|
|
37752
|
+
indirect: row.is_indirect_link ?? null,
|
|
37753
|
+
linksFromPage: row.links_count ?? null,
|
|
37754
|
+
linksFromDomain: row.group_count ?? null,
|
|
37755
|
+
metrics: [
|
|
37756
|
+
...metric("backlink-rank", "DataForSEO backlink rank", row.rank),
|
|
37757
|
+
...metric("source-domain-rank", "DataForSEO source domain rank", row.domain_from_rank),
|
|
37758
|
+
...metric("source-page-rank", "DataForSEO source page rank", row.page_from_rank),
|
|
37759
|
+
...metric("backlink-spam-score", "DataForSEO backlink spam score", row.backlink_spam_score ?? row.backlinks_spam_score)
|
|
37760
|
+
]
|
|
37761
|
+
}];
|
|
37762
|
+
});
|
|
37763
|
+
const rows = dedupe(mapped);
|
|
37764
|
+
const duplicateRows = mapped.length - rows.length;
|
|
37765
|
+
const providerTotalRows = totalRows(results.map((result) => result.total_count));
|
|
37766
|
+
return linkEvidence({
|
|
37767
|
+
capability: "backlinks",
|
|
37768
|
+
data: {
|
|
37769
|
+
target: normalized.target,
|
|
37770
|
+
mode,
|
|
37771
|
+
rows,
|
|
37772
|
+
totalRows: providerTotalRows
|
|
37773
|
+
},
|
|
37774
|
+
snapshot,
|
|
37775
|
+
coverage: linkCoverage({
|
|
37776
|
+
requestedRows: input.limit,
|
|
37777
|
+
returnedRows: rawRows.length,
|
|
37778
|
+
retainedRows: rows.length,
|
|
37779
|
+
invalidRows,
|
|
37780
|
+
providerTotalRows,
|
|
37781
|
+
offset,
|
|
37782
|
+
filtered: mode === "representative" || status !== "all",
|
|
37783
|
+
providerTotalComparable: mode !== "representative"
|
|
37784
|
+
}),
|
|
37785
|
+
endpoint: LINK_ENDPOINTS.backlinks,
|
|
37786
|
+
limit: input.limit,
|
|
37787
|
+
filters: {
|
|
37788
|
+
scope: normalized.scope,
|
|
37789
|
+
includeSubdomains,
|
|
37790
|
+
mode,
|
|
37791
|
+
status,
|
|
37792
|
+
includeIndirectLinks: true,
|
|
37793
|
+
excludeInternalBacklinks: true,
|
|
37794
|
+
offset
|
|
37795
|
+
},
|
|
37796
|
+
sort: orderBy,
|
|
37797
|
+
warnings: mappingWarnings({
|
|
37798
|
+
snapshotWarnings: snapshot.warnings,
|
|
37799
|
+
invalidRows,
|
|
37800
|
+
duplicateRows,
|
|
37801
|
+
label: "backlink"
|
|
37802
|
+
})
|
|
37803
|
+
});
|
|
37804
|
+
}
|
|
37805
|
+
//#endregion
|
|
37806
|
+
//#region packages/core/dist/providers/dataforseo/link-summary.js
|
|
37807
|
+
async function dataForSeoLinkSummary(client, input) {
|
|
37808
|
+
const normalized = linkTarget(input.target, input.scope);
|
|
37809
|
+
const includeSubdomains = normalized.scope === "domain" ? input.includeSubdomains ?? true : false;
|
|
37810
|
+
const snapshot = await client.linkSummary({
|
|
37811
|
+
...normalized,
|
|
37812
|
+
includeSubdomains,
|
|
37813
|
+
refresh: input.refresh,
|
|
37814
|
+
context: requestContext("link-evidence", input.context)
|
|
37815
|
+
});
|
|
37816
|
+
const rows = snapshot.response.tasks.flatMap((task) => task.result ?? []);
|
|
37817
|
+
const row = [...rows].sort((left, right) => compareCodepoints$3(left.target ?? "", right.target ?? ""))[0];
|
|
37818
|
+
const data = {
|
|
37819
|
+
target: normalized.target,
|
|
37820
|
+
scope: normalized.scope,
|
|
37821
|
+
backlinks: numberValue(row?.backlinks, "total backlinks"),
|
|
37822
|
+
referringDomains: numberValue(row?.referring_domains, "referring domains"),
|
|
37823
|
+
referringPages: numberValue(row?.referring_pages, "referring pages"),
|
|
37824
|
+
brokenBacklinks: numberValue(row?.broken_backlinks, "broken backlinks"),
|
|
37825
|
+
brokenPages: numberValue(row?.broken_pages, "broken referring pages"),
|
|
37826
|
+
metrics: [
|
|
37827
|
+
...metric("rank", "DataForSEO rank", row?.rank),
|
|
37828
|
+
...metric("backlinks-spam-score", "DataForSEO backlink spam score", row?.backlinks_spam_score),
|
|
37829
|
+
...metric("target-spam-score", "DataForSEO target spam score", row?.info?.target_spam_score)
|
|
37830
|
+
]
|
|
37831
|
+
};
|
|
37832
|
+
const retainedRows = row ? 1 : 0;
|
|
37833
|
+
return linkEvidence({
|
|
37834
|
+
capability: "link-summary",
|
|
37835
|
+
data,
|
|
37836
|
+
snapshot,
|
|
37837
|
+
coverage: {
|
|
37838
|
+
requestedRows: 1,
|
|
37839
|
+
returnedRows: rows.length,
|
|
37840
|
+
retainedRows,
|
|
37841
|
+
invalidRows: 0,
|
|
37842
|
+
providerTotalRows: rows.length,
|
|
37843
|
+
completeness: row ? "complete" : "unavailable",
|
|
37844
|
+
nextCursor: null
|
|
37845
|
+
},
|
|
37846
|
+
endpoint: LINK_ENDPOINTS.summary,
|
|
37847
|
+
limit: 1,
|
|
37848
|
+
filters: {
|
|
37849
|
+
scope: normalized.scope,
|
|
37850
|
+
includeSubdomains,
|
|
37851
|
+
status: "live",
|
|
37852
|
+
includeIndirectLinks: true,
|
|
37853
|
+
excludeInternalBacklinks: true
|
|
37854
|
+
},
|
|
37855
|
+
sort: [],
|
|
37856
|
+
warnings: [
|
|
37857
|
+
...snapshot.warnings,
|
|
37858
|
+
...rows.length > 1 ? [{
|
|
37859
|
+
code: "multiple-link-summary-rows",
|
|
37860
|
+
field: "data",
|
|
37861
|
+
message: "DataForSEO returned more than one summary row. One row was retained deterministically."
|
|
37862
|
+
}] : [],
|
|
37863
|
+
...!row ? [{
|
|
37864
|
+
code: "missing-link-summary-row",
|
|
37865
|
+
field: "data",
|
|
37866
|
+
message: "DataForSEO returned no summary row. Counts remain unavailable rather than zero."
|
|
37867
|
+
}] : []
|
|
37868
|
+
]
|
|
37869
|
+
});
|
|
37870
|
+
}
|
|
37871
|
+
//#endregion
|
|
37872
|
+
//#region packages/core/dist/providers/dataforseo/referring-domains.js
|
|
37873
|
+
function observedNumber(row) {
|
|
37874
|
+
return row.backlinks.state === "observed" ? row.backlinks.value : -1;
|
|
37875
|
+
}
|
|
37876
|
+
async function dataForSeoReferringDomains(client, input) {
|
|
37877
|
+
const normalized = linkTarget(input.target, input.scope);
|
|
37878
|
+
const includeSubdomains = normalized.scope === "domain" ? input.includeSubdomains ?? true : false;
|
|
37879
|
+
const offset = input.offset ?? 0;
|
|
37880
|
+
linkRowLimit(input.limit, offset);
|
|
37881
|
+
const orderBy = [
|
|
37882
|
+
"backlinks,desc",
|
|
37883
|
+
"rank,desc",
|
|
37884
|
+
"domain,asc"
|
|
37885
|
+
];
|
|
37886
|
+
const snapshot = await client.referringDomains({
|
|
37887
|
+
...normalized,
|
|
37888
|
+
includeSubdomains,
|
|
37889
|
+
limit: input.limit,
|
|
37890
|
+
offset,
|
|
37891
|
+
orderBy,
|
|
37892
|
+
refresh: input.refresh,
|
|
37893
|
+
context: requestContext("link-evidence", input.context)
|
|
37894
|
+
});
|
|
37895
|
+
const results = snapshot.response.tasks.flatMap((task) => task.result ?? []);
|
|
37896
|
+
const rawRows = results.flatMap((result) => result.items ?? []);
|
|
37897
|
+
let invalidRows = 0;
|
|
37898
|
+
const mapped = rawRows.flatMap((row) => {
|
|
37899
|
+
const domain = row.domain?.trim().toLowerCase();
|
|
37900
|
+
if (!domain) {
|
|
37901
|
+
invalidRows += 1;
|
|
37902
|
+
return [];
|
|
37903
|
+
}
|
|
37904
|
+
const firstSeenAt = normalizedDate(row.first_seen);
|
|
37905
|
+
return [{
|
|
37906
|
+
domain,
|
|
37907
|
+
backlinks: numberValue(row.backlinks, "domain backlinks"),
|
|
37908
|
+
referringPages: numberValue(row.referring_pages, "domain referring pages"),
|
|
37909
|
+
brokenBacklinks: numberValue(row.broken_backlinks, "domain broken backlinks"),
|
|
37910
|
+
brokenPages: numberValue(row.broken_pages, "domain broken pages"),
|
|
37911
|
+
firstSeenAt: stringValue(firstSeenAt, "domain first seen date"),
|
|
37912
|
+
metrics: [...metric("source-domain-rank", "DataForSEO source domain rank", row.rank), ...metric("domain-backlinks-spam-score", "DataForSEO domain backlink spam score", row.backlinks_spam_score)]
|
|
37913
|
+
}];
|
|
37914
|
+
});
|
|
37915
|
+
const byDomain = /* @__PURE__ */ new Map();
|
|
37916
|
+
for (const row of mapped.sort((left, right) => observedNumber(right) - observedNumber(left) || compareCodepoints$3(left.domain, right.domain))) if (!byDomain.has(row.domain)) byDomain.set(row.domain, row);
|
|
37917
|
+
const rows = [...byDomain.values()];
|
|
37918
|
+
const duplicateRows = mapped.length - rows.length;
|
|
37919
|
+
const providerTotalRows = totalRows(results.map((result) => result.total_count));
|
|
37920
|
+
return linkEvidence({
|
|
37921
|
+
capability: "referring-domains",
|
|
37922
|
+
data: {
|
|
37923
|
+
target: normalized.target,
|
|
37924
|
+
rows,
|
|
37925
|
+
totalRows: providerTotalRows
|
|
37926
|
+
},
|
|
37927
|
+
snapshot,
|
|
37928
|
+
coverage: linkCoverage({
|
|
37929
|
+
requestedRows: input.limit,
|
|
37930
|
+
returnedRows: rawRows.length,
|
|
37931
|
+
retainedRows: rows.length,
|
|
37932
|
+
invalidRows,
|
|
37933
|
+
providerTotalRows,
|
|
37934
|
+
offset,
|
|
37935
|
+
filtered: true
|
|
37936
|
+
}),
|
|
37937
|
+
endpoint: LINK_ENDPOINTS.referringDomains,
|
|
37938
|
+
limit: input.limit,
|
|
37939
|
+
filters: {
|
|
37940
|
+
scope: normalized.scope,
|
|
37941
|
+
includeSubdomains,
|
|
37942
|
+
status: "live",
|
|
37943
|
+
includeIndirectLinks: true,
|
|
37944
|
+
excludeInternalBacklinks: true,
|
|
37945
|
+
offset
|
|
37946
|
+
},
|
|
37947
|
+
sort: orderBy,
|
|
37948
|
+
warnings: mappingWarnings({
|
|
37949
|
+
snapshotWarnings: snapshot.warnings,
|
|
37950
|
+
invalidRows,
|
|
37951
|
+
duplicateRows,
|
|
37952
|
+
label: "referring-domain"
|
|
37953
|
+
})
|
|
37954
|
+
});
|
|
37955
|
+
}
|
|
37956
|
+
//#endregion
|
|
37957
|
+
//#region packages/core/dist/providers/dataforseo/link-research.js
|
|
37958
|
+
var DataForSeoLinkProvider = class {
|
|
37959
|
+
provider = "dataforseo";
|
|
37960
|
+
capabilitySupport = [
|
|
37961
|
+
"link-summary",
|
|
37962
|
+
"referring-domains",
|
|
37963
|
+
"backlinks"
|
|
37964
|
+
].map((capability) => ({
|
|
37965
|
+
capability,
|
|
37966
|
+
status: "available",
|
|
37967
|
+
markets: "all"
|
|
37968
|
+
}));
|
|
37969
|
+
client;
|
|
37970
|
+
constructor(options = {}) {
|
|
37971
|
+
this.client = options.client ?? new DataForSeoClient(options);
|
|
37972
|
+
}
|
|
37973
|
+
linkSummary(input) {
|
|
37974
|
+
return dataForSeoLinkSummary(this.client, input);
|
|
37975
|
+
}
|
|
37976
|
+
backlinks(input) {
|
|
37977
|
+
return dataForSeoBacklinks(this.client, input);
|
|
37978
|
+
}
|
|
37979
|
+
referringDomains(input) {
|
|
37980
|
+
return dataForSeoReferringDomains(this.client, input);
|
|
37981
|
+
}
|
|
37982
|
+
};
|
|
37983
|
+
//#endregion
|
|
37984
|
+
//#region packages/core/dist/links/dataforseo.js
|
|
37985
|
+
const DEFAULT_ROW_LIMIT$2 = 100;
|
|
37986
|
+
const MAX_ROW_LIMIT$2 = 500;
|
|
37987
|
+
function rowLimit(value) {
|
|
37988
|
+
const limit = value ?? DEFAULT_ROW_LIMIT$2;
|
|
37989
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_ROW_LIMIT$2) throw new SeoError("INVALID_INPUT", `DataForSEO link row limit must be between 1 and ${MAX_ROW_LIMIT$2}.`);
|
|
37990
|
+
return limit;
|
|
37991
|
+
}
|
|
37992
|
+
async function collectDataForSeoLinkEvidence(input) {
|
|
37993
|
+
const limit = rowLimit(input.rowLimit);
|
|
37994
|
+
const provider = input.provider ?? new DataForSeoLinkProvider();
|
|
37995
|
+
const context = input.context ?? {
|
|
37996
|
+
reportId: "link-evidence",
|
|
37997
|
+
reportRunId: randomUUID()
|
|
37998
|
+
};
|
|
37999
|
+
const summary = await provider.linkSummary({
|
|
38000
|
+
target: input.target,
|
|
38001
|
+
scope: input.scope,
|
|
38002
|
+
includeSubdomains: input.includeSubdomains,
|
|
38003
|
+
refresh: input.refresh,
|
|
38004
|
+
context
|
|
38005
|
+
});
|
|
38006
|
+
const backlinks = await provider.backlinks({
|
|
38007
|
+
target: summary.data.target,
|
|
38008
|
+
scope: summary.data.scope,
|
|
38009
|
+
includeSubdomains: input.includeSubdomains,
|
|
38010
|
+
mode: "representative",
|
|
38011
|
+
status: "live",
|
|
38012
|
+
limit,
|
|
38013
|
+
refresh: input.refresh,
|
|
38014
|
+
context
|
|
38015
|
+
});
|
|
38016
|
+
const rows = backlinks.data.rows.map((row) => ({
|
|
38017
|
+
sourceUrl: row.sourceUrl,
|
|
38018
|
+
sourceDomain: row.sourceDomain,
|
|
38019
|
+
targetUrl: row.targetUrl,
|
|
38020
|
+
anchorText: row.anchorText ?? void 0,
|
|
38021
|
+
firstSeenAt: row.firstSeenAt ?? void 0,
|
|
38022
|
+
lastSeenAt: row.lastSeenAt ?? void 0,
|
|
38023
|
+
nofollow: row.dofollow === null ? void 0 : !row.dofollow,
|
|
38024
|
+
linkType: row.linkType ?? void 0,
|
|
38025
|
+
attributes: row.attributes,
|
|
38026
|
+
state: row.state,
|
|
38027
|
+
indirect: row.indirect ?? void 0,
|
|
38028
|
+
linksFromPage: row.linksFromPage ?? void 0,
|
|
38029
|
+
linksFromDomain: row.linksFromDomain ?? void 0,
|
|
38030
|
+
providerMetrics: row.metrics
|
|
38031
|
+
}));
|
|
38032
|
+
const counts = /* @__PURE__ */ new Map();
|
|
38033
|
+
for (const row of rows) counts.set(row.targetUrl, (counts.get(row.targetUrl) ?? 0) + 1);
|
|
38034
|
+
const targetCounts = [...counts].map(([targetUrl, observedLinks]) => ({
|
|
38035
|
+
targetUrl,
|
|
38036
|
+
observedLinks
|
|
38037
|
+
})).sort((left, right) => right.observedLinks - left.observedLinks || (left.targetUrl < right.targetUrl ? -1 : left.targetUrl > right.targetUrl ? 1 : 0));
|
|
38038
|
+
const completeness = backlinks.coverage.completeness;
|
|
38039
|
+
const representative = backlinks.data.mode === "representative";
|
|
38040
|
+
const partial = completeness !== "complete" || representative;
|
|
38041
|
+
return {
|
|
38042
|
+
rows,
|
|
38043
|
+
targetCounts,
|
|
38044
|
+
provenance: {
|
|
38045
|
+
provider: "dataforseo",
|
|
38046
|
+
observedAt: backlinks.observedAt > summary.observedAt ? backlinks.observedAt : summary.observedAt,
|
|
38047
|
+
cached: summary.cache.status === "hit" && backlinks.cache.status === "hit",
|
|
38048
|
+
suppliedRows: backlinks.coverage.returnedRows ?? rows.length,
|
|
38049
|
+
validRows: rows.length,
|
|
38050
|
+
invalidRows: backlinks.coverage.invalidRows,
|
|
38051
|
+
duplicateRows: Math.max(0, (backlinks.coverage.returnedRows ?? rows.length) - backlinks.coverage.invalidRows - rows.length),
|
|
38052
|
+
capped: ["capped", "partial"].includes(completeness),
|
|
38053
|
+
rowLimit: limit,
|
|
38054
|
+
completeness: partial ? "partial" : "complete",
|
|
38055
|
+
providerRequests: {
|
|
38056
|
+
methods: [summary.request.endpoint, backlinks.request.endpoint],
|
|
38057
|
+
maxConcurrentRequests: 1
|
|
38058
|
+
},
|
|
38059
|
+
providerCoverage: {
|
|
38060
|
+
targetCountRows: {
|
|
38061
|
+
returnedRows: targetCounts.length,
|
|
38062
|
+
retainedRows: targetCounts.length,
|
|
38063
|
+
invalidRows: 0
|
|
38064
|
+
},
|
|
38065
|
+
detailRows: {
|
|
38066
|
+
returnedRows: backlinks.coverage.returnedRows ?? rows.length,
|
|
38067
|
+
retainedRows: rows.length,
|
|
38068
|
+
invalidRows: backlinks.coverage.invalidRows
|
|
38069
|
+
},
|
|
38070
|
+
summaryRows: {
|
|
38071
|
+
returnedRows: summary.coverage.returnedRows ?? 0,
|
|
38072
|
+
retainedRows: summary.coverage.retainedRows ?? 0,
|
|
38073
|
+
invalidRows: summary.coverage.invalidRows
|
|
38074
|
+
},
|
|
38075
|
+
backlinkRows: {
|
|
38076
|
+
returnedRows: backlinks.coverage.returnedRows ?? rows.length,
|
|
38077
|
+
retainedRows: rows.length,
|
|
38078
|
+
invalidRows: backlinks.coverage.invalidRows,
|
|
38079
|
+
providerTotalRows: backlinks.coverage.providerTotalRows
|
|
38080
|
+
}
|
|
38081
|
+
}
|
|
38082
|
+
},
|
|
38083
|
+
externalProvider: {
|
|
38084
|
+
summary,
|
|
38085
|
+
backlinks
|
|
38086
|
+
},
|
|
38087
|
+
warnings: [
|
|
38088
|
+
...summary.warnings.map((warning) => warning.message),
|
|
38089
|
+
...backlinks.warnings.map((warning) => warning.message),
|
|
38090
|
+
...representative ? ["The link list retains one representative backlink per referring domain. Provider summary counts remain separate from retained rows."] : [],
|
|
38091
|
+
...!representative && partial ? ["The link list is bounded or partial. Provider summary counts remain separate from retained rows."] : []
|
|
38092
|
+
]
|
|
38093
|
+
};
|
|
38094
|
+
}
|
|
38095
|
+
//#endregion
|
|
37002
38096
|
//#region packages/core/dist/links/import.js
|
|
37003
38097
|
const DEFAULT_ROW_LIMIT$1 = 1e4;
|
|
37004
38098
|
const MAX_ROW_LIMIT$1 = 1e5;
|
|
@@ -37187,6 +38281,10 @@ async function importLinkEvidence(input) {
|
|
|
37187
38281
|
//#region packages/core/dist/links/report.js
|
|
37188
38282
|
const DEFAULT_OUTPUT_LIMIT = 100;
|
|
37189
38283
|
const MAX_OUTPUT_LIMIT = 500;
|
|
38284
|
+
const MAX_TARGET_COUNTS = 100;
|
|
38285
|
+
const MAX_CONTEXT_ROWS = 100;
|
|
38286
|
+
const MAX_FINDINGS = 50;
|
|
38287
|
+
const MAX_STRUCTURED_DETAIL_ROWS = 750;
|
|
37190
38288
|
function boundedLimit(value) {
|
|
37191
38289
|
const limit = value ?? DEFAULT_OUTPUT_LIMIT;
|
|
37192
38290
|
if (!Number.isInteger(limit) || limit < 1 || limit > MAX_OUTPUT_LIMIT) throw new SeoError("INVALID_INPUT", `Link report limit must be between 1 and ${MAX_OUTPUT_LIMIT}.`);
|
|
@@ -37198,7 +38296,7 @@ function targetCounts(rows) {
|
|
|
37198
38296
|
return [...counts].map(([targetUrl, observedLinks]) => ({
|
|
37199
38297
|
targetUrl,
|
|
37200
38298
|
observedLinks
|
|
37201
|
-
})).sort((a, b) => b.observedLinks - a.observedLinks || a.targetUrl.
|
|
38299
|
+
})).sort((a, b) => b.observedLinks - a.observedLinks || (a.targetUrl < b.targetUrl ? -1 : a.targetUrl > b.targetUrl ? 1 : 0));
|
|
37202
38300
|
}
|
|
37203
38301
|
function linkEvidenceReport(input) {
|
|
37204
38302
|
const limit = boundedLimit(input.limit);
|
|
@@ -37206,8 +38304,24 @@ function linkEvidenceReport(input) {
|
|
|
37206
38304
|
const domains = new Set(input.evidence.rows.map((row) => row.sourceDomain));
|
|
37207
38305
|
const observedTargets = targetCounts(input.evidence.rows);
|
|
37208
38306
|
const counts = input.evidence.targetCounts.length ? input.evidence.targetCounts : observedTargets;
|
|
38307
|
+
const providerEvidence = input.evidence.externalProvider ? {
|
|
38308
|
+
summary: input.evidence.externalProvider.summary,
|
|
38309
|
+
backlinks: {
|
|
38310
|
+
...input.evidence.externalProvider.backlinks,
|
|
38311
|
+
data: {
|
|
38312
|
+
target: input.evidence.externalProvider.backlinks.data.target,
|
|
38313
|
+
mode: input.evidence.externalProvider.backlinks.data.mode,
|
|
38314
|
+
totalRows: input.evidence.externalProvider.backlinks.data.totalRows,
|
|
38315
|
+
retainedRows: input.evidence.externalProvider.backlinks.data.rows.length
|
|
38316
|
+
}
|
|
38317
|
+
}
|
|
38318
|
+
} : null;
|
|
38319
|
+
const targetCountsOutput = counts.slice(0, MAX_TARGET_COUNTS);
|
|
38320
|
+
const contextRows = input.targetContext?.rows.slice(0, MAX_CONTEXT_ROWS) ?? [];
|
|
38321
|
+
const findings = input.targetContext?.findings.slice(0, MAX_FINDINGS) ?? [];
|
|
38322
|
+
const returnedDetailRows = rows.length + targetCountsOutput.length + contextRows.length + findings.length;
|
|
37209
38323
|
return {
|
|
37210
|
-
schemaVersion:
|
|
38324
|
+
schemaVersion: 2,
|
|
37211
38325
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
37212
38326
|
dataStatus: input.evidence.provenance.completeness === "complete" ? "complete" : "partial",
|
|
37213
38327
|
summary: {
|
|
@@ -37216,6 +38330,8 @@ function linkEvidenceReport(input) {
|
|
|
37216
38330
|
targetPages: new Set(input.evidence.rows.map((row) => row.targetUrl)).size,
|
|
37217
38331
|
providerTargetPages: counts.length
|
|
37218
38332
|
},
|
|
38333
|
+
providerSummary: providerEvidence?.summary.data ?? null,
|
|
38334
|
+
providerEvidence,
|
|
37219
38335
|
provenance: input.evidence.provenance,
|
|
37220
38336
|
selection: {
|
|
37221
38337
|
availableRows: input.evidence.rows.length,
|
|
@@ -37223,13 +38339,45 @@ function linkEvidenceReport(input) {
|
|
|
37223
38339
|
omittedRows: Math.max(0, input.evidence.rows.length - rows.length),
|
|
37224
38340
|
limit
|
|
37225
38341
|
},
|
|
37226
|
-
|
|
38342
|
+
outputBudget: {
|
|
38343
|
+
unit: "structured-detail-rows",
|
|
38344
|
+
limit: MAX_STRUCTURED_DETAIL_ROWS,
|
|
38345
|
+
returned: returnedDetailRows,
|
|
38346
|
+
omitted: Math.max(0, input.evidence.rows.length - rows.length + Math.max(0, counts.length - targetCountsOutput.length) + Math.max(0, (input.targetContext?.rows.length ?? 0) - contextRows.length) + Math.max(0, (input.targetContext?.findings.length ?? 0) - findings.length)),
|
|
38347
|
+
sections: {
|
|
38348
|
+
links: {
|
|
38349
|
+
limit: MAX_OUTPUT_LIMIT,
|
|
38350
|
+
returned: rows.length
|
|
38351
|
+
},
|
|
38352
|
+
targetCounts: {
|
|
38353
|
+
limit: MAX_TARGET_COUNTS,
|
|
38354
|
+
returned: targetCountsOutput.length
|
|
38355
|
+
},
|
|
38356
|
+
targetContext: {
|
|
38357
|
+
limit: MAX_CONTEXT_ROWS,
|
|
38358
|
+
returned: contextRows.length
|
|
38359
|
+
},
|
|
38360
|
+
findings: {
|
|
38361
|
+
limit: MAX_FINDINGS,
|
|
38362
|
+
returned: findings.length
|
|
38363
|
+
}
|
|
38364
|
+
}
|
|
38365
|
+
},
|
|
38366
|
+
targetCounts: targetCountsOutput,
|
|
37227
38367
|
links: rows,
|
|
37228
|
-
|
|
38368
|
+
targetContext: input.targetContext ? {
|
|
38369
|
+
...input.targetContext,
|
|
38370
|
+
rows: contextRows,
|
|
38371
|
+
findings
|
|
38372
|
+
} : null,
|
|
38373
|
+
findings,
|
|
38374
|
+
warnings: [...input.evidence.warnings, ...input.targetContext?.warnings ?? []],
|
|
37229
38375
|
caveats: [
|
|
37230
38376
|
"This is bounded provider or imported evidence, not a complete backlink index.",
|
|
37231
38377
|
"A missing link is not proof that no link exists, and an observed link is not a quality or ranking verdict.",
|
|
37232
|
-
"Referring-domain counts describe the retained rows only."
|
|
38378
|
+
"Referring-domain counts describe the retained rows only.",
|
|
38379
|
+
...providerEvidence ? ["Provider profile counts describe its current index. The representative link rows are a separate bounded sample.", "Provider rank and spam metrics keep their provider names and scales. They are context, not ranking factors or universal authority scores."] : [],
|
|
38380
|
+
...input.targetContext ? ["Search Console page rows are retained first-party evidence. Missing or capped rows are never treated as zero.", "Saved crawl findings describe the recorded crawl time. Verify the live target before changing it."] : []
|
|
37233
38381
|
]
|
|
37234
38382
|
};
|
|
37235
38383
|
}
|
|
@@ -40651,9 +41799,14 @@ function registerProviderTools(server) {
|
|
|
40651
41799
|
}
|
|
40652
41800
|
});
|
|
40653
41801
|
server.registerTool("seo_link_evidence", {
|
|
40654
|
-
description: "Review bounded referring-link evidence from Bing Webmaster or a local export",
|
|
41802
|
+
description: "Review bounded referring-link evidence from DataForSEO, Bing Webmaster or a local export",
|
|
40655
41803
|
inputSchema: {
|
|
40656
41804
|
site: z$1.string().url().max(2e3).optional(),
|
|
41805
|
+
provider: z$1.enum(["dataforseo", "bing"]).optional(),
|
|
41806
|
+
target: z$1.string().trim().min(1).max(2048).optional(),
|
|
41807
|
+
scope: z$1.enum(["domain", "page"]).optional(),
|
|
41808
|
+
includeSubdomains: z$1.boolean().optional(),
|
|
41809
|
+
searchConsoleSite: z$1.string().trim().min(1).max(2048).optional(),
|
|
40657
41810
|
file: z$1.string().min(1).max(4096).optional(),
|
|
40658
41811
|
format: z$1.enum([
|
|
40659
41812
|
"csv",
|
|
@@ -40663,23 +41816,43 @@ function registerProviderTools(server) {
|
|
|
40663
41816
|
rowLimit: z$1.number().int().min(1).max(1e5).optional(),
|
|
40664
41817
|
targetLimit: z$1.number().int().min(1).max(50).optional(),
|
|
40665
41818
|
detailPagesPerTarget: z$1.number().int().min(1).max(3).optional(),
|
|
40666
|
-
limit: z$1.number().int().min(1).max(500).optional()
|
|
41819
|
+
limit: z$1.number().int().min(1).max(500).optional(),
|
|
41820
|
+
days: z$1.number().int().min(1).max(548).optional(),
|
|
41821
|
+
refresh: z$1.boolean().optional()
|
|
40667
41822
|
}
|
|
40668
|
-
}, async ({ site, file, format, rowLimit, targetLimit, detailPagesPerTarget, limit }) => {
|
|
41823
|
+
}, async ({ site, provider, target, scope, includeSubdomains, searchConsoleSite, file, format, rowLimit, targetLimit, detailPagesPerTarget, limit, days, refresh }) => {
|
|
40669
41824
|
try {
|
|
40670
|
-
|
|
41825
|
+
const liveProvider = provider ?? (target ? "dataforseo" : "bing");
|
|
41826
|
+
if (Number(Boolean(file)) + Number(Boolean(site)) + Number(Boolean(target)) !== 1) throw new SeoError("INVALID_INPUT", "Pass one link source: file, site for Bing, or target for DataForSEO.");
|
|
41827
|
+
if (file && provider) throw new SeoError("INVALID_INPUT", "Do not pass provider with a local link file.");
|
|
41828
|
+
if (liveProvider === "dataforseo" && !target) throw new SeoError("INVALID_INPUT", "Pass target for DataForSEO link evidence.");
|
|
41829
|
+
if (liveProvider === "bing" && !site && !file) throw new SeoError("INVALID_INPUT", "Pass site for Bing link evidence.");
|
|
41830
|
+
const evidence = file ? await importLinkEvidence({
|
|
41831
|
+
file,
|
|
41832
|
+
format,
|
|
41833
|
+
rowLimit
|
|
41834
|
+
}) : liveProvider === "dataforseo" ? await collectDataForSeoLinkEvidence({
|
|
41835
|
+
target: target ?? "",
|
|
41836
|
+
scope,
|
|
41837
|
+
includeSubdomains,
|
|
41838
|
+
rowLimit,
|
|
41839
|
+
refresh
|
|
41840
|
+
}) : await collectBingLinkEvidence({
|
|
41841
|
+
site: site ?? "",
|
|
41842
|
+
rowLimit,
|
|
41843
|
+
targetLimit,
|
|
41844
|
+
detailPagesPerTarget
|
|
41845
|
+
});
|
|
40671
41846
|
const report = linkEvidenceReport({
|
|
40672
|
-
evidence
|
|
40673
|
-
|
|
40674
|
-
|
|
40675
|
-
|
|
40676
|
-
|
|
40677
|
-
|
|
40678
|
-
|
|
40679
|
-
|
|
40680
|
-
|
|
40681
|
-
}),
|
|
40682
|
-
limit
|
|
41847
|
+
evidence,
|
|
41848
|
+
limit,
|
|
41849
|
+
targetContext: searchConsoleSite || evidence.provenance.provider === "dataforseo" ? await linkTargetContext({
|
|
41850
|
+
evidence,
|
|
41851
|
+
searchConsoleSite,
|
|
41852
|
+
crawlSite: searchConsoleSite ?? target,
|
|
41853
|
+
days,
|
|
41854
|
+
refresh
|
|
41855
|
+
}) : void 0
|
|
40683
41856
|
});
|
|
40684
41857
|
return toolSuccess(`${report.summary.observedLinks} referring links were retained from ${report.provenance.provider}.`, report);
|
|
40685
41858
|
} catch (error) {
|
|
@@ -41094,15 +42267,18 @@ const REPORT_DEPTH = {
|
|
|
41094
42267
|
"link-evidence": {
|
|
41095
42268
|
readOrder: [
|
|
41096
42269
|
"dataStatus, provenance, warnings, and caveats",
|
|
41097
|
-
"
|
|
41098
|
-
"
|
|
42270
|
+
"providerSummary and providerEvidence coverage, cache, cost, filters, and observation time when present",
|
|
42271
|
+
"selection, outputBudget, and retained summary counts",
|
|
42272
|
+
"findings and targetContext with crawl and Search Console evidence states",
|
|
42273
|
+
"targetCounts, then the bounded representative links list and provider-native metrics"
|
|
41099
42274
|
],
|
|
41100
42275
|
doNotClaim: [
|
|
41101
|
-
"The retained rows are not a complete backlink index.",
|
|
41102
|
-
"
|
|
41103
|
-
"
|
|
42276
|
+
"The retained rows are not a complete backlink index, and missing rows do not prove a link does not exist.",
|
|
42277
|
+
"Observed links do not establish quality, authority, value, or ranking impact.",
|
|
42278
|
+
"Provider rank and spam metrics keep their provider-specific scales. They are not search-engine ranking factors or interchangeable authority scores.",
|
|
42279
|
+
"A missing Search Console row is not zero, and a saved crawl finding does not prove the target still has that state."
|
|
41104
42280
|
],
|
|
41105
|
-
verify: "Open a selected referring URL
|
|
42281
|
+
verify: "Open a selected referring URL to confirm the link, then check any flagged target live with redirect-trace or audit-page before changing it.",
|
|
41106
42282
|
related: [
|
|
41107
42283
|
{
|
|
41108
42284
|
id: "link-recovery",
|
|
@@ -41115,6 +42291,10 @@ const REPORT_DEPTH = {
|
|
|
41115
42291
|
{
|
|
41116
42292
|
id: "site-crawl",
|
|
41117
42293
|
reason: "Checks internal links and live pages."
|
|
42294
|
+
},
|
|
42295
|
+
{
|
|
42296
|
+
id: "search-performance-overview",
|
|
42297
|
+
reason: "Adds broader first-party search context."
|
|
41118
42298
|
}
|
|
41119
42299
|
]
|
|
41120
42300
|
},
|
|
@@ -42843,10 +44023,14 @@ const REPORT_GUIDANCE = {
|
|
|
42843
44023
|
},
|
|
42844
44024
|
"link-evidence": {
|
|
42845
44025
|
name: "Referring link evidence",
|
|
42846
|
-
description: "Review
|
|
42847
|
-
useWhen: [
|
|
42848
|
-
|
|
42849
|
-
|
|
44026
|
+
description: "Review bounded referring-link evidence from a live provider, Bing Webmaster or a local export.",
|
|
44027
|
+
useWhen: [
|
|
44028
|
+
"You want concrete referring URLs and anchor text where available.",
|
|
44029
|
+
"You want to compare a live provider summary with saved crawl and Search Console evidence for linked target pages.",
|
|
44030
|
+
"You need one normalized view of DataForSEO, Bing or an exported CSV, JSON, or JSONL file."
|
|
44031
|
+
],
|
|
44032
|
+
avoidWhen: ["You need a complete backlink index or a universal authority score.", "You want to find broken internal links during a crawl."],
|
|
44033
|
+
outcome: "Provider summary counts, representative links, target-page search and crawl context, and bounded technical findings with explicit source coverage."
|
|
42850
44034
|
},
|
|
42851
44035
|
"server-log-analysis": {
|
|
42852
44036
|
name: "Server log crawler analysis",
|