seo 0.2.19 → 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 +127 -127
- package/dist/cli.js.map +1 -1
- package/dist/{dist-B0a6PpwF.js → dist-B-zhNcyH.js} +130 -130
- package/dist/dist-B-zhNcyH.js.map +1 -0
- package/dist/{dist-orqZyOnQ.js → dist-BITHJak_.js} +1 -1
- package/dist/{package-Dl66IhTH.js → esm-CSE-hgHf.js} +5 -5
- package/dist/esm-CSE-hgHf.js.map +1 -0
- package/dist/index.d.ts +571 -110
- package/dist/index.js +1515 -317
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +1531 -331
- 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-B0a6PpwF.js.map +0 -1
- package/dist/package-Dl66IhTH.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
|
|
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";
|
|
@@ -115,6 +115,86 @@ function seoErrorEnvelope(error) {
|
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
117
|
//#endregion
|
|
118
|
+
//#region packages/core/dist/version.js
|
|
119
|
+
const SEO_VERSION = version;
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region packages/core/dist/fetch/crawler-identity.js
|
|
122
|
+
const SEO_CRAWLER_TOKEN = "SEO-Skill";
|
|
123
|
+
const SEO_CRAWLER_USER_AGENT = `${SEO_CRAWLER_TOKEN}/${SEO_VERSION} (+https://seoskill.dev)`;
|
|
124
|
+
const SEO_CRAWLER_IDENTITY = {
|
|
125
|
+
name: "SEO Skill",
|
|
126
|
+
robotsToken: SEO_CRAWLER_TOKEN,
|
|
127
|
+
version: SEO_VERSION,
|
|
128
|
+
userAgent: SEO_CRAWLER_USER_AGENT,
|
|
129
|
+
documentationUrl: "https://seoskill.dev/docs/crawler/"
|
|
130
|
+
};
|
|
131
|
+
//#endregion
|
|
132
|
+
//#region packages/core/dist/fetch/http-client.js
|
|
133
|
+
var ResponseSizeLimitError = class extends Error {
|
|
134
|
+
maxBytes;
|
|
135
|
+
constructor(maxBytes, label = "Response") {
|
|
136
|
+
super(`${label} exceeds the ${maxBytes}-byte response limit.`);
|
|
137
|
+
this.maxBytes = maxBytes;
|
|
138
|
+
this.name = "ResponseSizeLimitError";
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
function requestHeaders(profile) {
|
|
142
|
+
if (profile === "bot") return new Headers({
|
|
143
|
+
accept: "text/plain,*/*;q=0.8",
|
|
144
|
+
"accept-language": "en-US,en;q=0.9",
|
|
145
|
+
connection: "close",
|
|
146
|
+
"user-agent": SEO_CRAWLER_USER_AGENT
|
|
147
|
+
});
|
|
148
|
+
return new Headers({
|
|
149
|
+
accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
150
|
+
"accept-language": "en-US,en;q=0.9",
|
|
151
|
+
connection: "close",
|
|
152
|
+
"sec-ch-ua-mobile": "?0",
|
|
153
|
+
"sec-ch-ua-platform": "\"macOS\"",
|
|
154
|
+
"upgrade-insecure-requests": "1",
|
|
155
|
+
"user-agent": SEO_CRAWLER_USER_AGENT
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
function publicHttpFetch(url, input = {}) {
|
|
159
|
+
const { profile = "browser", headers, ...init } = input;
|
|
160
|
+
const base = requestHeaders(profile);
|
|
161
|
+
if (headers instanceof Headers) for (const [key, value] of headers.entries()) base.set(key, value);
|
|
162
|
+
else if (Array.isArray(headers)) for (const [key, value] of headers) base.set(key, value);
|
|
163
|
+
else if (headers) {
|
|
164
|
+
for (const [key, value] of Object.entries(headers)) if (value !== void 0) base.set(key, value);
|
|
165
|
+
}
|
|
166
|
+
return fetch$1(url, {
|
|
167
|
+
...init,
|
|
168
|
+
headers: base
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
async function readBoundedResponseText(response, maxBytes, label = "Response") {
|
|
172
|
+
const declaredLength = Number(response.headers.get("content-length"));
|
|
173
|
+
if (Number.isFinite(declaredLength) && declaredLength > maxBytes) {
|
|
174
|
+
await response.body?.cancel().catch(() => void 0);
|
|
175
|
+
throw new ResponseSizeLimitError(maxBytes, label);
|
|
176
|
+
}
|
|
177
|
+
const reader = response.body?.getReader();
|
|
178
|
+
if (!reader) return "";
|
|
179
|
+
const chunks = [];
|
|
180
|
+
let total = 0;
|
|
181
|
+
try {
|
|
182
|
+
while (true) {
|
|
183
|
+
const { done, value } = await reader.read();
|
|
184
|
+
if (done) break;
|
|
185
|
+
total += value.byteLength;
|
|
186
|
+
if (total > maxBytes) {
|
|
187
|
+
await reader.cancel().catch(() => void 0);
|
|
188
|
+
throw new ResponseSizeLimitError(maxBytes, label);
|
|
189
|
+
}
|
|
190
|
+
chunks.push(value);
|
|
191
|
+
}
|
|
192
|
+
} finally {
|
|
193
|
+
reader.releaseLock();
|
|
194
|
+
}
|
|
195
|
+
return Buffer.concat(chunks, total).toString("utf8");
|
|
196
|
+
}
|
|
197
|
+
//#endregion
|
|
118
198
|
//#region packages/core/dist/paths.js
|
|
119
199
|
let memoizedPaths;
|
|
120
200
|
function defaultConfigDir(input) {
|
|
@@ -1558,6 +1638,22 @@ function hashKey(parts) {
|
|
|
1558
1638
|
return Buffer.from(json).toString("base64url");
|
|
1559
1639
|
}
|
|
1560
1640
|
//#endregion
|
|
1641
|
+
//#region packages/core/dist/ga4/account-summaries.js
|
|
1642
|
+
const propertySummarySchema = z.object({
|
|
1643
|
+
property: z.string().trim().min(1),
|
|
1644
|
+
displayName: z.string().optional(),
|
|
1645
|
+
propertyType: z.string().optional(),
|
|
1646
|
+
parent: z.string().optional()
|
|
1647
|
+
}).strip();
|
|
1648
|
+
z.object({
|
|
1649
|
+
accountSummaries: z.array(z.object({
|
|
1650
|
+
account: z.string().trim().min(1),
|
|
1651
|
+
displayName: z.string().optional(),
|
|
1652
|
+
propertySummaries: z.array(propertySummarySchema).optional()
|
|
1653
|
+
}).strip()).max(200).optional(),
|
|
1654
|
+
nextPageToken: z.string().optional()
|
|
1655
|
+
}).strip();
|
|
1656
|
+
//#endregion
|
|
1561
1657
|
//#region packages/core/dist/ga4/client.js
|
|
1562
1658
|
function ga4RequestCanUseCache(body) {
|
|
1563
1659
|
return body.dateRanges.every((range) => /^\d{4}-\d{2}-\d{2}$/.test(range.startDate) && /^\d{4}-\d{2}-\d{2}$/.test(range.endDate));
|
|
@@ -1718,7 +1814,7 @@ function aiReferralSourceForValue(value) {
|
|
|
1718
1814
|
function compareText$18(left, right) {
|
|
1719
1815
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
1720
1816
|
}
|
|
1721
|
-
function metric(row, name) {
|
|
1817
|
+
function metric$1(row, name) {
|
|
1722
1818
|
const value = row[name];
|
|
1723
1819
|
const parsed = Number(value);
|
|
1724
1820
|
if (value === void 0 || value.trim() === "" || !Number.isInteger(parsed) || parsed < 0) throw new SeoError("PROVIDER_UNAVAILABLE", `Google Analytics returned an invalid ${name} value.`);
|
|
@@ -1753,8 +1849,8 @@ function analyzeAiReferralRows(input) {
|
|
|
1753
1849
|
...emptyMetrics()
|
|
1754
1850
|
};
|
|
1755
1851
|
addMetrics(total, {
|
|
1756
|
-
sessions: metric(row, "sessions"),
|
|
1757
|
-
eventCount: metric(row, "eventCount")
|
|
1852
|
+
sessions: metric$1(row, "sessions"),
|
|
1853
|
+
eventCount: metric$1(row, "eventCount")
|
|
1758
1854
|
});
|
|
1759
1855
|
total.observedSessionSources.add(row.sessionSource ?? "");
|
|
1760
1856
|
sourceTotals.set(definition.id, total);
|
|
@@ -1766,8 +1862,8 @@ function analyzeAiReferralRows(input) {
|
|
|
1766
1862
|
const definition = aiReferralSourceForValue(row.sessionSource ?? "");
|
|
1767
1863
|
if (!definition || !sourceTotals.has(definition.id)) throw new SeoError("PROVIDER_UNAVAILABLE", "Google Analytics detail rows contained an unexpected session source.");
|
|
1768
1864
|
const metrics = {
|
|
1769
|
-
sessions: metric(row, "sessions"),
|
|
1770
|
-
eventCount: metric(row, "eventCount")
|
|
1865
|
+
sessions: metric$1(row, "sessions"),
|
|
1866
|
+
eventCount: metric$1(row, "eventCount")
|
|
1771
1867
|
};
|
|
1772
1868
|
addMetrics(detailTotal, metrics);
|
|
1773
1869
|
const landingPage = row.landingPagePlusQueryString || "(not set)";
|
|
@@ -3410,20 +3506,6 @@ async function extractPage(fetchResult, extractor = "defuddle", dependencies = {
|
|
|
3410
3506
|
};
|
|
3411
3507
|
}
|
|
3412
3508
|
//#endregion
|
|
3413
|
-
//#region packages/core/dist/version.js
|
|
3414
|
-
const SEO_VERSION = version;
|
|
3415
|
-
//#endregion
|
|
3416
|
-
//#region packages/core/dist/fetch/crawler-identity.js
|
|
3417
|
-
const SEO_CRAWLER_TOKEN = "SEO-Skill";
|
|
3418
|
-
const SEO_CRAWLER_USER_AGENT = `${SEO_CRAWLER_TOKEN}/${SEO_VERSION} (+https://seoskill.dev)`;
|
|
3419
|
-
const SEO_CRAWLER_IDENTITY = {
|
|
3420
|
-
name: "SEO Skill",
|
|
3421
|
-
robotsToken: SEO_CRAWLER_TOKEN,
|
|
3422
|
-
version: SEO_VERSION,
|
|
3423
|
-
userAgent: SEO_CRAWLER_USER_AGENT,
|
|
3424
|
-
documentationUrl: "https://seoskill.dev/docs/crawler/"
|
|
3425
|
-
};
|
|
3426
|
-
//#endregion
|
|
3427
3509
|
//#region packages/core/dist/fetch/access-block.js
|
|
3428
3510
|
const CLOUDFLARE_SKIP_DOCS = "https://developers.cloudflare.com/waf/custom-rules/skip/";
|
|
3429
3511
|
function isHeadersLike(headers) {
|
|
@@ -3495,72 +3577,6 @@ function detectAccessBlock(input) {
|
|
|
3495
3577
|
};
|
|
3496
3578
|
}
|
|
3497
3579
|
//#endregion
|
|
3498
|
-
//#region packages/core/dist/fetch/http-client.js
|
|
3499
|
-
var ResponseSizeLimitError = class extends Error {
|
|
3500
|
-
maxBytes;
|
|
3501
|
-
constructor(maxBytes, label = "Response") {
|
|
3502
|
-
super(`${label} exceeds the ${maxBytes}-byte response limit.`);
|
|
3503
|
-
this.maxBytes = maxBytes;
|
|
3504
|
-
this.name = "ResponseSizeLimitError";
|
|
3505
|
-
}
|
|
3506
|
-
};
|
|
3507
|
-
function requestHeaders(profile) {
|
|
3508
|
-
if (profile === "bot") return new Headers({
|
|
3509
|
-
accept: "text/plain,*/*;q=0.8",
|
|
3510
|
-
"accept-language": "en-US,en;q=0.9",
|
|
3511
|
-
connection: "close",
|
|
3512
|
-
"user-agent": SEO_CRAWLER_USER_AGENT
|
|
3513
|
-
});
|
|
3514
|
-
return new Headers({
|
|
3515
|
-
accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
3516
|
-
"accept-language": "en-US,en;q=0.9",
|
|
3517
|
-
connection: "close",
|
|
3518
|
-
"sec-ch-ua-mobile": "?0",
|
|
3519
|
-
"sec-ch-ua-platform": "\"macOS\"",
|
|
3520
|
-
"upgrade-insecure-requests": "1",
|
|
3521
|
-
"user-agent": SEO_CRAWLER_USER_AGENT
|
|
3522
|
-
});
|
|
3523
|
-
}
|
|
3524
|
-
function publicHttpFetch(url, input = {}) {
|
|
3525
|
-
const { profile = "browser", headers, ...init } = input;
|
|
3526
|
-
const base = requestHeaders(profile);
|
|
3527
|
-
if (headers instanceof Headers) for (const [key, value] of headers.entries()) base.set(key, value);
|
|
3528
|
-
else if (Array.isArray(headers)) for (const [key, value] of headers) base.set(key, value);
|
|
3529
|
-
else if (headers) {
|
|
3530
|
-
for (const [key, value] of Object.entries(headers)) if (value !== void 0) base.set(key, value);
|
|
3531
|
-
}
|
|
3532
|
-
return fetch$1(url, {
|
|
3533
|
-
...init,
|
|
3534
|
-
headers: base
|
|
3535
|
-
});
|
|
3536
|
-
}
|
|
3537
|
-
async function readBoundedResponseText(response, maxBytes, label = "Response") {
|
|
3538
|
-
const declaredLength = Number(response.headers.get("content-length"));
|
|
3539
|
-
if (Number.isFinite(declaredLength) && declaredLength > maxBytes) {
|
|
3540
|
-
await response.body?.cancel().catch(() => void 0);
|
|
3541
|
-
throw new ResponseSizeLimitError(maxBytes, label);
|
|
3542
|
-
}
|
|
3543
|
-
const reader = response.body?.getReader();
|
|
3544
|
-
if (!reader) return "";
|
|
3545
|
-
const chunks = [];
|
|
3546
|
-
let total = 0;
|
|
3547
|
-
try {
|
|
3548
|
-
while (true) {
|
|
3549
|
-
const { done, value } = await reader.read();
|
|
3550
|
-
if (done) break;
|
|
3551
|
-
total += value.byteLength;
|
|
3552
|
-
if (total > maxBytes) {
|
|
3553
|
-
await reader.cancel().catch(() => void 0);
|
|
3554
|
-
throw new ResponseSizeLimitError(maxBytes, label);
|
|
3555
|
-
}
|
|
3556
|
-
chunks.push(value);
|
|
3557
|
-
}
|
|
3558
|
-
} finally {
|
|
3559
|
-
reader.releaseLock();
|
|
3560
|
-
}
|
|
3561
|
-
return Buffer.concat(chunks, total).toString("utf8");
|
|
3562
|
-
}
|
|
3563
|
-
//#endregion
|
|
3564
3580
|
//#region packages/core/dist/fetch/page-fetcher/rate-controls.js
|
|
3565
3581
|
const HOST_QUEUES = /* @__PURE__ */ new Map();
|
|
3566
3582
|
const HOST_HEALTH = /* @__PURE__ */ new Map();
|
|
@@ -5307,7 +5323,7 @@ function estimateSerpTitleWidth(title) {
|
|
|
5307
5323
|
}
|
|
5308
5324
|
//#endregion
|
|
5309
5325
|
//#region packages/core/dist/analyze/audit-page.js
|
|
5310
|
-
const defaultDependencies$
|
|
5326
|
+
const defaultDependencies$16 = {
|
|
5311
5327
|
fetchPage,
|
|
5312
5328
|
queryPageMetrics,
|
|
5313
5329
|
now: () => /* @__PURE__ */ new Date()
|
|
@@ -5338,7 +5354,7 @@ function buildRecommendations(report) {
|
|
|
5338
5354
|
});
|
|
5339
5355
|
return recommendations;
|
|
5340
5356
|
}
|
|
5341
|
-
async function auditPage(input, dependencies = defaultDependencies$
|
|
5357
|
+
async function auditPage(input, dependencies = defaultDependencies$16) {
|
|
5342
5358
|
const fetched = await dependencies.fetchPage(input.url, {
|
|
5343
5359
|
js: input.js,
|
|
5344
5360
|
refresh: input.refresh
|
|
@@ -6912,7 +6928,7 @@ function pageOpportunityRecommendations(analysis) {
|
|
|
6912
6928
|
//#endregion
|
|
6913
6929
|
//#region packages/core/dist/analyze/page-opportunities.js
|
|
6914
6930
|
const MAX_BENCHMARK_ROWS = 1e5;
|
|
6915
|
-
const defaultDependencies$
|
|
6931
|
+
const defaultDependencies$15 = {
|
|
6916
6932
|
searchAnalytics: querySearchAnalytics,
|
|
6917
6933
|
fetch: fetchPage,
|
|
6918
6934
|
extract: extractPage,
|
|
@@ -6984,7 +7000,7 @@ function reportCaveats$1(input) {
|
|
|
6984
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.` : ""
|
|
6985
7001
|
].filter(Boolean);
|
|
6986
7002
|
}
|
|
6987
|
-
async function pageOpportunitiesReport(input, dependencies = defaultDependencies$
|
|
7003
|
+
async function pageOpportunitiesReport(input, dependencies = defaultDependencies$15) {
|
|
6988
7004
|
const url = assertUrlMatchesGscProperty(input.site, input.url);
|
|
6989
7005
|
const days = reportDays$1(input.days);
|
|
6990
7006
|
const range = defaultDateRange$1(days);
|
|
@@ -10771,7 +10787,7 @@ function absoluteHttpUrl(value) {
|
|
|
10771
10787
|
return false;
|
|
10772
10788
|
}
|
|
10773
10789
|
}
|
|
10774
|
-
function urlKey(value) {
|
|
10790
|
+
function urlKey$1(value) {
|
|
10775
10791
|
if (!value) return void 0;
|
|
10776
10792
|
try {
|
|
10777
10793
|
const url = new URL(value);
|
|
@@ -10970,7 +10986,7 @@ function auditCrawlPages(pages, opts = {}) {
|
|
|
10970
10986
|
for (const page of pages) {
|
|
10971
10987
|
if (!isAuditableHtmlPage(page)) continue;
|
|
10972
10988
|
for (const value of [page.url, page.finalUrl]) {
|
|
10973
|
-
const key = urlKey(value);
|
|
10989
|
+
const key = urlKey$1(value);
|
|
10974
10990
|
if (key) pageByUrl.set(key, page);
|
|
10975
10991
|
}
|
|
10976
10992
|
}
|
|
@@ -11112,7 +11128,7 @@ function auditCrawlPages(pages, opts = {}) {
|
|
|
11112
11128
|
canonicalRaw: page.canonicalRaw,
|
|
11113
11129
|
canonical: page.canonical
|
|
11114
11130
|
}));
|
|
11115
|
-
const canonicalTarget = pageByUrl.get(urlKey(page.canonical) ?? "");
|
|
11131
|
+
const canonicalTarget = pageByUrl.get(urlKey$1(page.canonical) ?? "");
|
|
11116
11132
|
if (canonicalTarget?.canonical && !sameUrl(canonicalTarget.canonical, page.canonical)) issues.push(issue("canonical_chain", page, page.canonical, {
|
|
11117
11133
|
canonical: page.canonical,
|
|
11118
11134
|
nextCanonical: canonicalTarget.canonical,
|
|
@@ -11286,7 +11302,7 @@ const SENSITIVE_FIELD_NAMES = /* @__PURE__ */ new Set([
|
|
|
11286
11302
|
"refresh-token"
|
|
11287
11303
|
]);
|
|
11288
11304
|
const SENSITIVE_PARAM_PATTERN = /^(access_?token|auth|api_?key|client_?secret|code|jwt|key|password|refresh_?token|secret|session|sig|signature|token)$/i;
|
|
11289
|
-
function uniqueSorted(values = []) {
|
|
11305
|
+
function uniqueSorted$1(values = []) {
|
|
11290
11306
|
return [...new Set(values.filter(Boolean))].sort();
|
|
11291
11307
|
}
|
|
11292
11308
|
function normalizeFetchRate(input, strategy) {
|
|
@@ -11306,13 +11322,13 @@ function normalizeCrawlConfig(input) {
|
|
|
11306
11322
|
mode: input.mode ?? (strategy === "health" ? "sitemap" : "site"),
|
|
11307
11323
|
strategy,
|
|
11308
11324
|
...input.sitemapUrl ? { sitemapUrl: new URL(input.sitemapUrl).toString() } : {},
|
|
11309
|
-
urls: uniqueSorted(input.urls ?? []),
|
|
11325
|
+
urls: uniqueSorted$1(input.urls ?? []),
|
|
11310
11326
|
maxPages: input.maxPages ?? 500,
|
|
11311
11327
|
maxDepth: input.maxDepth ?? 16,
|
|
11312
11328
|
concurrency,
|
|
11313
11329
|
timeoutMs: input.timeoutMs ?? 2e4,
|
|
11314
|
-
include: uniqueSorted(input.include ?? []),
|
|
11315
|
-
exclude: uniqueSorted(input.exclude ?? []),
|
|
11330
|
+
include: uniqueSorted$1(input.include ?? []),
|
|
11331
|
+
exclude: uniqueSorted$1(input.exclude ?? []),
|
|
11316
11332
|
respectRobots: input.respectRobots ?? true,
|
|
11317
11333
|
useSitemap: input.useSitemap ?? true,
|
|
11318
11334
|
checkExternal: strategy === "health" ? false : input.checkExternal ?? true,
|
|
@@ -13557,7 +13573,7 @@ async function fetchSitemapUrls(input) {
|
|
|
13557
13573
|
}
|
|
13558
13574
|
//#endregion
|
|
13559
13575
|
//#region packages/core/dist/analyze/workflows/analytics-value.js
|
|
13560
|
-
const defaultDependencies$
|
|
13576
|
+
const defaultDependencies$14 = { runGa4Report };
|
|
13561
13577
|
function normalizePath(value) {
|
|
13562
13578
|
if (!value || value === "(not set)") return "";
|
|
13563
13579
|
const [path = ""] = value.split("?");
|
|
@@ -13617,7 +13633,7 @@ function landingPageRankingPolicy(input) {
|
|
|
13617
13633
|
warnings
|
|
13618
13634
|
};
|
|
13619
13635
|
}
|
|
13620
|
-
async function fetchLandingPageValues(input, dependencies = defaultDependencies$
|
|
13636
|
+
async function fetchLandingPageValues(input, dependencies = defaultDependencies$14) {
|
|
13621
13637
|
const retainedRowLimit = input.limit ?? 5e3;
|
|
13622
13638
|
if (!input.propertyId) return {
|
|
13623
13639
|
values: /* @__PURE__ */ new Map(),
|
|
@@ -16585,15 +16601,15 @@ function analyzeCtrUnderperformersFromRows(input) {
|
|
|
16585
16601
|
}
|
|
16586
16602
|
//#endregion
|
|
16587
16603
|
//#region packages/core/dist/analyze/ctr-underperformers.js
|
|
16588
|
-
const MAX_GSC_ROWS$
|
|
16604
|
+
const MAX_GSC_ROWS$5 = 1e5;
|
|
16589
16605
|
function plural$3(count, singular, pluralLabel = `${singular}s`) {
|
|
16590
16606
|
return count === 1 ? singular : pluralLabel;
|
|
16591
16607
|
}
|
|
16592
|
-
const defaultDependencies$
|
|
16608
|
+
const defaultDependencies$13 = {
|
|
16593
16609
|
searchAnalytics: querySearchAnalytics,
|
|
16594
16610
|
now: () => /* @__PURE__ */ new Date()
|
|
16595
16611
|
};
|
|
16596
|
-
async function ctrUnderperformersReport(input, dependencies = defaultDependencies$
|
|
16612
|
+
async function ctrUnderperformersReport(input, dependencies = defaultDependencies$13) {
|
|
16597
16613
|
const minImpressions = integerOption$2({
|
|
16598
16614
|
value: input.minImpressions,
|
|
16599
16615
|
fallback: 200,
|
|
@@ -16615,7 +16631,7 @@ async function ctrUnderperformersReport(input, dependencies = defaultDependencie
|
|
|
16615
16631
|
dimensions: ["query", "page"],
|
|
16616
16632
|
type: "web",
|
|
16617
16633
|
dataState: "final",
|
|
16618
|
-
maxRows: MAX_GSC_ROWS$
|
|
16634
|
+
maxRows: MAX_GSC_ROWS$5
|
|
16619
16635
|
}, { refresh: input.refresh });
|
|
16620
16636
|
const { items, totalClickShortfall, returnedClickShortfall, selection } = analyzeCtrUnderperformersFromRows({
|
|
16621
16637
|
rows: source.rows,
|
|
@@ -16626,7 +16642,7 @@ async function ctrUnderperformersReport(input, dependencies = defaultDependencie
|
|
|
16626
16642
|
includeBrand: input.includeBrand
|
|
16627
16643
|
});
|
|
16628
16644
|
const top = items[0];
|
|
16629
|
-
const possiblyTruncated = source.rowsFetched >= MAX_GSC_ROWS$
|
|
16645
|
+
const possiblyTruncated = source.rowsFetched >= MAX_GSC_ROWS$5;
|
|
16630
16646
|
const partialValidation = selection.invalidRows > 0;
|
|
16631
16647
|
const dataStatus = selection.sourceRows === 0 ? "empty" : possiblyTruncated || partialValidation ? "partial" : selection.eligibleUnderperformers === 0 ? "filtered" : "complete";
|
|
16632
16648
|
const completeness = possiblyTruncated && partialValidation ? "partial-and-possibly-truncated" : possiblyTruncated ? "possibly-truncated" : partialValidation ? "partial" : "complete";
|
|
@@ -16643,7 +16659,7 @@ async function ctrUnderperformersReport(input, dependencies = defaultDependencie
|
|
|
16643
16659
|
dataState: "final",
|
|
16644
16660
|
rowsFetched: source.rowsFetched,
|
|
16645
16661
|
calls: source.calls,
|
|
16646
|
-
maxRows: MAX_GSC_ROWS$
|
|
16662
|
+
maxRows: MAX_GSC_ROWS$5,
|
|
16647
16663
|
possiblyTruncated,
|
|
16648
16664
|
completeness,
|
|
16649
16665
|
validation: {
|
|
@@ -16673,7 +16689,7 @@ async function ctrUnderperformersReport(input, dependencies = defaultDependencie
|
|
|
16673
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.",
|
|
16674
16690
|
...selection.invalidRows ? [`${selection.invalidRows} invalid provider ${plural$3(selection.invalidRows, "row")} ${selection.invalidRows === 1 ? "was" : "were"} excluded, so this report is partial.`] : [],
|
|
16675
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.`] : [],
|
|
16676
|
-
...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.`] : [],
|
|
16677
16693
|
...selection.limitedUnderperformers ? [`${selection.limitedUnderperformers} eligible ${plural$3(selection.limitedUnderperformers, "underperformer")} ${selection.limitedUnderperformers === 1 ? "was" : "were"} omitted by the output limit.`] : []
|
|
16678
16694
|
],
|
|
16679
16695
|
recommendations: top ? [
|
|
@@ -17186,11 +17202,11 @@ function unavailableSegmentImpactReport(input) {
|
|
|
17186
17202
|
//#endregion
|
|
17187
17203
|
//#region packages/core/dist/analyze/segment-impact.js
|
|
17188
17204
|
const DEFAULT_MAX_ROWS = 1e5;
|
|
17189
|
-
const defaultDependencies$
|
|
17205
|
+
const defaultDependencies$12 = {
|
|
17190
17206
|
searchAnalytics: querySearchAnalytics,
|
|
17191
17207
|
now: () => /* @__PURE__ */ new Date()
|
|
17192
17208
|
};
|
|
17193
|
-
async function segmentImpact(input, dependencies = defaultDependencies$
|
|
17209
|
+
async function segmentImpact(input, dependencies = defaultDependencies$12) {
|
|
17194
17210
|
validateSegmentSite(input.site);
|
|
17195
17211
|
const now = dependencies.now();
|
|
17196
17212
|
const ranges = resolveSegmentRanges({
|
|
@@ -17743,8 +17759,8 @@ function analyzeCannibalRows(input) {
|
|
|
17743
17759
|
}
|
|
17744
17760
|
//#endregion
|
|
17745
17761
|
//#region packages/core/dist/analyze/site-diagnostics/cannibal.js
|
|
17746
|
-
const MAX_GSC_ROWS$
|
|
17747
|
-
const defaultDependencies$
|
|
17762
|
+
const MAX_GSC_ROWS$4 = 1e5;
|
|
17763
|
+
const defaultDependencies$11 = {
|
|
17748
17764
|
searchAnalytics: querySearchAnalytics,
|
|
17749
17765
|
now: () => /* @__PURE__ */ new Date()
|
|
17750
17766
|
};
|
|
@@ -17757,7 +17773,7 @@ function verdict$4(input) {
|
|
|
17757
17773
|
const result = `${input.returned} of ${input.eligible} multi-URL query candidate${input.eligible === 1 ? "" : "s"} returned for intent and technical review.`;
|
|
17758
17774
|
return input.status === "partial" ? `${result} Provider evidence is partial.` : result;
|
|
17759
17775
|
}
|
|
17760
|
-
async function cannibalReport(input, dependencies = defaultDependencies$
|
|
17776
|
+
async function cannibalReport(input, dependencies = defaultDependencies$11) {
|
|
17761
17777
|
const days = integerOption$2({
|
|
17762
17778
|
value: input.days,
|
|
17763
17779
|
fallback: 28,
|
|
@@ -17787,7 +17803,7 @@ async function cannibalReport(input, dependencies = defaultDependencies$10) {
|
|
|
17787
17803
|
...range,
|
|
17788
17804
|
type: "web",
|
|
17789
17805
|
dataState: "final",
|
|
17790
|
-
maxRows: MAX_GSC_ROWS$
|
|
17806
|
+
maxRows: MAX_GSC_ROWS$4
|
|
17791
17807
|
};
|
|
17792
17808
|
const [pageExposure, propertyDemand] = await Promise.all([dependencies.searchAnalytics(input.site, {
|
|
17793
17809
|
...requestBase,
|
|
@@ -17810,8 +17826,8 @@ async function cannibalReport(input, dependencies = defaultDependencies$10) {
|
|
|
17810
17826
|
brandTerms: input.brandTerms,
|
|
17811
17827
|
includeBrand: input.includeBrand
|
|
17812
17828
|
});
|
|
17813
|
-
const pageTruncated = pageExposure.rowsFetched >= MAX_GSC_ROWS$
|
|
17814
|
-
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;
|
|
17815
17831
|
const possiblyTruncated = pageTruncated || propertyTruncated;
|
|
17816
17832
|
const partialValidation = analysis.selection.invalidRows + analysis.selection.propertyInvalidRows > 0;
|
|
17817
17833
|
const completeness = possiblyTruncated && partialValidation ? "partial-and-possibly-truncated" : possiblyTruncated ? "possibly-truncated" : partialValidation ? "partial" : "complete";
|
|
@@ -17836,7 +17852,7 @@ async function cannibalReport(input, dependencies = defaultDependencies$10) {
|
|
|
17836
17852
|
invalidRows: analysis.selection.invalidRows
|
|
17837
17853
|
},
|
|
17838
17854
|
calls: pageExposure.calls,
|
|
17839
|
-
maxRows: MAX_GSC_ROWS$
|
|
17855
|
+
maxRows: MAX_GSC_ROWS$4,
|
|
17840
17856
|
possiblyTruncated: pageTruncated
|
|
17841
17857
|
},
|
|
17842
17858
|
propertyDemand: {
|
|
@@ -17848,7 +17864,7 @@ async function cannibalReport(input, dependencies = defaultDependencies$10) {
|
|
|
17848
17864
|
invalidRows: analysis.selection.propertyInvalidRows
|
|
17849
17865
|
},
|
|
17850
17866
|
calls: propertyDemand.calls,
|
|
17851
|
-
maxRows: MAX_GSC_ROWS$
|
|
17867
|
+
maxRows: MAX_GSC_ROWS$4,
|
|
17852
17868
|
possiblyTruncated: propertyTruncated
|
|
17853
17869
|
},
|
|
17854
17870
|
completeness
|
|
@@ -18156,8 +18172,8 @@ function analyzeDecay(input) {
|
|
|
18156
18172
|
}
|
|
18157
18173
|
//#endregion
|
|
18158
18174
|
//#region packages/core/dist/analyze/site-diagnostics/decaying.js
|
|
18159
|
-
const MAX_GSC_ROWS$
|
|
18160
|
-
const defaultDependencies$
|
|
18175
|
+
const MAX_GSC_ROWS$3 = 1e5;
|
|
18176
|
+
const defaultDependencies$10 = {
|
|
18161
18177
|
searchAnalytics: querySearchAnalytics,
|
|
18162
18178
|
now: () => /* @__PURE__ */ new Date()
|
|
18163
18179
|
};
|
|
@@ -18168,7 +18184,7 @@ function verdict$3(report) {
|
|
|
18168
18184
|
if (!report.selection.eligibleRows) return "No observed retained query/page declines matched the report filters.";
|
|
18169
18185
|
return `${report.selection.returnedRows} of ${report.selection.eligibleRows} observed retained query/page declines returned for review.`;
|
|
18170
18186
|
}
|
|
18171
|
-
async function decayingReport(input, dependencies = defaultDependencies$
|
|
18187
|
+
async function decayingReport(input, dependencies = defaultDependencies$10) {
|
|
18172
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.");
|
|
18173
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.");
|
|
18174
18190
|
const days = integerOption$2({
|
|
@@ -18221,7 +18237,7 @@ async function decayingReport(input, dependencies = defaultDependencies$9) {
|
|
|
18221
18237
|
aggregationType: "auto",
|
|
18222
18238
|
type: "web",
|
|
18223
18239
|
dataState: "final",
|
|
18224
|
-
maxRows: MAX_GSC_ROWS$
|
|
18240
|
+
maxRows: MAX_GSC_ROWS$3
|
|
18225
18241
|
};
|
|
18226
18242
|
const [current, previous] = await Promise.all([dependencies.searchAnalytics(input.site, {
|
|
18227
18243
|
...request,
|
|
@@ -18244,8 +18260,8 @@ async function decayingReport(input, dependencies = defaultDependencies$9) {
|
|
|
18244
18260
|
brandTerms: input.brandTerms,
|
|
18245
18261
|
includeBrand: input.includeBrand
|
|
18246
18262
|
});
|
|
18247
|
-
const currentTruncated = current.rowsFetched >= MAX_GSC_ROWS$
|
|
18248
|
-
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;
|
|
18249
18265
|
const possiblyTruncated = currentTruncated || previousTruncated;
|
|
18250
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";
|
|
18251
18267
|
const reportBase = {
|
|
@@ -18272,13 +18288,13 @@ async function decayingReport(input, dependencies = defaultDependencies$9) {
|
|
|
18272
18288
|
current: {
|
|
18273
18289
|
rowsFetched: current.rowsFetched,
|
|
18274
18290
|
calls: current.calls,
|
|
18275
|
-
maxRows: MAX_GSC_ROWS$
|
|
18291
|
+
maxRows: MAX_GSC_ROWS$3,
|
|
18276
18292
|
possiblyTruncated: currentTruncated
|
|
18277
18293
|
},
|
|
18278
18294
|
previous: {
|
|
18279
18295
|
rowsFetched: previous.rowsFetched,
|
|
18280
18296
|
calls: previous.calls,
|
|
18281
|
-
maxRows: MAX_GSC_ROWS$
|
|
18297
|
+
maxRows: MAX_GSC_ROWS$3,
|
|
18282
18298
|
possiblyTruncated: previousTruncated
|
|
18283
18299
|
},
|
|
18284
18300
|
completeness: possiblyTruncated ? "possibly-truncated" : "retained-query-rows-only"
|
|
@@ -18654,12 +18670,12 @@ function quickWinTemplateSummaries(items) {
|
|
|
18654
18670
|
}
|
|
18655
18671
|
//#endregion
|
|
18656
18672
|
//#region packages/core/dist/analyze/site-diagnostics/quick-wins.js
|
|
18657
|
-
const DEFAULT_DAYS$
|
|
18658
|
-
const MAX_DAYS$
|
|
18673
|
+
const DEFAULT_DAYS$3 = 28;
|
|
18674
|
+
const MAX_DAYS$3 = 548;
|
|
18659
18675
|
const DEFAULT_VERIFY_LIMIT$1 = 5;
|
|
18660
18676
|
const MAX_VERIFY_LIMIT = 100;
|
|
18661
18677
|
const MAX_SOURCE_ROWS$3 = 1e5;
|
|
18662
|
-
const defaultDependencies$
|
|
18678
|
+
const defaultDependencies$9 = {
|
|
18663
18679
|
searchAnalytics: querySearchAnalytics,
|
|
18664
18680
|
fetch: fetchPage,
|
|
18665
18681
|
extract: extractPage,
|
|
@@ -18678,13 +18694,13 @@ function applyVerification$1(item, coverage) {
|
|
|
18678
18694
|
evidenceRef: `${item.recommendation.evidenceRef} ${coverage.summary}`
|
|
18679
18695
|
};
|
|
18680
18696
|
}
|
|
18681
|
-
async function quickWinsReport(input, dependencies = defaultDependencies$
|
|
18682
|
-
const explicitRange = explicitDateRange(input, MAX_DAYS$
|
|
18697
|
+
async function quickWinsReport(input, dependencies = defaultDependencies$9) {
|
|
18698
|
+
const explicitRange = explicitDateRange(input, MAX_DAYS$3);
|
|
18683
18699
|
const days = explicitRange?.days ?? integerOption$2({
|
|
18684
18700
|
value: input.days,
|
|
18685
|
-
fallback: DEFAULT_DAYS$
|
|
18701
|
+
fallback: DEFAULT_DAYS$3,
|
|
18686
18702
|
minimum: 1,
|
|
18687
|
-
maximum: MAX_DAYS$
|
|
18703
|
+
maximum: MAX_DAYS$3,
|
|
18688
18704
|
label: "Days"
|
|
18689
18705
|
});
|
|
18690
18706
|
const verifyLimit = integerOption$2({
|
|
@@ -19051,11 +19067,11 @@ function analyzeStrikingDistanceRows(input) {
|
|
|
19051
19067
|
}
|
|
19052
19068
|
//#endregion
|
|
19053
19069
|
//#region packages/core/dist/analyze/striking-distance.js
|
|
19054
|
-
const DEFAULT_DAYS$
|
|
19055
|
-
const MAX_DAYS$
|
|
19070
|
+
const DEFAULT_DAYS$2 = 28;
|
|
19071
|
+
const MAX_DAYS$2 = 548;
|
|
19056
19072
|
const DEFAULT_VERIFY_LIMIT = 5;
|
|
19057
19073
|
const MAX_SOURCE_ROWS$2 = 1e5;
|
|
19058
|
-
const defaultDependencies$
|
|
19074
|
+
const defaultDependencies$8 = {
|
|
19059
19075
|
searchAnalytics: querySearchAnalytics,
|
|
19060
19076
|
verifyContent: verifyQueryContent,
|
|
19061
19077
|
now: () => /* @__PURE__ */ new Date()
|
|
@@ -19065,8 +19081,8 @@ function boundedInteger$3(value, fallback, minimum, maximum) {
|
|
|
19065
19081
|
return Math.min(maximum, Math.max(minimum, Math.floor(value)));
|
|
19066
19082
|
}
|
|
19067
19083
|
function reportDays(value) {
|
|
19068
|
-
if (value === void 0) return DEFAULT_DAYS$
|
|
19069
|
-
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}.`);
|
|
19070
19086
|
return value;
|
|
19071
19087
|
}
|
|
19072
19088
|
function dateRange(days, now) {
|
|
@@ -19105,7 +19121,7 @@ function verdict$2(analysis) {
|
|
|
19105
19121
|
if (analysis.dataStatus === "filtered") return "No retained query/page rows met the report filters.";
|
|
19106
19122
|
return `${countLabel(analysis.summary.eligibleRows, "eligible query/page row")} found; ${countLabel(analysis.summary.returnedRows, "row")} returned for review.`;
|
|
19107
19123
|
}
|
|
19108
|
-
async function strikingDistance(input, dependencies = defaultDependencies$
|
|
19124
|
+
async function strikingDistance(input, dependencies = defaultDependencies$8) {
|
|
19109
19125
|
const days = reportDays(input.days);
|
|
19110
19126
|
const generatedAt = dependencies.now().toISOString();
|
|
19111
19127
|
const range = dateRange(days, new Date(generatedAt));
|
|
@@ -19952,7 +19968,7 @@ function emptyQuickWins(input) {
|
|
|
19952
19968
|
warnings: []
|
|
19953
19969
|
};
|
|
19954
19970
|
}
|
|
19955
|
-
const defaultDependencies$
|
|
19971
|
+
const defaultDependencies$7 = {
|
|
19956
19972
|
trafficAnomaly,
|
|
19957
19973
|
updateCorrelation,
|
|
19958
19974
|
segmentImpact,
|
|
@@ -19963,7 +19979,7 @@ const defaultDependencies$6 = {
|
|
|
19963
19979
|
};
|
|
19964
19980
|
async function diagnoseProperty(input, dependencies = {}) {
|
|
19965
19981
|
const providers = {
|
|
19966
|
-
...defaultDependencies$
|
|
19982
|
+
...defaultDependencies$7,
|
|
19967
19983
|
...dependencies
|
|
19968
19984
|
};
|
|
19969
19985
|
const limit = input.limit ?? 10;
|
|
@@ -20827,6 +20843,42 @@ async function providerRequestJson(input) {
|
|
|
20827
20843
|
return parsed.data;
|
|
20828
20844
|
}
|
|
20829
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
|
|
20830
20882
|
//#region packages/core/dist/providers/dataforseo/account-schema.js
|
|
20831
20883
|
const userDataMoneySchema = z.object({
|
|
20832
20884
|
total: z.number().finite().nonnegative().optional(),
|
|
@@ -20844,6 +20896,11 @@ const priceComponentSchema = z.object({
|
|
|
20844
20896
|
const normalPriorityPriceSchema = z.object({ priority_normal: z.array(priceComponentSchema).max(20).optional() }).passthrough();
|
|
20845
20897
|
const livePriceSchema = z.object({ live: normalPriorityPriceSchema.optional() }).passthrough();
|
|
20846
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(),
|
|
20847
20904
|
dataforseo_labs: z.object({
|
|
20848
20905
|
keyword_overview: livePriceSchema.optional(),
|
|
20849
20906
|
keyword_ideas: livePriceSchema.optional(),
|
|
@@ -20901,7 +20958,7 @@ function discoveryNextCursor(response) {
|
|
|
20901
20958
|
//#region packages/core/dist/providers/dataforseo/schema.js
|
|
20902
20959
|
const MAX_PROVIDER_MONTHLY_SEARCH_ROWS = 120;
|
|
20903
20960
|
const nullableNumber$1 = z.number().finite().nullable().optional();
|
|
20904
|
-
const nullableCount$
|
|
20961
|
+
const nullableCount$2 = z.union([z.number().finite(), z.string().trim().min(1)]).nullable().optional();
|
|
20905
20962
|
const monthlySearchSchema$1 = z.object({
|
|
20906
20963
|
year: z.number().int(),
|
|
20907
20964
|
month: z.number().int(),
|
|
@@ -20917,7 +20974,7 @@ const keywordOverviewItemSchema = z.object({
|
|
|
20917
20974
|
last_updated_time: z.string().trim().min(1).nullable().optional()
|
|
20918
20975
|
}).nullish(),
|
|
20919
20976
|
keyword_properties: z.object({ keyword_difficulty: nullableNumber$1 }).nullish(),
|
|
20920
|
-
serp_info: z.object({ se_results_count: nullableCount$
|
|
20977
|
+
serp_info: z.object({ se_results_count: nullableCount$2 }).nullish(),
|
|
20921
20978
|
search_intent_info: z.object({ main_intent: z.enum([
|
|
20922
20979
|
"informational",
|
|
20923
20980
|
"navigational",
|
|
@@ -20990,37 +21047,37 @@ const dataForSeoDiscoveryResponseSchema = z.object({
|
|
|
20990
21047
|
//#region packages/core/dist/providers/dataforseo/domain-schema.js
|
|
20991
21048
|
const nullableNumber = z.number().finite().nullable().optional();
|
|
20992
21049
|
const nullableNonnegativeNumber = z.number().finite().nonnegative().nullable().optional();
|
|
20993
|
-
const nullableCount = z.number().int().nonnegative().nullable().optional();
|
|
21050
|
+
const nullableCount$1 = z.number().int().nonnegative().nullable().optional();
|
|
20994
21051
|
const rankingMetricsSchema = z.object({
|
|
20995
|
-
pos_1: nullableCount,
|
|
20996
|
-
pos_2_3: nullableCount,
|
|
20997
|
-
pos_4_10: nullableCount,
|
|
20998
|
-
pos_11_20: nullableCount,
|
|
20999
|
-
pos_21_30: nullableCount,
|
|
21000
|
-
pos_31_40: nullableCount,
|
|
21001
|
-
pos_41_50: nullableCount,
|
|
21002
|
-
pos_51_60: nullableCount,
|
|
21003
|
-
pos_61_70: nullableCount,
|
|
21004
|
-
pos_71_80: nullableCount,
|
|
21005
|
-
pos_81_90: nullableCount,
|
|
21006
|
-
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,
|
|
21007
21064
|
etv: nullableNonnegativeNumber,
|
|
21008
|
-
count: nullableCount,
|
|
21065
|
+
count: nullableCount$1,
|
|
21009
21066
|
estimated_paid_traffic_cost: nullableNonnegativeNumber,
|
|
21010
|
-
is_new: nullableCount,
|
|
21011
|
-
is_up: nullableCount,
|
|
21012
|
-
is_down: nullableCount,
|
|
21013
|
-
is_lost: nullableCount
|
|
21067
|
+
is_new: nullableCount$1,
|
|
21068
|
+
is_up: nullableCount$1,
|
|
21069
|
+
is_down: nullableCount$1,
|
|
21070
|
+
is_lost: nullableCount$1
|
|
21014
21071
|
}).passthrough();
|
|
21015
21072
|
const metricsSchema = z.object({ organic: rankingMetricsSchema.nullable().optional() }).passthrough();
|
|
21016
|
-
const paidResponseSchema = z.object({
|
|
21073
|
+
const paidResponseSchema$1 = z.object({
|
|
21017
21074
|
status_code: z.number().int(),
|
|
21018
21075
|
status_message: z.string().max(1e3),
|
|
21019
21076
|
cost: z.number().finite().nonnegative().optional(),
|
|
21020
21077
|
tasks_count: z.number().int().nonnegative(),
|
|
21021
21078
|
tasks_error: z.number().int().nonnegative()
|
|
21022
21079
|
}).passthrough();
|
|
21023
|
-
const taskFields = {
|
|
21080
|
+
const taskFields$1 = {
|
|
21024
21081
|
id: z.string().trim().min(1).max(100).optional(),
|
|
21025
21082
|
status_code: z.number().int(),
|
|
21026
21083
|
status_message: z.string().max(1e3),
|
|
@@ -21040,8 +21097,8 @@ const overviewResultSchema = z.object({
|
|
|
21040
21097
|
items_count: z.number().int().nonnegative().optional(),
|
|
21041
21098
|
items: z.array(overviewItemSchema).max(1e3).nullable().optional()
|
|
21042
21099
|
}).passthrough();
|
|
21043
|
-
const dataForSeoDomainOverviewResponseSchema = paidResponseSchema.extend({ tasks: z.array(z.object({
|
|
21044
|
-
...taskFields,
|
|
21100
|
+
const dataForSeoDomainOverviewResponseSchema = paidResponseSchema$1.extend({ tasks: z.array(z.object({
|
|
21101
|
+
...taskFields$1,
|
|
21045
21102
|
result: z.array(overviewResultSchema).max(10).nullable().optional()
|
|
21046
21103
|
}).passthrough()).max(10) });
|
|
21047
21104
|
const rankedSerpItemSchema = z.object({
|
|
@@ -21061,8 +21118,8 @@ const rankedKeywordsResultSchema = z.object({
|
|
|
21061
21118
|
items_count: z.number().int().nonnegative().optional(),
|
|
21062
21119
|
items: z.array(rankedKeywordItemSchema).max(1e3).nullable().optional()
|
|
21063
21120
|
}).passthrough();
|
|
21064
|
-
const dataForSeoRankedKeywordsResponseSchema = paidResponseSchema.extend({ tasks: z.array(z.object({
|
|
21065
|
-
...taskFields,
|
|
21121
|
+
const dataForSeoRankedKeywordsResponseSchema = paidResponseSchema$1.extend({ tasks: z.array(z.object({
|
|
21122
|
+
...taskFields$1,
|
|
21066
21123
|
result: z.array(rankedKeywordsResultSchema).max(10).nullable().optional()
|
|
21067
21124
|
}).passthrough()).max(10) });
|
|
21068
21125
|
const rankingPageItemSchema = z.object({
|
|
@@ -21075,8 +21132,8 @@ const rankingPagesResultSchema = z.object({
|
|
|
21075
21132
|
items_count: z.number().int().nonnegative().optional(),
|
|
21076
21133
|
items: z.array(rankingPageItemSchema).max(1e3).nullable().optional()
|
|
21077
21134
|
}).passthrough();
|
|
21078
|
-
const dataForSeoRankingPagesResponseSchema = paidResponseSchema.extend({ tasks: z.array(z.object({
|
|
21079
|
-
...taskFields,
|
|
21135
|
+
const dataForSeoRankingPagesResponseSchema = paidResponseSchema$1.extend({ tasks: z.array(z.object({
|
|
21136
|
+
...taskFields$1,
|
|
21080
21137
|
result: z.array(rankingPagesResultSchema).max(10).nullable().optional()
|
|
21081
21138
|
}).passthrough()).max(10) });
|
|
21082
21139
|
const competitorItemSchema = z.object({
|
|
@@ -21084,9 +21141,9 @@ const competitorItemSchema = z.object({
|
|
|
21084
21141
|
avg_position: nullableNumber,
|
|
21085
21142
|
median_position: nullableNumber,
|
|
21086
21143
|
etv: nullableNonnegativeNumber,
|
|
21087
|
-
keywords_count: nullableCount,
|
|
21144
|
+
keywords_count: nullableCount$1,
|
|
21088
21145
|
visibility: nullableNonnegativeNumber,
|
|
21089
|
-
relevant_serp_items: nullableCount,
|
|
21146
|
+
relevant_serp_items: nullableCount$1,
|
|
21090
21147
|
keywords_positions: z.record(z.string().trim().min(1).max(80), z.array(z.number().int().positive()).max(100)).nullable().optional()
|
|
21091
21148
|
}).passthrough();
|
|
21092
21149
|
const competitorsResultSchema = z.object({
|
|
@@ -21095,8 +21152,8 @@ const competitorsResultSchema = z.object({
|
|
|
21095
21152
|
items_count: z.number().int().nonnegative().optional(),
|
|
21096
21153
|
items: z.array(competitorItemSchema).max(1e3).nullable().optional()
|
|
21097
21154
|
}).passthrough();
|
|
21098
|
-
const dataForSeoSerpCompetitorsResponseSchema = paidResponseSchema.extend({ tasks: z.array(z.object({
|
|
21099
|
-
...taskFields,
|
|
21155
|
+
const dataForSeoSerpCompetitorsResponseSchema = paidResponseSchema$1.extend({ tasks: z.array(z.object({
|
|
21156
|
+
...taskFields$1,
|
|
21100
21157
|
result: z.array(competitorsResultSchema).max(10).nullable().optional()
|
|
21101
21158
|
}).passthrough()).max(10) });
|
|
21102
21159
|
const DOMAIN_OVERVIEW_PATH = "v3/dataforseo_labs/google/domain_rank_overview/live";
|
|
@@ -21286,6 +21343,209 @@ function serpCompetitorsPaidRequest(input, ttlMs) {
|
|
|
21286
21343
|
};
|
|
21287
21344
|
}
|
|
21288
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
|
|
21289
21549
|
//#region packages/core/dist/providers/dataforseo/paid-request.js
|
|
21290
21550
|
function usdToMicros(value) {
|
|
21291
21551
|
if (value === void 0) return null;
|
|
@@ -21809,41 +22069,6 @@ const MAX_KEYWORD_CHARACTERS = 80;
|
|
|
21809
22069
|
const MAX_KEYWORD_WORDS = 10;
|
|
21810
22070
|
const MAX_DISCOVERY_SEEDS$1 = 5;
|
|
21811
22071
|
const MAX_DISCOVERY_ROWS$1 = 100;
|
|
21812
|
-
function unitPrice(components) {
|
|
21813
|
-
const prices = components ?? [];
|
|
21814
|
-
const total = (type) => {
|
|
21815
|
-
const matching = prices.filter((item) => item.cost_type === type);
|
|
21816
|
-
return matching.length ? matching.reduce((sum, item) => sum + Math.round(item.cost * 1e6), 0) : prices.length ? 0 : null;
|
|
21817
|
-
};
|
|
21818
|
-
return {
|
|
21819
|
-
perRequestMicros: total("per_request"),
|
|
21820
|
-
perResultMicros: total("per_result")
|
|
21821
|
-
};
|
|
21822
|
-
}
|
|
21823
|
-
function keywordOverviewPrice(account) {
|
|
21824
|
-
return unitPrice(account.price?.dataforseo_labs?.keyword_overview?.live?.priority_normal);
|
|
21825
|
-
}
|
|
21826
|
-
function keywordDiscoveryPrices(account) {
|
|
21827
|
-
return {
|
|
21828
|
-
ideas: unitPrice(account.price?.dataforseo_labs?.keyword_ideas?.live?.priority_normal),
|
|
21829
|
-
related: unitPrice(account.price?.dataforseo_labs?.related_keywords?.live?.priority_normal),
|
|
21830
|
-
suggestions: unitPrice(account.price?.dataforseo_labs?.keyword_suggestions?.live?.priority_normal)
|
|
21831
|
-
};
|
|
21832
|
-
}
|
|
21833
|
-
function domainResearchPrices(account) {
|
|
21834
|
-
return {
|
|
21835
|
-
domainOverview: unitPrice(account.price?.dataforseo_labs?.domain_rank_overview?.live?.priority_normal),
|
|
21836
|
-
rankedKeywords: unitPrice(account.price?.dataforseo_labs?.ranked_keywords?.live?.priority_normal),
|
|
21837
|
-
rankingPages: unitPrice(account.price?.dataforseo_labs?.relevant_pages?.live?.priority_normal),
|
|
21838
|
-
serpCompetitors: unitPrice(account.price?.dataforseo_labs?.serp_competitors?.live?.priority_normal)
|
|
21839
|
-
};
|
|
21840
|
-
}
|
|
21841
|
-
function serpLiveAdvancedPrice(account) {
|
|
21842
|
-
return unitPrice(account.price?.serp?.live?.advanced?.priority_normal);
|
|
21843
|
-
}
|
|
21844
|
-
function serpTaskPostPrice(account) {
|
|
21845
|
-
return unitPrice(account.price?.serp?.task_post?.priority_normal);
|
|
21846
|
-
}
|
|
21847
22072
|
function responseTaskIds(response) {
|
|
21848
22073
|
return [...new Set(response.tasks.flatMap((task) => task.id ? [task.id] : []))].sort((left, right) => left < right ? -1 : left > right ? 1 : 0);
|
|
21849
22074
|
}
|
|
@@ -21863,6 +22088,7 @@ var DataForSeoClient = class {
|
|
|
21863
22088
|
serpTtlMs;
|
|
21864
22089
|
accountPricingTtlMs;
|
|
21865
22090
|
domainResearchTtlMs;
|
|
22091
|
+
linkTtlMs;
|
|
21866
22092
|
spendLimits;
|
|
21867
22093
|
accountPricing;
|
|
21868
22094
|
accountPricingRequest;
|
|
@@ -21879,6 +22105,7 @@ var DataForSeoClient = class {
|
|
|
21879
22105
|
this.serpTtlMs = options.serpTtlMs ?? DEFAULT_SERP_TTL_MS;
|
|
21880
22106
|
this.accountPricingTtlMs = options.accountPricingTtlMs ?? DEFAULT_ACCOUNT_PRICING_TTL_MS;
|
|
21881
22107
|
this.domainResearchTtlMs = options.domainResearchTtlMs ?? 6048e5;
|
|
22108
|
+
this.linkTtlMs = options.linkTtlMs ?? 864e5;
|
|
21882
22109
|
this.spendLimits = options.spendLimits;
|
|
21883
22110
|
}
|
|
21884
22111
|
async getCredentials(operation) {
|
|
@@ -21961,11 +22188,7 @@ var DataForSeoClient = class {
|
|
|
21961
22188
|
accountDailySpendMicros: dataForSeoUsdToMicros(account.money?.statistics?.day?.total),
|
|
21962
22189
|
accountDailySpendPeriod: account.money?.statistics?.day?.value ?? null,
|
|
21963
22190
|
accountDailyLimitMicros: dataForSeoUsdToMicros(account.money?.limits?.day?.total),
|
|
21964
|
-
|
|
21965
|
-
keywordDiscoveryPrices: keywordDiscoveryPrices(account),
|
|
21966
|
-
domainResearchPrices: domainResearchPrices(account),
|
|
21967
|
-
serpLiveAdvancedPrice: serpLiveAdvancedPrice(account),
|
|
21968
|
-
serpTaskPostPrice: serpTaskPostPrice(account),
|
|
22191
|
+
...dataForSeoAccountPrices(account),
|
|
21969
22192
|
backlinksSubscriptionExpiresAt: account.backlinks_subscription_expiry_date ?? null,
|
|
21970
22193
|
aiMentionsSubscriptionExpiresAt: account.llm_mentions_subscription_expiry_date ?? null,
|
|
21971
22194
|
apiVersion: response.version ?? null,
|
|
@@ -22028,6 +22251,15 @@ var DataForSeoClient = class {
|
|
|
22028
22251
|
async serpCompetitors(input) {
|
|
22029
22252
|
return this.paidPost(serpCompetitorsPaidRequest(input, this.domainResearchTtlMs));
|
|
22030
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
|
+
}
|
|
22031
22263
|
async keywordOverview(input) {
|
|
22032
22264
|
const keywords = input.keywords.map((keyword) => keyword.trim());
|
|
22033
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({
|
|
@@ -22371,20 +22603,20 @@ function compareCodepoints$3(left, right) {
|
|
|
22371
22603
|
function normalizedKeyword$2(keyword) {
|
|
22372
22604
|
return keyword.trim().replace(/\s+/gu, " ").toLowerCase();
|
|
22373
22605
|
}
|
|
22374
|
-
function missing$
|
|
22606
|
+
function missing$2(field) {
|
|
22375
22607
|
return unavailableValue("missing", `DataForSEO omitted ${field}.`);
|
|
22376
22608
|
}
|
|
22377
22609
|
function numericValue(values, field, valid) {
|
|
22378
22610
|
const present = values.filter((value) => value !== null && value !== void 0);
|
|
22379
|
-
if (present.length === 0) return missing$
|
|
22611
|
+
if (present.length === 0) return missing$2(field);
|
|
22380
22612
|
const numbers = present.filter((value) => typeof value === "number" && valid(value));
|
|
22381
22613
|
if (numbers.length !== present.length) return unavailableValue("invalid", `DataForSEO returned an invalid ${field}.`);
|
|
22382
22614
|
const unique = [...new Set(numbers)];
|
|
22383
22615
|
return unique.length === 1 ? observedValue(unique[0]) : unavailableValue("invalid", `DataForSEO returned conflicting ${field} values.`);
|
|
22384
22616
|
}
|
|
22385
|
-
function stringValue(values, field, normalize) {
|
|
22617
|
+
function stringValue$1(values, field, normalize) {
|
|
22386
22618
|
const present = values.filter((value) => value !== null && value !== void 0);
|
|
22387
|
-
if (present.length === 0) return missing$
|
|
22619
|
+
if (present.length === 0) return missing$2(field);
|
|
22388
22620
|
const strings = present.flatMap((value) => {
|
|
22389
22621
|
if (typeof value !== "string") return [];
|
|
22390
22622
|
const normalized = normalize(value);
|
|
@@ -22396,7 +22628,7 @@ function stringValue(values, field, normalize) {
|
|
|
22396
22628
|
}
|
|
22397
22629
|
function monthlySearchesValue(rows) {
|
|
22398
22630
|
const histories = rows.map((row) => row.keyword_info?.monthly_searches).filter((value) => value !== null && value !== void 0);
|
|
22399
|
-
if (histories.length === 0) return missing$
|
|
22631
|
+
if (histories.length === 0) return missing$2("monthlySearches");
|
|
22400
22632
|
const byMonth = /* @__PURE__ */ new Map();
|
|
22401
22633
|
let invalid = false;
|
|
22402
22634
|
for (const history of histories) for (const item of history) {
|
|
@@ -22429,27 +22661,27 @@ function resultCountValues(rows) {
|
|
|
22429
22661
|
function metricForKeyword(keyword, rows) {
|
|
22430
22662
|
if (rows.length === 0) return {
|
|
22431
22663
|
keyword,
|
|
22432
|
-
monthlySearchVolume: missing$
|
|
22433
|
-
monthlySearches: missing$
|
|
22434
|
-
searchVolumeUpdatedAt: missing$
|
|
22435
|
-
cpcUsd: missing$
|
|
22436
|
-
paidCompetition: missing$
|
|
22437
|
-
keywordDifficulty: missing$
|
|
22438
|
-
intent: missing$
|
|
22439
|
-
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")
|
|
22440
22672
|
};
|
|
22441
22673
|
return {
|
|
22442
22674
|
keyword,
|
|
22443
22675
|
monthlySearchVolume: numericValue(rows.map((row) => row.keyword_info?.search_volume), "monthlySearchVolume", (value) => Number.isSafeInteger(value) && value >= 0),
|
|
22444
22676
|
monthlySearches: monthlySearchesValue(rows),
|
|
22445
|
-
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) => {
|
|
22446
22678
|
const timestamp = Date.parse(value);
|
|
22447
22679
|
return Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : null;
|
|
22448
22680
|
}),
|
|
22449
22681
|
cpcUsd: numericValue(rows.map((row) => row.keyword_info?.cpc), "cpcUsd", (value) => Number.isFinite(value) && value >= 0),
|
|
22450
22682
|
paidCompetition: numericValue(rows.map((row) => row.keyword_info?.competition), "paidCompetition", (value) => Number.isFinite(value) && value >= 0 && value <= 1),
|
|
22451
22683
|
keywordDifficulty: numericValue(rows.map((row) => row.keyword_properties?.keyword_difficulty), "keywordDifficulty", (value) => Number.isFinite(value) && value >= 0 && value <= 100),
|
|
22452
|
-
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),
|
|
22453
22685
|
resultCount: numericValue(resultCountValues(rows), "resultCount", (value) => Number.isSafeInteger(value) && value >= 0)
|
|
22454
22686
|
};
|
|
22455
22687
|
}
|
|
@@ -22502,21 +22734,21 @@ const COMPETITOR_RESULT_TYPES = /* @__PURE__ */ new Set([
|
|
|
22502
22734
|
"featured_snippet",
|
|
22503
22735
|
"local_pack"
|
|
22504
22736
|
]);
|
|
22505
|
-
function missing(field) {
|
|
22737
|
+
function missing$1(field) {
|
|
22506
22738
|
return unavailableValue("missing", `DataForSEO omitted ${field}.`);
|
|
22507
22739
|
}
|
|
22508
|
-
function numberValue(value, field) {
|
|
22509
|
-
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);
|
|
22510
22742
|
}
|
|
22511
22743
|
function countValue(value, field) {
|
|
22512
|
-
return numberValue(value, field);
|
|
22744
|
+
return numberValue$1(value, field);
|
|
22513
22745
|
}
|
|
22514
22746
|
function sumBuckets(metrics, fields, label) {
|
|
22515
22747
|
const values = fields.map((field) => metrics[field]);
|
|
22516
|
-
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));
|
|
22517
22749
|
}
|
|
22518
22750
|
function rankingDistribution(metrics) {
|
|
22519
|
-
if (!metrics) return missing("organic ranking distribution");
|
|
22751
|
+
if (!metrics) return missing$1("organic ranking distribution");
|
|
22520
22752
|
const buckets = {
|
|
22521
22753
|
first: sumBuckets(metrics, ["pos_1"], "position 1 rankings"),
|
|
22522
22754
|
top3: sumBuckets(metrics, ["pos_1", "pos_2_3"], "top 3 rankings"),
|
|
@@ -22567,9 +22799,9 @@ function rankingDistribution(metrics) {
|
|
|
22567
22799
|
}
|
|
22568
22800
|
function organicFootprint(metrics) {
|
|
22569
22801
|
return {
|
|
22570
|
-
estimatedMonthlyTraffic: numberValue(metrics?.etv, "estimated organic monthly traffic"),
|
|
22802
|
+
estimatedMonthlyTraffic: numberValue$1(metrics?.etv, "estimated organic monthly traffic"),
|
|
22571
22803
|
rankedKeywords: countValue(metrics?.count, "ranked organic keywords"),
|
|
22572
|
-
estimatedMonthlyTrafficCostUsd: numberValue(metrics?.estimated_paid_traffic_cost, "estimated organic traffic cost"),
|
|
22804
|
+
estimatedMonthlyTrafficCostUsd: numberValue$1(metrics?.estimated_paid_traffic_cost, "estimated organic traffic cost"),
|
|
22573
22805
|
rankings: rankingDistribution(metrics),
|
|
22574
22806
|
newRankings: countValue(metrics?.is_new, "new rankings"),
|
|
22575
22807
|
improvedRankings: countValue(metrics?.is_up, "improved rankings"),
|
|
@@ -22624,7 +22856,7 @@ function researchMarket(input, operation) {
|
|
|
22624
22856
|
});
|
|
22625
22857
|
return parsed;
|
|
22626
22858
|
}
|
|
22627
|
-
function rowLimit(limit, offset = 0) {
|
|
22859
|
+
function rowLimit$1(limit, offset = 0) {
|
|
22628
22860
|
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1e3) throw new ProviderError({
|
|
22629
22861
|
provider: "dataforseo",
|
|
22630
22862
|
operation: "domain-research",
|
|
@@ -22648,7 +22880,7 @@ function resultTypes(input, allowed) {
|
|
|
22648
22880
|
});
|
|
22649
22881
|
return normalized;
|
|
22650
22882
|
}
|
|
22651
|
-
function safeUrl$
|
|
22883
|
+
function safeUrl$2(value) {
|
|
22652
22884
|
if (!value) return null;
|
|
22653
22885
|
try {
|
|
22654
22886
|
const url = new URL(value);
|
|
@@ -22668,7 +22900,7 @@ function dedupeBy(rows, key) {
|
|
|
22668
22900
|
}
|
|
22669
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]);
|
|
22670
22902
|
}
|
|
22671
|
-
function observedNumber(value) {
|
|
22903
|
+
function observedNumber$1(value) {
|
|
22672
22904
|
return value.state === "observed" ? value.value : -1;
|
|
22673
22905
|
}
|
|
22674
22906
|
function duplicateWarning(available, retained, label) {
|
|
@@ -22679,7 +22911,7 @@ function duplicateWarning(available, retained, label) {
|
|
|
22679
22911
|
message: `${duplicates} duplicate ${label} row${duplicates === 1 ? "" : "s"} were collapsed deterministically.`
|
|
22680
22912
|
}] : [];
|
|
22681
22913
|
}
|
|
22682
|
-
function totalRows(values) {
|
|
22914
|
+
function totalRows$1(values) {
|
|
22683
22915
|
const present = values.filter((value) => value !== null && value !== void 0);
|
|
22684
22916
|
return present.length ? Math.max(...present) : null;
|
|
22685
22917
|
}
|
|
@@ -22695,7 +22927,7 @@ function coverage(input) {
|
|
|
22695
22927
|
nextCursor: hasMore ? String(input.offset + input.returnedRows) : null
|
|
22696
22928
|
};
|
|
22697
22929
|
}
|
|
22698
|
-
function requestContext(reportId, supplied) {
|
|
22930
|
+
function requestContext$1(reportId, supplied) {
|
|
22699
22931
|
return supplied ?? {
|
|
22700
22932
|
reportId,
|
|
22701
22933
|
reportRunId: randomUUID()
|
|
@@ -22744,7 +22976,7 @@ async function dataForSeoDomainOverview(client, input) {
|
|
|
22744
22976
|
languageCode: market.languageCode.split("-")[0],
|
|
22745
22977
|
...location,
|
|
22746
22978
|
refresh: input.refresh,
|
|
22747
|
-
context: requestContext("domain-overview", input.context)
|
|
22979
|
+
context: requestContext$1("domain-overview", input.context)
|
|
22748
22980
|
});
|
|
22749
22981
|
const results = snapshot.response.tasks.flatMap((task) => task.result ?? []);
|
|
22750
22982
|
const matchingResult = results.find((row) => !row.target || domain(row.target) === target);
|
|
@@ -22836,7 +23068,7 @@ async function dataForSeoRankedKeywords(client, input) {
|
|
|
22836
23068
|
const market = researchMarket(input.market, "ranked-keywords");
|
|
22837
23069
|
const target = rankedTarget(input.target);
|
|
22838
23070
|
const offset = input.offset ?? 0;
|
|
22839
|
-
rowLimit(input.limit, offset);
|
|
23071
|
+
rowLimit$1(input.limit, offset);
|
|
22840
23072
|
const types = resultTypes(input.resultTypes, RANKED_RESULT_TYPES);
|
|
22841
23073
|
const excluded = excludedTerms(input);
|
|
22842
23074
|
const requestedFilters = filters$1(input, excluded);
|
|
@@ -22856,7 +23088,7 @@ async function dataForSeoRankedKeywords(client, input) {
|
|
|
22856
23088
|
limit: input.limit,
|
|
22857
23089
|
offset,
|
|
22858
23090
|
refresh: input.refresh,
|
|
22859
|
-
context: requestContext("ranked-keywords", input.context)
|
|
23091
|
+
context: requestContext$1("ranked-keywords", input.context)
|
|
22860
23092
|
});
|
|
22861
23093
|
const responseResults = snapshot.response.tasks.flatMap((task) => task.result ?? []);
|
|
22862
23094
|
const rawRows = responseResults.flatMap((result) => result.items ?? []);
|
|
@@ -22865,7 +23097,7 @@ async function dataForSeoRankedKeywords(client, input) {
|
|
|
22865
23097
|
const keywordData = row.keyword_data;
|
|
22866
23098
|
const serp = row.ranked_serp_element?.serp_item;
|
|
22867
23099
|
const keyword = keywordData?.keyword ? normalizedKeyword$2(keywordData.keyword) : "";
|
|
22868
|
-
const url = safeUrl$
|
|
23100
|
+
const url = safeUrl$2(serp?.url);
|
|
22869
23101
|
if (!keywordData || !keyword || !url || !serp?.rank_group || !serp.rank_absolute || !serp.type) {
|
|
22870
23102
|
invalidRows += 1;
|
|
22871
23103
|
return [];
|
|
@@ -22876,11 +23108,11 @@ async function dataForSeoRankedKeywords(client, input) {
|
|
|
22876
23108
|
rankGroup: serp.rank_group,
|
|
22877
23109
|
rankAbsolute: serp.rank_absolute,
|
|
22878
23110
|
resultType: serp.type,
|
|
22879
|
-
estimatedMonthlyTraffic: numberValue(serp.etv, "estimated keyword traffic")
|
|
23111
|
+
estimatedMonthlyTraffic: numberValue$1(serp.etv, "estimated keyword traffic")
|
|
22880
23112
|
}];
|
|
22881
23113
|
});
|
|
22882
|
-
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));
|
|
22883
|
-
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));
|
|
22884
23116
|
return domainEvidence({
|
|
22885
23117
|
capability: "ranked-keywords",
|
|
22886
23118
|
data: {
|
|
@@ -22953,7 +23185,7 @@ async function dataForSeoRankingPages(client, input) {
|
|
|
22953
23185
|
const market = researchMarket(input.market, "ranking-pages");
|
|
22954
23186
|
const target = domain(input.domain);
|
|
22955
23187
|
const offset = input.offset ?? 0;
|
|
22956
|
-
rowLimit(input.limit, offset);
|
|
23188
|
+
rowLimit$1(input.limit, offset);
|
|
22957
23189
|
const requestedFilters = filters(input);
|
|
22958
23190
|
const orderBy = ["metrics.organic.etv,desc", "page_address,asc"];
|
|
22959
23191
|
const snapshot = await client.rankingPages({
|
|
@@ -22965,13 +23197,13 @@ async function dataForSeoRankingPages(client, input) {
|
|
|
22965
23197
|
limit: input.limit,
|
|
22966
23198
|
offset,
|
|
22967
23199
|
refresh: input.refresh,
|
|
22968
|
-
context: requestContext("ranking-pages", input.context)
|
|
23200
|
+
context: requestContext$1("ranking-pages", input.context)
|
|
22969
23201
|
});
|
|
22970
23202
|
const responseResults = snapshot.response.tasks.flatMap((task) => task.result ?? []);
|
|
22971
23203
|
const rawRows = responseResults.flatMap((result) => result.items ?? []);
|
|
22972
23204
|
let invalidRows = 0;
|
|
22973
23205
|
const mappedRows = rawRows.flatMap((row) => {
|
|
22974
|
-
const url = safeUrl$
|
|
23206
|
+
const url = safeUrl$2(row.page_address);
|
|
22975
23207
|
if (!url) {
|
|
22976
23208
|
invalidRows += 1;
|
|
22977
23209
|
return [];
|
|
@@ -22981,8 +23213,8 @@ async function dataForSeoRankingPages(client, input) {
|
|
|
22981
23213
|
organic: organicFootprint(row.metrics?.organic)
|
|
22982
23214
|
}];
|
|
22983
23215
|
});
|
|
22984
|
-
const rows = dedupeBy(mappedRows, (row) => row.url).sort((left, right) => observedNumber(right.organic.estimatedMonthlyTraffic) - observedNumber(left.organic.estimatedMonthlyTraffic) || compareCodepoints$3(left.url, right.url));
|
|
22985
|
-
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));
|
|
22986
23218
|
return domainEvidence({
|
|
22987
23219
|
capability: "relevant-pages",
|
|
22988
23220
|
data: {
|
|
@@ -23027,7 +23259,7 @@ function keywords(input) {
|
|
|
23027
23259
|
async function dataForSeoSerpCompetitors(client, input) {
|
|
23028
23260
|
const market = researchMarket(input.market, "serp-competitors");
|
|
23029
23261
|
const offset = input.offset ?? 0;
|
|
23030
|
-
rowLimit(input.limit, offset);
|
|
23262
|
+
rowLimit$1(input.limit, offset);
|
|
23031
23263
|
const requestedKeywords = keywords(input.keywords);
|
|
23032
23264
|
const types = resultTypes(input.resultTypes, COMPETITOR_RESULT_TYPES);
|
|
23033
23265
|
const orderBy = [
|
|
@@ -23045,7 +23277,7 @@ async function dataForSeoSerpCompetitors(client, input) {
|
|
|
23045
23277
|
limit: input.limit,
|
|
23046
23278
|
offset,
|
|
23047
23279
|
refresh: input.refresh,
|
|
23048
|
-
context: requestContext("serp-competitors", input.context)
|
|
23280
|
+
context: requestContext$1("serp-competitors", input.context)
|
|
23049
23281
|
});
|
|
23050
23282
|
const responseResults = snapshot.response.tasks.flatMap((task) => task.result ?? []);
|
|
23051
23283
|
const rawRows = responseResults.flatMap((result) => result.items ?? []);
|
|
@@ -23068,16 +23300,16 @@ async function dataForSeoSerpCompetitors(client, input) {
|
|
|
23068
23300
|
return [{
|
|
23069
23301
|
domain: normalizedDomain,
|
|
23070
23302
|
matchedKeywords: row.keywords_count,
|
|
23071
|
-
averagePosition: numberValue(row.avg_position, "average position"),
|
|
23072
|
-
medianPosition: numberValue(row.median_position, "median position"),
|
|
23073
|
-
visibility: numberValue(row.visibility, "visibility"),
|
|
23074
|
-
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"),
|
|
23075
23307
|
relevantResults: countValue(row.relevant_serp_items, "relevant result count"),
|
|
23076
23308
|
keywordPositions
|
|
23077
23309
|
}];
|
|
23078
23310
|
});
|
|
23079
|
-
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));
|
|
23080
|
-
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));
|
|
23081
23313
|
return domainEvidence({
|
|
23082
23314
|
capability: "serp-competitors",
|
|
23083
23315
|
data: {
|
|
@@ -23992,7 +24224,7 @@ function analyzeCompetitorGap(acquisition) {
|
|
|
23992
24224
|
}
|
|
23993
24225
|
//#endregion
|
|
23994
24226
|
//#region packages/core/dist/analyze/domain-research/gap-report.js
|
|
23995
|
-
const MAX_FINDINGS = 10;
|
|
24227
|
+
const MAX_FINDINGS$2 = 10;
|
|
23996
24228
|
const MAX_SOURCE_BRIEFS = 5;
|
|
23997
24229
|
function count(candidates, classification) {
|
|
23998
24230
|
return candidates.filter((item) => item.classification === classification).length;
|
|
@@ -24007,7 +24239,7 @@ function dataStatus(input) {
|
|
|
24007
24239
|
function findings$2(input) {
|
|
24008
24240
|
const result = [];
|
|
24009
24241
|
for (const [index, candidate] of input.candidates.entries()) {
|
|
24010
|
-
if (result.length >= MAX_FINDINGS) break;
|
|
24242
|
+
if (result.length >= MAX_FINDINGS$2) break;
|
|
24011
24243
|
if (candidate.classification === "relevant-gap-candidate") result.push({
|
|
24012
24244
|
code: "relevant-competitor-gap",
|
|
24013
24245
|
evidenceRefs: [`candidates[${index}]`, ...candidate.evidenceRefs],
|
|
@@ -24022,7 +24254,7 @@ function findings$2(input) {
|
|
|
24022
24254
|
});
|
|
24023
24255
|
}
|
|
24024
24256
|
for (const [index, pattern] of input.patterns.entries()) {
|
|
24025
|
-
if (result.length >= MAX_FINDINGS) break;
|
|
24257
|
+
if (result.length >= MAX_FINDINGS$2) break;
|
|
24026
24258
|
result.push({
|
|
24027
24259
|
code: "competitor-template-pattern",
|
|
24028
24260
|
evidenceRefs: [`repeatedCompetitorPatterns[${index}]`, ...pattern.evidenceRefs],
|
|
@@ -25069,7 +25301,7 @@ function measurementWindow(input) {
|
|
|
25069
25301
|
}
|
|
25070
25302
|
//#endregion
|
|
25071
25303
|
//#region packages/core/dist/analyze/experiments/measure-change.js
|
|
25072
|
-
const defaultDependencies$
|
|
25304
|
+
const defaultDependencies$6 = {
|
|
25073
25305
|
searchAnalytics: querySearchAnalytics,
|
|
25074
25306
|
googleAnalyticsReport: runGa4Report,
|
|
25075
25307
|
contentGroup: getContentGroup,
|
|
@@ -25087,7 +25319,7 @@ function counterfactualDelta(input) {
|
|
|
25087
25319
|
const expectedAfter = input.treatmentBefore * (input.controlAfter / input.controlBefore);
|
|
25088
25320
|
return fixed(input.treatmentAfter - expectedAfter);
|
|
25089
25321
|
}
|
|
25090
|
-
async function measureChange(input, dependencies = defaultDependencies$
|
|
25322
|
+
async function measureChange(input, dependencies = defaultDependencies$6) {
|
|
25091
25323
|
const now = dependencies.now();
|
|
25092
25324
|
const change = (input.id ? getChange(input.id) : void 0) ?? {
|
|
25093
25325
|
id: "adhoc",
|
|
@@ -25810,14 +26042,14 @@ async function verifyInternalLinkCandidate(input) {
|
|
|
25810
26042
|
}
|
|
25811
26043
|
//#endregion
|
|
25812
26044
|
//#region packages/core/dist/analyze/internal-links.js
|
|
25813
|
-
const MAX_GSC_ROWS$
|
|
25814
|
-
const defaultDependencies$
|
|
26045
|
+
const MAX_GSC_ROWS$2 = 1e5;
|
|
26046
|
+
const defaultDependencies$5 = {
|
|
25815
26047
|
searchAnalytics: querySearchAnalytics,
|
|
25816
26048
|
fetch: fetchPage,
|
|
25817
26049
|
extract: extractPage,
|
|
25818
26050
|
now: () => /* @__PURE__ */ new Date()
|
|
25819
26051
|
};
|
|
25820
|
-
async function internalLinksReport(input, dependencies = defaultDependencies$
|
|
26052
|
+
async function internalLinksReport(input, dependencies = defaultDependencies$5) {
|
|
25821
26053
|
const targetPage = new URL(assertUrlMatchesGscProperty(input.site, input.targetUrl));
|
|
25822
26054
|
targetPage.hash = "";
|
|
25823
26055
|
const targetUrl = targetPage.toString();
|
|
@@ -25866,7 +26098,7 @@ async function internalLinksReport(input, dependencies = defaultDependencies$4)
|
|
|
25866
26098
|
dimensions: ["query", "page"],
|
|
25867
26099
|
type: "web",
|
|
25868
26100
|
dataState: "final",
|
|
25869
|
-
maxRows: MAX_GSC_ROWS$
|
|
26101
|
+
maxRows: MAX_GSC_ROWS$2,
|
|
25870
26102
|
dimensionFilterGroups: [{
|
|
25871
26103
|
groupType: "and",
|
|
25872
26104
|
filters: [{
|
|
@@ -25898,7 +26130,7 @@ async function internalLinksReport(input, dependencies = defaultDependencies$4)
|
|
|
25898
26130
|
dimensions: ["query", "page"],
|
|
25899
26131
|
type: "web",
|
|
25900
26132
|
dataState: "final",
|
|
25901
|
-
maxRows: MAX_GSC_ROWS$
|
|
26133
|
+
maxRows: MAX_GSC_ROWS$2
|
|
25902
26134
|
}, { refresh: input.refresh }) : {
|
|
25903
26135
|
rows: [],
|
|
25904
26136
|
calls: 0,
|
|
@@ -25943,8 +26175,8 @@ async function internalLinksReport(input, dependencies = defaultDependencies$4)
|
|
|
25943
26175
|
else checkedSources += 1;
|
|
25944
26176
|
}
|
|
25945
26177
|
const uncheckedCandidates = Math.max(0, analysis.candidates.length - attemptedSources);
|
|
25946
|
-
const targetTruncated = targetRequests.some(({ result }) => result.rowsFetched >= MAX_GSC_ROWS$
|
|
25947
|
-
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;
|
|
25948
26180
|
const invalidRows = analysis.selection.targetInvalidRows + analysis.selection.sourceInvalidRows;
|
|
25949
26181
|
const structuredWarnings = uniqueInternalLinksWarnings(warnings);
|
|
25950
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";
|
|
@@ -25985,18 +26217,18 @@ async function internalLinksReport(input, dependencies = defaultDependencies$4)
|
|
|
25985
26217
|
pageFilter: alias,
|
|
25986
26218
|
rowsFetched: result.rowsFetched,
|
|
25987
26219
|
calls: result.calls,
|
|
25988
|
-
possiblyTruncated: result.rowsFetched >= MAX_GSC_ROWS$
|
|
26220
|
+
possiblyTruncated: result.rowsFetched >= MAX_GSC_ROWS$2
|
|
25989
26221
|
})),
|
|
25990
26222
|
rowsFetched: targetRequests.reduce((sum, { result }) => sum + result.rowsFetched, 0),
|
|
25991
26223
|
calls: targetRequests.reduce((sum, { result }) => sum + result.calls, 0),
|
|
25992
|
-
maxRowsPerRequest: MAX_GSC_ROWS$
|
|
26224
|
+
maxRowsPerRequest: MAX_GSC_ROWS$2,
|
|
25993
26225
|
possiblyTruncated: targetTruncated
|
|
25994
26226
|
},
|
|
25995
26227
|
candidates: {
|
|
25996
26228
|
queried: queryCandidates,
|
|
25997
26229
|
rowsFetched: sourceResult.rowsFetched,
|
|
25998
26230
|
calls: sourceResult.calls,
|
|
25999
|
-
maxRows: MAX_GSC_ROWS$
|
|
26231
|
+
maxRows: MAX_GSC_ROWS$2,
|
|
26000
26232
|
possiblyTruncated: sourceTruncated
|
|
26001
26233
|
},
|
|
26002
26234
|
completeness: !queryCandidates ? "not-queried" : targetTruncated || sourceTruncated ? "possibly-truncated" : "retained-rows-only"
|
|
@@ -26015,7 +26247,7 @@ async function internalLinksReport(input, dependencies = defaultDependencies$4)
|
|
|
26015
26247
|
minImpressions,
|
|
26016
26248
|
resultLimit: limit,
|
|
26017
26249
|
checkLimit,
|
|
26018
|
-
maxGscRowsPerRequest: MAX_GSC_ROWS$
|
|
26250
|
+
maxGscRowsPerRequest: MAX_GSC_ROWS$2
|
|
26019
26251
|
},
|
|
26020
26252
|
target: {
|
|
26021
26253
|
requestedUrl: target.requestedUrl,
|
|
@@ -26952,8 +27184,8 @@ function analyzeSecondPageRows(input) {
|
|
|
26952
27184
|
}
|
|
26953
27185
|
//#endregion
|
|
26954
27186
|
//#region packages/core/dist/analyze/keyword-opportunities.js
|
|
26955
|
-
const DEFAULT_DAYS = 28;
|
|
26956
|
-
const MAX_DAYS = 548;
|
|
27187
|
+
const DEFAULT_DAYS$1 = 28;
|
|
27188
|
+
const MAX_DAYS$1 = 548;
|
|
26957
27189
|
const DEFAULT_REPORT_LIMIT = 10;
|
|
26958
27190
|
const MAX_REPORT_LIMIT = 25;
|
|
26959
27191
|
const DEFAULT_KEYWORD_LIMIT = 30;
|
|
@@ -27000,9 +27232,9 @@ function validateInput$2(input) {
|
|
|
27000
27232
|
site,
|
|
27001
27233
|
days: integerOption$1({
|
|
27002
27234
|
value: input.days,
|
|
27003
|
-
fallback: DEFAULT_DAYS,
|
|
27235
|
+
fallback: DEFAULT_DAYS$1,
|
|
27004
27236
|
minimum: 1,
|
|
27005
|
-
maximum: MAX_DAYS,
|
|
27237
|
+
maximum: MAX_DAYS$1,
|
|
27006
27238
|
label: "Days"
|
|
27007
27239
|
}),
|
|
27008
27240
|
limit: integerOption$1({
|
|
@@ -28537,7 +28769,7 @@ function analyzeIndexCoverageSignals(input) {
|
|
|
28537
28769
|
const MAX_SITEMAP_DOCUMENTS = 20;
|
|
28538
28770
|
const MAX_SEARCH_ROWS = 25e4;
|
|
28539
28771
|
const MAX_SITEMAP_URLS = 25e4;
|
|
28540
|
-
const defaultDependencies$
|
|
28772
|
+
const defaultDependencies$4 = {
|
|
28541
28773
|
loadCrawl: loadCrawlReport,
|
|
28542
28774
|
latestCrawl: latestCrawlReport,
|
|
28543
28775
|
searchAnalytics: querySearchAnalytics,
|
|
@@ -28569,7 +28801,7 @@ function sitemapCompleteness(input) {
|
|
|
28569
28801
|
if (input.inventoryTruncated) return "truncated";
|
|
28570
28802
|
return input.results.every((result) => result.dataStatus === "complete") ? "complete" : "partial";
|
|
28571
28803
|
}
|
|
28572
|
-
async function indexCoverageSignals(input, dependencies = defaultDependencies$
|
|
28804
|
+
async function indexCoverageSignals(input, dependencies = defaultDependencies$4) {
|
|
28573
28805
|
const days = integerOption$2({
|
|
28574
28806
|
value: input.days,
|
|
28575
28807
|
fallback: 90,
|
|
@@ -29052,7 +29284,7 @@ function pruneIndexWatchSnapshots(rootSite, url, database = getDb()) {
|
|
|
29052
29284
|
WHERE attempts.attempt_rank > ? AND latest_success.id IS NULL
|
|
29053
29285
|
)`).run(rootSite, url, rootSite, url, 20).changes;
|
|
29054
29286
|
}
|
|
29055
|
-
const defaultDependencies$
|
|
29287
|
+
const defaultDependencies$3 = {
|
|
29056
29288
|
inspectUrl,
|
|
29057
29289
|
now: () => /* @__PURE__ */ new Date(),
|
|
29058
29290
|
store: {
|
|
@@ -29097,7 +29329,7 @@ function isFatalBatchError(error) {
|
|
|
29097
29329
|
"AUTH_REQUIRED"
|
|
29098
29330
|
].includes(error.code);
|
|
29099
29331
|
}
|
|
29100
|
-
async function indexWatch(input, dependencies = defaultDependencies$
|
|
29332
|
+
async function indexWatch(input, dependencies = defaultDependencies$3) {
|
|
29101
29333
|
const languageCode = validateLanguageCode(input.languageCode);
|
|
29102
29334
|
const dailyLimit = integerOption$2({
|
|
29103
29335
|
value: input.dailyLimit,
|
|
@@ -31192,9 +31424,9 @@ function pseoSampleUrls(report) {
|
|
|
31192
31424
|
}
|
|
31193
31425
|
//#endregion
|
|
31194
31426
|
//#region packages/core/dist/analyze/pseo/audit.js
|
|
31195
|
-
const MAX_GSC_ROWS = 5e4;
|
|
31427
|
+
const MAX_GSC_ROWS$1 = 5e4;
|
|
31196
31428
|
const DEFAULT_SITEMAP_URLS = 5e4;
|
|
31197
|
-
const defaultDependencies$
|
|
31429
|
+
const defaultDependencies$2 = {
|
|
31198
31430
|
searchAnalytics: querySearchAnalytics,
|
|
31199
31431
|
fetchPage,
|
|
31200
31432
|
inspectUrl,
|
|
@@ -31396,7 +31628,7 @@ async function inspectSample(input) {
|
|
|
31396
31628
|
};
|
|
31397
31629
|
}
|
|
31398
31630
|
}
|
|
31399
|
-
async function acquirePseoAuditEvidence(input, dependencies = defaultDependencies$
|
|
31631
|
+
async function acquirePseoAuditEvidence(input, dependencies = defaultDependencies$2) {
|
|
31400
31632
|
const options = pseoAuditOptions(input);
|
|
31401
31633
|
const now = dependencies.now();
|
|
31402
31634
|
const generatedAt = now.toISOString();
|
|
@@ -31415,7 +31647,7 @@ async function acquirePseoAuditEvidence(input, dependencies = defaultDependencie
|
|
|
31415
31647
|
type: "web",
|
|
31416
31648
|
dataState: "final",
|
|
31417
31649
|
aggregationType: "auto",
|
|
31418
|
-
maxRows: MAX_GSC_ROWS
|
|
31650
|
+
maxRows: MAX_GSC_ROWS$1
|
|
31419
31651
|
};
|
|
31420
31652
|
input.progress?.(`Fetching ${options.days} days of GSC page and query/page rows`);
|
|
31421
31653
|
const [pageResponse, queryPageResponse] = await Promise.all([dependencies.searchAnalytics(input.site, {
|
|
@@ -31466,7 +31698,7 @@ function buildPseoAuditFromAcquisition(input, acquisition, evidence) {
|
|
|
31466
31698
|
minimumTemplateImpressions: options.minimumTemplateImpressions,
|
|
31467
31699
|
crawlSamplesPerTemplate: options.crawlSamples,
|
|
31468
31700
|
inspectionSamplesPerTemplate: options.inspectSamples,
|
|
31469
|
-
maxRowsPerRequest: MAX_GSC_ROWS,
|
|
31701
|
+
maxRowsPerRequest: MAX_GSC_ROWS$1,
|
|
31470
31702
|
pageRowsFetched: acquisition.pageRowsFetched,
|
|
31471
31703
|
queryPageRowsFetched: acquisition.queryPageRowsFetched,
|
|
31472
31704
|
sitemapsRequested: input.sitemaps?.length ?? 0,
|
|
@@ -31477,7 +31709,7 @@ function buildPseoAuditFromAcquisition(input, acquisition, evidence) {
|
|
|
31477
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."]
|
|
31478
31710
|
});
|
|
31479
31711
|
}
|
|
31480
|
-
async function pseoAuditReport(input, dependencies = defaultDependencies$
|
|
31712
|
+
async function pseoAuditReport(input, dependencies = defaultDependencies$2) {
|
|
31481
31713
|
const acquisition = await acquirePseoAuditEvidence(input, dependencies);
|
|
31482
31714
|
const sampleUrls = pseoSampleUrls(buildPseoAuditFromAcquisition(input, acquisition));
|
|
31483
31715
|
const topQueries = topQueryByUrl(acquisition.queryPageRows, input);
|
|
@@ -32319,7 +32551,7 @@ function pseoOpportunityVerdict(input) {
|
|
|
32319
32551
|
//#region packages/core/dist/providers/dataforseo/serp-snapshot.js
|
|
32320
32552
|
const MAX_SERP_DEPTH = 100;
|
|
32321
32553
|
const SERP_ENDPOINT = "v3/serp/google/organic/live/advanced";
|
|
32322
|
-
function safeUrl(value) {
|
|
32554
|
+
function safeUrl$1(value) {
|
|
32323
32555
|
if (!value) return null;
|
|
32324
32556
|
try {
|
|
32325
32557
|
const parsed = new URL(value);
|
|
@@ -32332,11 +32564,11 @@ function safeUrl(value) {
|
|
|
32332
32564
|
}
|
|
32333
32565
|
}
|
|
32334
32566
|
function validOrganic(item) {
|
|
32335
|
-
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;
|
|
32336
32568
|
}
|
|
32337
32569
|
function organicResult(item) {
|
|
32338
32570
|
if (!validOrganic(item)) return null;
|
|
32339
|
-
const url = safeUrl(item.url);
|
|
32571
|
+
const url = safeUrl$1(item.url);
|
|
32340
32572
|
if (!url) return null;
|
|
32341
32573
|
return {
|
|
32342
32574
|
rankGroup: item.rank_group,
|
|
@@ -32385,7 +32617,7 @@ function mapDataForSeoSerpSnapshot(input, snapshot, endpoint = SERP_ENDPOINT) {
|
|
|
32385
32617
|
field: "checkedAt",
|
|
32386
32618
|
message: "DataForSEO returned an invalid SERP observation time; the local response time was retained."
|
|
32387
32619
|
});
|
|
32388
|
-
const checkUrl = safeUrl(result.check_url);
|
|
32620
|
+
const checkUrl = safeUrl$1(result.check_url);
|
|
32389
32621
|
if (result.check_url && !checkUrl) warnings.push({
|
|
32390
32622
|
code: "invalid-serp-check-url",
|
|
32391
32623
|
field: "checkUrl",
|
|
@@ -34528,7 +34760,7 @@ function groupedMappings(evidence) {
|
|
|
34528
34760
|
//#endregion
|
|
34529
34761
|
//#region packages/core/dist/analyze/second-page.js
|
|
34530
34762
|
const MAX_SOURCE_ROWS = 1e5;
|
|
34531
|
-
const defaultDependencies = {
|
|
34763
|
+
const defaultDependencies$1 = {
|
|
34532
34764
|
searchAnalytics: querySearchAnalytics,
|
|
34533
34765
|
fetch: fetchPage,
|
|
34534
34766
|
extract: extractPage,
|
|
@@ -34605,7 +34837,7 @@ function reportRecommendations(items) {
|
|
|
34605
34837
|
"Treat the priority score as a triage heuristic, not an estimated traffic lift."
|
|
34606
34838
|
]);
|
|
34607
34839
|
}
|
|
34608
|
-
async function secondPage(input, dependencies = defaultDependencies) {
|
|
34840
|
+
async function secondPage(input, dependencies = defaultDependencies$1) {
|
|
34609
34841
|
const days = integerOption({
|
|
34610
34842
|
value: input.range,
|
|
34611
34843
|
fallback: 28,
|
|
@@ -36796,10 +37028,10 @@ const DEFAULT_TARGET_LIMIT = 20;
|
|
|
36796
37028
|
const MAX_TARGET_LIMIT = 50;
|
|
36797
37029
|
const DEFAULT_DETAIL_PAGES = 1;
|
|
36798
37030
|
const MAX_DETAIL_PAGES = 3;
|
|
36799
|
-
const DEFAULT_ROW_LIMIT$
|
|
36800
|
-
const MAX_ROW_LIMIT$
|
|
37031
|
+
const DEFAULT_ROW_LIMIT$3 = 500;
|
|
37032
|
+
const MAX_ROW_LIMIT$3 = 1e3;
|
|
36801
37033
|
const MAX_CONCURRENCY = 3;
|
|
36802
|
-
function integer(value, fallback, max, name) {
|
|
37034
|
+
function integer$1(value, fallback, max, name) {
|
|
36803
37035
|
const resolved = value ?? fallback;
|
|
36804
37036
|
if (!Number.isInteger(resolved) || resolved < 1 || resolved > max) throw new SeoError("INVALID_INPUT", `${name} must be between 1 and ${max}.`);
|
|
36805
37037
|
return resolved;
|
|
@@ -36860,9 +37092,9 @@ async function targetDetails(input) {
|
|
|
36860
37092
|
}
|
|
36861
37093
|
async function collectBingLinkEvidence(input) {
|
|
36862
37094
|
if (!input.site.trim()) throw new SeoError("INVALID_INPUT", "Pass a Bing Webmaster site URL.");
|
|
36863
|
-
const targetLimit = integer(input.targetLimit, DEFAULT_TARGET_LIMIT, MAX_TARGET_LIMIT, "Bing link target limit");
|
|
36864
|
-
const detailPages = integer(input.detailPagesPerTarget, DEFAULT_DETAIL_PAGES, MAX_DETAIL_PAGES, "Bing detail page limit");
|
|
36865
|
-
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");
|
|
36866
37098
|
const client = input.client ?? (await createBingWebmasterClient()).client;
|
|
36867
37099
|
const countRows = [];
|
|
36868
37100
|
let countPagesRequested = 0;
|
|
@@ -36983,6 +37215,884 @@ async function collectBingLinkEvidence(input) {
|
|
|
36983
37215
|
};
|
|
36984
37216
|
}
|
|
36985
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
|
|
36986
38096
|
//#region packages/core/dist/links/import.js
|
|
36987
38097
|
const DEFAULT_ROW_LIMIT$1 = 1e4;
|
|
36988
38098
|
const MAX_ROW_LIMIT$1 = 1e5;
|
|
@@ -37171,6 +38281,10 @@ async function importLinkEvidence(input) {
|
|
|
37171
38281
|
//#region packages/core/dist/links/report.js
|
|
37172
38282
|
const DEFAULT_OUTPUT_LIMIT = 100;
|
|
37173
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;
|
|
37174
38288
|
function boundedLimit(value) {
|
|
37175
38289
|
const limit = value ?? DEFAULT_OUTPUT_LIMIT;
|
|
37176
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}.`);
|
|
@@ -37182,7 +38296,7 @@ function targetCounts(rows) {
|
|
|
37182
38296
|
return [...counts].map(([targetUrl, observedLinks]) => ({
|
|
37183
38297
|
targetUrl,
|
|
37184
38298
|
observedLinks
|
|
37185
|
-
})).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));
|
|
37186
38300
|
}
|
|
37187
38301
|
function linkEvidenceReport(input) {
|
|
37188
38302
|
const limit = boundedLimit(input.limit);
|
|
@@ -37190,8 +38304,24 @@ function linkEvidenceReport(input) {
|
|
|
37190
38304
|
const domains = new Set(input.evidence.rows.map((row) => row.sourceDomain));
|
|
37191
38305
|
const observedTargets = targetCounts(input.evidence.rows);
|
|
37192
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;
|
|
37193
38323
|
return {
|
|
37194
|
-
schemaVersion:
|
|
38324
|
+
schemaVersion: 2,
|
|
37195
38325
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
37196
38326
|
dataStatus: input.evidence.provenance.completeness === "complete" ? "complete" : "partial",
|
|
37197
38327
|
summary: {
|
|
@@ -37200,6 +38330,8 @@ function linkEvidenceReport(input) {
|
|
|
37200
38330
|
targetPages: new Set(input.evidence.rows.map((row) => row.targetUrl)).size,
|
|
37201
38331
|
providerTargetPages: counts.length
|
|
37202
38332
|
},
|
|
38333
|
+
providerSummary: providerEvidence?.summary.data ?? null,
|
|
38334
|
+
providerEvidence,
|
|
37203
38335
|
provenance: input.evidence.provenance,
|
|
37204
38336
|
selection: {
|
|
37205
38337
|
availableRows: input.evidence.rows.length,
|
|
@@ -37207,13 +38339,45 @@ function linkEvidenceReport(input) {
|
|
|
37207
38339
|
omittedRows: Math.max(0, input.evidence.rows.length - rows.length),
|
|
37208
38340
|
limit
|
|
37209
38341
|
},
|
|
37210
|
-
|
|
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,
|
|
37211
38367
|
links: rows,
|
|
37212
|
-
|
|
38368
|
+
targetContext: input.targetContext ? {
|
|
38369
|
+
...input.targetContext,
|
|
38370
|
+
rows: contextRows,
|
|
38371
|
+
findings
|
|
38372
|
+
} : null,
|
|
38373
|
+
findings,
|
|
38374
|
+
warnings: [...input.evidence.warnings, ...input.targetContext?.warnings ?? []],
|
|
37213
38375
|
caveats: [
|
|
37214
38376
|
"This is bounded provider or imported evidence, not a complete backlink index.",
|
|
37215
38377
|
"A missing link is not proof that no link exists, and an observed link is not a quality or ranking verdict.",
|
|
37216
|
-
"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."] : []
|
|
37217
38381
|
]
|
|
37218
38382
|
};
|
|
37219
38383
|
}
|
|
@@ -40635,9 +41799,14 @@ function registerProviderTools(server) {
|
|
|
40635
41799
|
}
|
|
40636
41800
|
});
|
|
40637
41801
|
server.registerTool("seo_link_evidence", {
|
|
40638
|
-
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",
|
|
40639
41803
|
inputSchema: {
|
|
40640
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(),
|
|
40641
41810
|
file: z$1.string().min(1).max(4096).optional(),
|
|
40642
41811
|
format: z$1.enum([
|
|
40643
41812
|
"csv",
|
|
@@ -40647,23 +41816,43 @@ function registerProviderTools(server) {
|
|
|
40647
41816
|
rowLimit: z$1.number().int().min(1).max(1e5).optional(),
|
|
40648
41817
|
targetLimit: z$1.number().int().min(1).max(50).optional(),
|
|
40649
41818
|
detailPagesPerTarget: z$1.number().int().min(1).max(3).optional(),
|
|
40650
|
-
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()
|
|
40651
41822
|
}
|
|
40652
|
-
}, async ({ site, file, format, rowLimit, targetLimit, detailPagesPerTarget, limit }) => {
|
|
41823
|
+
}, async ({ site, provider, target, scope, includeSubdomains, searchConsoleSite, file, format, rowLimit, targetLimit, detailPagesPerTarget, limit, days, refresh }) => {
|
|
40653
41824
|
try {
|
|
40654
|
-
|
|
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
|
+
});
|
|
40655
41846
|
const report = linkEvidenceReport({
|
|
40656
|
-
evidence
|
|
40657
|
-
|
|
40658
|
-
|
|
40659
|
-
|
|
40660
|
-
|
|
40661
|
-
|
|
40662
|
-
|
|
40663
|
-
|
|
40664
|
-
|
|
40665
|
-
}),
|
|
40666
|
-
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
|
|
40667
41856
|
});
|
|
40668
41857
|
return toolSuccess(`${report.summary.observedLinks} referring links were retained from ${report.provenance.provider}.`, report);
|
|
40669
41858
|
} catch (error) {
|
|
@@ -41078,15 +42267,18 @@ const REPORT_DEPTH = {
|
|
|
41078
42267
|
"link-evidence": {
|
|
41079
42268
|
readOrder: [
|
|
41080
42269
|
"dataStatus, provenance, warnings, and caveats",
|
|
41081
|
-
"
|
|
41082
|
-
"
|
|
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"
|
|
41083
42274
|
],
|
|
41084
42275
|
doNotClaim: [
|
|
41085
|
-
"The retained rows are not a complete backlink index.",
|
|
41086
|
-
"
|
|
41087
|
-
"
|
|
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."
|
|
41088
42280
|
],
|
|
41089
|
-
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.",
|
|
41090
42282
|
related: [
|
|
41091
42283
|
{
|
|
41092
42284
|
id: "link-recovery",
|
|
@@ -41099,6 +42291,10 @@ const REPORT_DEPTH = {
|
|
|
41099
42291
|
{
|
|
41100
42292
|
id: "site-crawl",
|
|
41101
42293
|
reason: "Checks internal links and live pages."
|
|
42294
|
+
},
|
|
42295
|
+
{
|
|
42296
|
+
id: "search-performance-overview",
|
|
42297
|
+
reason: "Adds broader first-party search context."
|
|
41102
42298
|
}
|
|
41103
42299
|
]
|
|
41104
42300
|
},
|
|
@@ -42827,10 +44023,14 @@ const REPORT_GUIDANCE = {
|
|
|
42827
44023
|
},
|
|
42828
44024
|
"link-evidence": {
|
|
42829
44025
|
name: "Referring link evidence",
|
|
42830
|
-
description: "Review
|
|
42831
|
-
useWhen: [
|
|
42832
|
-
|
|
42833
|
-
|
|
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."
|
|
42834
44034
|
},
|
|
42835
44035
|
"server-log-analysis": {
|
|
42836
44036
|
name: "Server log crawler analysis",
|