seo 0.13.3 → 0.14.0
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/PRIVACY.md +9 -1
- package/dist/cli.js +84 -80
- package/dist/cli.js.map +1 -1
- package/dist/{dist-BbTqJIE_.js → dist-4Yam8ps7.js} +1 -1
- package/dist/dist-B_2zww72.js +427 -0
- package/dist/dist-B_2zww72.js.map +1 -0
- package/dist/{esm-hHlvm1W0.js → esm-BmuxgZyN.js} +95 -188
- package/dist/{esm-hHlvm1W0.js.map → esm-BmuxgZyN.js.map} +1 -1
- package/dist/index.d.ts +73 -406
- package/dist/index.js +1448 -564
- package/dist/index.js.map +1 -1
- package/dist/mcp.d.ts +0 -14
- package/dist/mcp.js +1020 -807
- package/dist/mcp.js.map +1 -1
- package/dist/provider-sdk.d.ts +0 -2
- package/dist/provider-sdk.js +1 -2
- package/package.json +6 -5
- package/site-kit/README.md +13 -0
- package/site-kit/guide/build.md +70 -0
- package/site-kit/guide/data.md +49 -0
- package/site-kit/guide/golden-page.md +50 -0
- package/site-kit/guide/grow.md +58 -0
- package/site-kit/guide/launch.md +55 -0
- package/site-kit/guide/market.md +57 -0
- package/site-kit/guide/plan.md +49 -0
- package/site-kit/guide/scale.md +41 -0
- package/site-kit/guide/setup.md +59 -0
- package/site-kit/guide/writing.md +55 -0
- package/site-kit/starter/.github/workflows/refresh-data.yml +88 -0
- package/site-kit/starter/.node-version +1 -0
- package/site-kit/starter/AGENTS.md +65 -0
- package/site-kit/starter/README.md +11 -0
- package/site-kit/starter/astro.config.mjs +31 -0
- package/site-kit/starter/gitignore +7 -0
- package/site-kit/starter/llms.config.mjs +38 -0
- package/site-kit/starter/package.json +31 -0
- package/site-kit/starter/pnpm-workspace.yaml +5 -0
- package/site-kit/starter/public/_headers +5 -0
- package/site-kit/starter/public/favicon.svg +1 -0
- package/site-kit/starter/public/fonts/InterLatinVF.woff2 +0 -0
- package/site-kit/starter/public/fonts/JetBrainsMonoLatinVF.woff2 +0 -0
- package/site-kit/starter/public/fonts/LICENSE-inter.txt +93 -0
- package/site-kit/starter/public/fonts/LICENSE-jetbrains-mono.txt +93 -0
- package/site-kit/starter/scripts/deploy.mjs +60 -0
- package/site-kit/starter/scripts/markdown-rewrites.mjs +33 -0
- package/site-kit/starter/scripts/refresh-data.mjs +60 -0
- package/site-kit/starter/scripts/validate-data.mjs +47 -0
- package/site-kit/starter/site.config.json +9 -0
- package/site-kit/starter/src/components/Breadcrumbs.astro +27 -0
- package/site-kit/starter/src/components/Button.astro +46 -0
- package/site-kit/starter/src/components/Card.astro +54 -0
- package/site-kit/starter/src/components/CodeBlock.astro +62 -0
- package/site-kit/starter/src/components/FactTable.astro +45 -0
- package/site-kit/starter/src/components/FaqAccordion.astro +143 -0
- package/site-kit/starter/src/components/Footer.astro +34 -0
- package/site-kit/starter/src/components/FormInput.astro +18 -0
- package/site-kit/starter/src/components/FormSelect.astro +34 -0
- package/site-kit/starter/src/components/Header.astro +17 -0
- package/site-kit/starter/src/components/HugeIcon.astro +36 -0
- package/site-kit/starter/src/components/Logo.astro +21 -0
- package/site-kit/starter/src/components/PageHeader.astro +33 -0
- package/site-kit/starter/src/components/SectionHeading.astro +18 -0
- package/site-kit/starter/src/components/ui/button.ts +51 -0
- package/site-kit/starter/src/data/parks.json +106 -0
- package/site-kit/starter/src/data/sources.json +11 -0
- package/site-kit/starter/src/integrations/og-images.ts +49 -0
- package/site-kit/starter/src/integrations/sitemaps.ts +104 -0
- package/site-kit/starter/src/layouts/Base.astro +69 -0
- package/site-kit/starter/src/lib/cn.ts +5 -0
- package/site-kit/starter/src/lib/narrative.ts +115 -0
- package/site-kit/starter/src/lib/og/card.ts +85 -0
- package/site-kit/starter/src/lib/og/path.ts +14 -0
- package/site-kit/starter/src/lib/og/render.ts +30 -0
- package/site-kit/starter/src/lib/parks.ts +84 -0
- package/site-kit/starter/src/lib/seo.ts +97 -0
- package/site-kit/starter/src/pages/404.astro +30 -0
- package/site-kit/starter/src/pages/about.astro +39 -0
- package/site-kit/starter/src/pages/index.astro +52 -0
- package/site-kit/starter/src/pages/parks/[slug].astro +95 -0
- package/site-kit/starter/src/pages/regions/[region].astro +58 -0
- package/site-kit/starter/src/pages/robots.txt.ts +16 -0
- package/site-kit/starter/src/site.ts +29 -0
- package/site-kit/starter/src/styles/globals.css +561 -0
- package/site-kit/starter/tsconfig.json +5 -0
- package/site-kit/starter/wrangler.jsonc +16 -0
- package/skills/seo/SKILL.md +19 -1
- package/dist/dist-DBI-BioS.js +0 -420
- package/dist/dist-DBI-BioS.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRequire as __seoCreateRequire } from 'node:module'; const require = __seoCreateRequire(import.meta.url);
|
|
2
|
-
import { n as version, t as load } from "./esm-
|
|
2
|
+
import { n as version, t as load } from "./esm-BmuxgZyN.js";
|
|
3
3
|
import { SEO_PROVIDER_API_VERSION } from "./provider-sdk.js";
|
|
4
4
|
import crypto, { createHash, randomUUID, sign } from "node:crypto";
|
|
5
5
|
import { z } from "zod";
|
|
@@ -28,7 +28,7 @@ import { execFile } from "node:child_process";
|
|
|
28
28
|
import pino from "pino";
|
|
29
29
|
import pretty from "pino-pretty";
|
|
30
30
|
import { request } from "node:https";
|
|
31
|
-
|
|
31
|
+
// packages/core/src/analytics/providers.ts
|
|
32
32
|
const ANALYTICS_PROVIDER_IDS = ["google", "clicky"];
|
|
33
33
|
const ANALYTICS_PROVIDER_DETAILS = {
|
|
34
34
|
google: {
|
|
@@ -56,8 +56,7 @@ function analyticsConnectionLabel(connection) {
|
|
|
56
56
|
if (connection.provider === "clicky") return `${label} site ${connection.siteId}`;
|
|
57
57
|
return `${label} account`;
|
|
58
58
|
}
|
|
59
|
-
|
|
60
|
-
//#region packages/core/src/rules.ts
|
|
59
|
+
// packages/core/src/rules.ts
|
|
61
60
|
const RULE_DEFINITIONS = [
|
|
62
61
|
{
|
|
63
62
|
id: "missing_title",
|
|
@@ -999,8 +998,7 @@ function recommendationForRule(ruleId) {
|
|
|
999
998
|
function hasRule(ruleId) {
|
|
1000
999
|
return RULES_BY_ID.has(ruleId);
|
|
1001
1000
|
}
|
|
1002
|
-
|
|
1003
|
-
//#region packages/core/src/analyze/agent-report.ts
|
|
1001
|
+
// packages/core/src/analyze/agent-report.ts
|
|
1004
1002
|
const ACTION_KEYS = /* @__PURE__ */ new Set([
|
|
1005
1003
|
"actions",
|
|
1006
1004
|
"findings",
|
|
@@ -1399,8 +1397,7 @@ function agentActionsView(report, options = {}) {
|
|
|
1399
1397
|
...Object.keys(retainedEvidence).length ? { retainedEvidence } : {}
|
|
1400
1398
|
};
|
|
1401
1399
|
}
|
|
1402
|
-
|
|
1403
|
-
//#region packages/core/src/providers/errors.ts
|
|
1400
|
+
// packages/core/src/providers/errors.ts
|
|
1404
1401
|
const PROVIDER_ERROR_CODES = {
|
|
1405
1402
|
configuration: "AUTH_CONFIG_REQUIRED",
|
|
1406
1403
|
authentication: "AUTH_CONFIG_REQUIRED",
|
|
@@ -1437,8 +1434,7 @@ var ProviderError = class extends Error {
|
|
|
1437
1434
|
};
|
|
1438
1435
|
}
|
|
1439
1436
|
};
|
|
1440
|
-
|
|
1441
|
-
//#region packages/core/src/errors.ts
|
|
1437
|
+
// packages/core/src/errors.ts
|
|
1442
1438
|
const errorMetadata = {
|
|
1443
1439
|
ACCESS_DENIED: {
|
|
1444
1440
|
exitCode: 4,
|
|
@@ -1564,8 +1560,7 @@ async function collectGa4AccountSummaries(fetchPage) {
|
|
|
1564
1560
|
}
|
|
1565
1561
|
throw new Error(`Google Analytics account summary discovery exceeded 20 pages.`);
|
|
1566
1562
|
}
|
|
1567
|
-
|
|
1568
|
-
//#region packages/core/src/http/read-json.ts
|
|
1563
|
+
// packages/core/src/http/read-json.ts
|
|
1569
1564
|
const GZIP = [31, 139];
|
|
1570
1565
|
async function gunzip(bytes) {
|
|
1571
1566
|
const stream = new Response(bytes).body?.pipeThrough(new DecompressionStream("gzip"));
|
|
@@ -1615,8 +1610,7 @@ async function readJson(response, label, options = {}) {
|
|
|
1615
1610
|
async function readJsonOrUndefined(response, label) {
|
|
1616
1611
|
return readJson(response, label).catch(() => void 0);
|
|
1617
1612
|
}
|
|
1618
|
-
|
|
1619
|
-
//#region packages/core/src/google/api.ts
|
|
1613
|
+
// packages/core/src/google/api.ts
|
|
1620
1614
|
var GoogleApiError = class extends SeoError {
|
|
1621
1615
|
status;
|
|
1622
1616
|
constructor(code, message, status) {
|
|
@@ -1745,8 +1739,7 @@ async function runAnalyticsReport(gfetch, propertyId, body, options) {
|
|
|
1745
1739
|
body
|
|
1746
1740
|
});
|
|
1747
1741
|
}
|
|
1748
|
-
|
|
1749
|
-
//#region packages/core/src/bing/api.ts
|
|
1742
|
+
// packages/core/src/bing/api.ts
|
|
1750
1743
|
const BASE = "https://ssl.bing.com/webmaster/api.svc/json";
|
|
1751
1744
|
const MAX_BYTES$4 = 8e6;
|
|
1752
1745
|
function bingCall(auth, options) {
|
|
@@ -1776,7 +1769,7 @@ function bingCall(auth, options) {
|
|
|
1776
1769
|
return (await readJson(response, "Bing Webmaster", { maxBytes: MAX_BYTES$4 })).d;
|
|
1777
1770
|
};
|
|
1778
1771
|
}
|
|
1779
|
-
function day$
|
|
1772
|
+
function day$3(value) {
|
|
1780
1773
|
if (typeof value !== "string") return null;
|
|
1781
1774
|
const legacy = /^\/Date\((-?\d+)(?:[+-]\d{4})?\)\/$/.exec(value);
|
|
1782
1775
|
const date = legacy ? new Date(Number(legacy[1])) : new Date(value);
|
|
@@ -1797,7 +1790,7 @@ async function bingTraffic(call, site) {
|
|
|
1797
1790
|
const rows = [];
|
|
1798
1791
|
let invalidRows = 0;
|
|
1799
1792
|
for (const row of list$2(await call("GetRankAndTrafficStats", { siteUrl: site }), "traffic list")) {
|
|
1800
|
-
const date = day$
|
|
1793
|
+
const date = day$3(row.Date);
|
|
1801
1794
|
const clicks = count$3(row.Clicks);
|
|
1802
1795
|
const impressions = count$3(row.Impressions);
|
|
1803
1796
|
if (!date || clicks === null || impressions === null) {
|
|
@@ -1820,7 +1813,7 @@ async function keyed(call, method, params) {
|
|
|
1820
1813
|
const rows = [];
|
|
1821
1814
|
let invalidRows = 0;
|
|
1822
1815
|
for (const row of list$2(await call(method, params), "row list")) {
|
|
1823
|
-
const date = day$
|
|
1816
|
+
const date = day$3(row.Date);
|
|
1824
1817
|
const key = typeof row.Query === "string" ? row.Query.trim() : "";
|
|
1825
1818
|
const clicks = count$3(row.Clicks);
|
|
1826
1819
|
const impressions = count$3(row.Impressions);
|
|
@@ -1857,8 +1850,7 @@ function bingSiteFor(sites, target) {
|
|
|
1857
1850
|
if (!wanted) return null;
|
|
1858
1851
|
return sites.filter((site) => host(site.url) === wanted).sort((a, b) => Number(b.verified) - Number(a.verified) || Number(b.url.startsWith("https://")) - Number(a.url.startsWith("https://")) || a.url.localeCompare(b.url, "en"))[0] ?? null;
|
|
1859
1852
|
}
|
|
1860
|
-
|
|
1861
|
-
//#region packages/core/src/gsc/dates.ts
|
|
1853
|
+
// packages/core/src/gsc/dates.ts
|
|
1862
1854
|
const GSC_TIME_ZONE = "America/Los_Angeles";
|
|
1863
1855
|
const GSC_FINAL_DATA_LAG_DAYS = 4;
|
|
1864
1856
|
function dateInTimeZone(now, timeZone) {
|
|
@@ -1887,8 +1879,7 @@ function finalGscDateRange(days = 28, now = /* @__PURE__ */ new Date()) {
|
|
|
1887
1879
|
endDate
|
|
1888
1880
|
};
|
|
1889
1881
|
}
|
|
1890
|
-
|
|
1891
|
-
//#region packages/core/src/analyze/site-diagnostics/quick-wins-report-input.ts
|
|
1882
|
+
// packages/core/src/analyze/site-diagnostics/quick-wins-report-input.ts
|
|
1892
1883
|
function integerOption$1(input) {
|
|
1893
1884
|
if (input.value === void 0) return input.fallback;
|
|
1894
1885
|
if (!Number.isInteger(input.value) || input.value < input.minimum || input.value > input.maximum) throw new SeoError("INVALID_INPUT", `${input.label} must be a whole number between ${input.minimum} and ${input.maximum}.`);
|
|
@@ -1914,8 +1905,7 @@ function explicitDateRange(input, maximumDays) {
|
|
|
1914
1905
|
days
|
|
1915
1906
|
};
|
|
1916
1907
|
}
|
|
1917
|
-
|
|
1918
|
-
//#region packages/core/src/analyze/site-diagnostics/decay-report-input.ts
|
|
1908
|
+
// packages/core/src/analyze/site-diagnostics/decay-report-input.ts
|
|
1919
1909
|
function decayNumberOption(input) {
|
|
1920
1910
|
const value = input.value ?? input.fallback;
|
|
1921
1911
|
if (!Number.isFinite(value) || value < input.minimum || value > input.maximum) throw new SeoError("INVALID_INPUT", `${input.label} must be between ${input.minimum} and ${input.maximum}.`);
|
|
@@ -1971,8 +1961,7 @@ function decayComparisonRange(current, comparison = "previous-period") {
|
|
|
1971
1961
|
endDate
|
|
1972
1962
|
};
|
|
1973
1963
|
}
|
|
1974
|
-
|
|
1975
|
-
//#region packages/core/src/updates/ranking-history.ts
|
|
1964
|
+
// packages/core/src/updates/ranking-history.ts
|
|
1976
1965
|
const RANKING_HISTORY = [
|
|
1977
1966
|
{
|
|
1978
1967
|
id: "XhUDXP7A67iHCD2kmbVu",
|
|
@@ -2227,8 +2216,7 @@ const RANKING_HISTORY = [
|
|
|
2227
2216
|
external_desc: "November 2021 spam update"
|
|
2228
2217
|
}
|
|
2229
2218
|
];
|
|
2230
|
-
|
|
2231
|
-
//#region packages/core/src/updates/search-updates.ts
|
|
2219
|
+
// packages/core/src/updates/search-updates.ts
|
|
2232
2220
|
function classifyUpdate(name, product) {
|
|
2233
2221
|
const lower = `${name} ${product}`.toLowerCase();
|
|
2234
2222
|
if (lower.includes("core")) return "core";
|
|
@@ -2277,8 +2265,7 @@ function findOverlappingSearchUpdates(input) {
|
|
|
2277
2265
|
return updateStart <= windowEnd && updateEnd >= windowStart;
|
|
2278
2266
|
});
|
|
2279
2267
|
}
|
|
2280
|
-
|
|
2281
|
-
//#region packages/core/src/commands/types.ts
|
|
2268
|
+
// packages/core/src/commands/types.ts
|
|
2282
2269
|
var CommandError = class extends Error {
|
|
2283
2270
|
code;
|
|
2284
2271
|
constructor(code, message) {
|
|
@@ -2286,8 +2273,7 @@ var CommandError = class extends Error {
|
|
|
2286
2273
|
this.code = code;
|
|
2287
2274
|
}
|
|
2288
2275
|
};
|
|
2289
|
-
|
|
2290
|
-
//#region packages/core/src/commands/shared.ts
|
|
2276
|
+
// packages/core/src/commands/shared.ts
|
|
2291
2277
|
const FACTS_ONLY = "These are facts from Search Console, not instructions.";
|
|
2292
2278
|
const ADVICE_KEYS = /^(recommendations?|actions?|advice|suggested[A-Z]\w*|nextSteps?)$/;
|
|
2293
2279
|
function factsOnly(value) {
|
|
@@ -2423,8 +2409,7 @@ function matchProperty(names, site) {
|
|
|
2423
2409
|
}
|
|
2424
2410
|
}).filter((entry) => entry !== null && schemes.includes(entry.url.protocol) && bare(entry.url.hostname) === host && `${url.pathname}/`.startsWith(entry.url.pathname)).sort((a, b) => b.url.pathname.length - a.url.pathname.length || schemes.indexOf(a.url.protocol) - schemes.indexOf(b.url.protocol) || Number(b.url.hostname === url.hostname) - Number(a.url.hostname === url.hostname) || a.name.localeCompare(b.name))[0]?.name ?? null;
|
|
2425
2411
|
}
|
|
2426
|
-
|
|
2427
|
-
//#region packages/core/src/commands/present.ts
|
|
2412
|
+
// packages/core/src/commands/present.ts
|
|
2428
2413
|
const LIST_KEYS = [
|
|
2429
2414
|
"items",
|
|
2430
2415
|
"rows",
|
|
@@ -2493,7 +2478,7 @@ const METRIC_COLUMNS = [
|
|
|
2493
2478
|
"score",
|
|
2494
2479
|
"priority.score"
|
|
2495
2480
|
];
|
|
2496
|
-
const SUMMARY_ROWS$
|
|
2481
|
+
const SUMMARY_ROWS$2 = 10;
|
|
2497
2482
|
const CELL_MAX = 80;
|
|
2498
2483
|
const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2499
2484
|
const isPrimitive = (value) => value === null || [
|
|
@@ -2652,7 +2637,7 @@ function presentReport(input) {
|
|
|
2652
2637
|
}
|
|
2653
2638
|
};
|
|
2654
2639
|
}
|
|
2655
|
-
const shown = flat.slice(0, Math.min(input.limit, SUMMARY_ROWS$
|
|
2640
|
+
const shown = flat.slice(0, Math.min(input.limit, SUMMARY_ROWS$2));
|
|
2656
2641
|
const columns = columnsFor(flat, 8);
|
|
2657
2642
|
return {
|
|
2658
2643
|
text: [
|
|
@@ -2687,8 +2672,7 @@ function preview(report, origin, rows) {
|
|
|
2687
2672
|
text: table$1(flat.slice(0, rows), columnsFor(flat, 6), origin)
|
|
2688
2673
|
};
|
|
2689
2674
|
}
|
|
2690
|
-
|
|
2691
|
-
//#region packages/core/src/commands/sites.ts
|
|
2675
|
+
// packages/core/src/commands/sites.ts
|
|
2692
2676
|
async function readableSites(google, signIn) {
|
|
2693
2677
|
return (await listSearchConsoleSites(google, { signIn })).filter((entry) => entry.permissionLevel !== "siteUnverifiedUser").map((entry) => ({
|
|
2694
2678
|
siteUrl: entry.siteUrl,
|
|
@@ -2762,8 +2746,7 @@ async function runSites(input) {
|
|
|
2762
2746
|
}
|
|
2763
2747
|
};
|
|
2764
2748
|
}
|
|
2765
|
-
|
|
2766
|
-
//#region packages/core/src/commands/linked.ts
|
|
2749
|
+
// packages/core/src/commands/linked.ts
|
|
2767
2750
|
const MAX_ROWS$2 = 1e5;
|
|
2768
2751
|
const PERMISSION_RANK = {
|
|
2769
2752
|
siteOwner: 0,
|
|
@@ -2866,8 +2849,7 @@ var LinkedAccounts = class {
|
|
|
2866
2849
|
});
|
|
2867
2850
|
}
|
|
2868
2851
|
};
|
|
2869
|
-
|
|
2870
|
-
//#region packages/core/src/paths.ts
|
|
2852
|
+
// packages/core/src/paths.ts
|
|
2871
2853
|
let memoizedPaths;
|
|
2872
2854
|
function defaultConfigDir(input) {
|
|
2873
2855
|
if (input.platform === "win32") return join(input.env.APPDATA ?? join(input.home, "AppData", "Roaming"), "seo", "Config");
|
|
@@ -2962,8 +2944,7 @@ function fileSize(path) {
|
|
|
2962
2944
|
return 0;
|
|
2963
2945
|
}
|
|
2964
2946
|
}
|
|
2965
|
-
|
|
2966
|
-
//#region packages/core/src/storage/files.ts
|
|
2947
|
+
// packages/core/src/storage/files.ts
|
|
2967
2948
|
function ensureParentDir(path, mode = 448) {
|
|
2968
2949
|
mkdirSync(dirname(path), {
|
|
2969
2950
|
recursive: true,
|
|
@@ -3001,8 +2982,7 @@ function safeRemove(path) {
|
|
|
3001
2982
|
force: true
|
|
3002
2983
|
});
|
|
3003
2984
|
}
|
|
3004
|
-
|
|
3005
|
-
//#region packages/core/src/telemetry-failures.ts
|
|
2985
|
+
// packages/core/src/telemetry-failures.ts
|
|
3006
2986
|
const TELEMETRY_ERROR_CATEGORIES = [
|
|
3007
2987
|
"auth",
|
|
3008
2988
|
"crawl_timeout",
|
|
@@ -3277,8 +3257,7 @@ function classifyTelemetryFailure(error) {
|
|
|
3277
3257
|
function isTelemetryOperation(value) {
|
|
3278
3258
|
return typeof value === "string" && TELEMETRY_OPERATIONS.includes(value);
|
|
3279
3259
|
}
|
|
3280
|
-
|
|
3281
|
-
//#region packages/core/src/telemetry-shared.ts
|
|
3260
|
+
// packages/core/src/telemetry-shared.ts
|
|
3282
3261
|
const TELEMETRY_ENDPOINT = "https://seoskill.dev/api/t";
|
|
3283
3262
|
const TELEMETRY_SCHEMA_VERSION = 2;
|
|
3284
3263
|
function isoWeek(date) {
|
|
@@ -3297,11 +3276,9 @@ function agentFromClientName(name) {
|
|
|
3297
3276
|
if (value.includes("codex") || value.includes("openai")) return "codex";
|
|
3298
3277
|
return "unknown";
|
|
3299
3278
|
}
|
|
3300
|
-
|
|
3301
|
-
//#region packages/core/src/version.ts
|
|
3279
|
+
// packages/core/src/version.ts
|
|
3302
3280
|
const SEO_VERSION = version;
|
|
3303
|
-
|
|
3304
|
-
//#region packages/core/src/telemetry.ts
|
|
3281
|
+
// packages/core/src/telemetry.ts
|
|
3305
3282
|
const TELEMETRY_TIMEOUT_MS = 2e3;
|
|
3306
3283
|
const TELEMETRY_EVENTS = [
|
|
3307
3284
|
"first_run",
|
|
@@ -3665,8 +3642,7 @@ function telemetryFailureInput(error) {
|
|
|
3665
3642
|
};
|
|
3666
3643
|
return classifyTelemetryFailure(error);
|
|
3667
3644
|
}
|
|
3668
|
-
|
|
3669
|
-
//#region packages/core/src/cloud.ts
|
|
3645
|
+
// packages/core/src/cloud.ts
|
|
3670
3646
|
const CLOUD_URL = "https://seoskill.dev";
|
|
3671
3647
|
const PRIVATE = 384;
|
|
3672
3648
|
const EARLY = 6e4;
|
|
@@ -3822,8 +3798,7 @@ async function cloudLogout() {
|
|
|
3822
3798
|
async function openCloudLink(url) {
|
|
3823
3799
|
await open(url).catch(() => void 0);
|
|
3824
3800
|
}
|
|
3825
|
-
|
|
3826
|
-
//#region packages/core/src/types/auth.ts
|
|
3801
|
+
// packages/core/src/types/auth.ts
|
|
3827
3802
|
const tokenSchema = z.object({
|
|
3828
3803
|
provider: z.literal("google"),
|
|
3829
3804
|
account_email: z.string().email().max(320),
|
|
@@ -3852,8 +3827,7 @@ const tokenStoreSchema = z.object({
|
|
|
3852
3827
|
message: "The active Google account must exist in accounts."
|
|
3853
3828
|
});
|
|
3854
3829
|
});
|
|
3855
|
-
|
|
3856
|
-
//#region packages/core/src/providers/contracts.ts
|
|
3830
|
+
// packages/core/src/providers/contracts.ts
|
|
3857
3831
|
const providerIdSchema = z.string().min(1).max(64).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/u);
|
|
3858
3832
|
const providerCapabilitySchema = z.enum([
|
|
3859
3833
|
"keyword-metrics",
|
|
@@ -3871,8 +3845,7 @@ const providerCapabilitySchema = z.enum([
|
|
|
3871
3845
|
"ai-mentions",
|
|
3872
3846
|
"ai-prompt-observation"
|
|
3873
3847
|
]);
|
|
3874
|
-
|
|
3875
|
-
//#region packages/core/src/types/config.ts
|
|
3848
|
+
// packages/core/src/types/config.ts
|
|
3876
3849
|
const providerPreferenceSchema = z.enum(["cheap", "authoritative"]);
|
|
3877
3850
|
const analyticsProviderSchema = z.enum(["google", "clicky"]);
|
|
3878
3851
|
const analyticsExtensionIdSchema = z.string().min(1).max(64).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/u);
|
|
@@ -3925,8 +3898,7 @@ async function setKeyringPassword(service, account, password) {
|
|
|
3925
3898
|
async function deleteKeyringPassword(service, account) {
|
|
3926
3899
|
return (await resolveKeyring()).deletePassword(service, account);
|
|
3927
3900
|
}
|
|
3928
|
-
|
|
3929
|
-
//#region packages/core/src/storage/config.ts
|
|
3901
|
+
// packages/core/src/storage/config.ts
|
|
3930
3902
|
const KEYRING_SERVICE$1 = "seo";
|
|
3931
3903
|
const PRIVATE_FILE_MODE$2 = 384;
|
|
3932
3904
|
function tightenConfigPermissions(path) {
|
|
@@ -4208,8 +4180,7 @@ function getPrivacySnapshot() {
|
|
|
4208
4180
|
}
|
|
4209
4181
|
];
|
|
4210
4182
|
}
|
|
4211
|
-
|
|
4212
|
-
//#region packages/core/src/storage/lock.ts
|
|
4183
|
+
// packages/core/src/storage/lock.ts
|
|
4213
4184
|
async function withFileLock(path, task) {
|
|
4214
4185
|
ensureParentDir(path);
|
|
4215
4186
|
const fastLocks = process.env.SEO_LOCK_FAST === "1";
|
|
@@ -4238,14 +4209,12 @@ async function withFileLock(path, task) {
|
|
|
4238
4209
|
await release();
|
|
4239
4210
|
}
|
|
4240
4211
|
}
|
|
4241
|
-
|
|
4242
|
-
//#region packages/core/src/gsc/shared-client.generated.ts
|
|
4212
|
+
// packages/core/src/gsc/shared-client.generated.ts
|
|
4243
4213
|
const SHARED_OAUTH_CLIENT = {
|
|
4244
4214
|
clientId: "548418788053-q7p964rqvjiv8ooamciprku6bpu34u9d.apps.googleusercontent.com",
|
|
4245
4215
|
clientSecret: "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"
|
|
4246
4216
|
};
|
|
4247
|
-
|
|
4248
|
-
//#region packages/core/src/gsc/auth/client-config.ts
|
|
4217
|
+
// packages/core/src/gsc/auth/client-config.ts
|
|
4249
4218
|
function getAuthModeStatus() {
|
|
4250
4219
|
const byo = readOauthClient();
|
|
4251
4220
|
return {
|
|
@@ -4277,8 +4246,7 @@ function getClientConfig(source) {
|
|
|
4277
4246
|
function missingOAuthClientMessage(source) {
|
|
4278
4247
|
return source === "shared" ? "The stored Google login uses the shared seo app, but this build is missing that OAuth client. Report this package issue at https://github.com/iannuttall/seo/issues, or use `seo auth setup-client` with your own desktop OAuth client." : "The stored Google login uses a BYO OAuth client that is no longer configured. Restore the same client with `seo auth setup-client`, or run `seo auth logout` before signing in with a different client.";
|
|
4279
4248
|
}
|
|
4280
|
-
|
|
4281
|
-
//#region packages/core/src/gsc/auth/types.ts
|
|
4249
|
+
// packages/core/src/gsc/auth/types.ts
|
|
4282
4250
|
const GOOGLE_READONLY_SCOPES = ["https://www.googleapis.com/auth/webmasters.readonly", "https://www.googleapis.com/auth/analytics.readonly"];
|
|
4283
4251
|
const GOOGLE_READONLY_SCOPE_LABELS = {
|
|
4284
4252
|
"https://www.googleapis.com/auth/webmasters.readonly": "Search Console",
|
|
@@ -4293,8 +4261,7 @@ const GOOGLE_SCOPE = [
|
|
|
4293
4261
|
"openid",
|
|
4294
4262
|
"email"
|
|
4295
4263
|
].join(" ");
|
|
4296
|
-
|
|
4297
|
-
//#region packages/core/src/gsc/auth/service-account.ts
|
|
4264
|
+
// packages/core/src/gsc/auth/service-account.ts
|
|
4298
4265
|
const GOOGLE_TOKEN_ENDPOINT$1 = "https://oauth2.googleapis.com/token";
|
|
4299
4266
|
const TOKEN_BUFFER_MS = 6e4;
|
|
4300
4267
|
function authConfigError(message) {
|
|
@@ -4440,8 +4407,7 @@ var ServiceAccountAccessTokenClient = class {
|
|
|
4440
4407
|
return this.token.value;
|
|
4441
4408
|
}
|
|
4442
4409
|
};
|
|
4443
|
-
|
|
4444
|
-
//#region packages/core/src/gsc/auth/token-endpoint.ts
|
|
4410
|
+
// packages/core/src/gsc/auth/token-endpoint.ts
|
|
4445
4411
|
const GOOGLE_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token";
|
|
4446
4412
|
var GoogleTokenEndpointError = class extends Error {
|
|
4447
4413
|
status;
|
|
@@ -4495,8 +4461,7 @@ async function exchangeGoogleCode(input, fetchImpl = fetch) {
|
|
|
4495
4461
|
}
|
|
4496
4462
|
return readJson(response, "Google sign-in");
|
|
4497
4463
|
}
|
|
4498
|
-
|
|
4499
|
-
//#region packages/core/src/gsc/auth/authorized-client.ts
|
|
4464
|
+
// packages/core/src/gsc/auth/authorized-client.ts
|
|
4500
4465
|
const REFRESH_BUFFER_MS = 6e4;
|
|
4501
4466
|
function authRequired(accountEmail) {
|
|
4502
4467
|
return new SeoError("AUTH_REQUIRED", accountEmail ? `Google account ${accountEmail} is not connected. Run \`seo auth login\` first.` : notSignedIn());
|
|
@@ -4648,8 +4613,7 @@ async function authStatus() {
|
|
|
4648
4613
|
serviceAccount
|
|
4649
4614
|
};
|
|
4650
4615
|
}
|
|
4651
|
-
|
|
4652
|
-
//#region packages/core/src/gsc/auth/callback-page.ts
|
|
4616
|
+
// packages/core/src/gsc/auth/callback-page.ts
|
|
4653
4617
|
function callbackCopy(options) {
|
|
4654
4618
|
if (options.status === "connected") return {
|
|
4655
4619
|
title: "Google account connected",
|
|
@@ -4688,8 +4652,7 @@ function oauthCallbackPage(options = { status: "connected" }) {
|
|
|
4688
4652
|
</body>
|
|
4689
4653
|
</html>`;
|
|
4690
4654
|
}
|
|
4691
|
-
|
|
4692
|
-
//#region packages/core/src/gsc/auth/loopback-callback.ts
|
|
4655
|
+
// packages/core/src/gsc/auth/loopback-callback.ts
|
|
4693
4656
|
const OAUTH_CALLBACK_TIMEOUT_MS = 3e5;
|
|
4694
4657
|
function timeoutError() {
|
|
4695
4658
|
return new SeoError("AUTH_REQUIRED", "Google sign-in timed out. No account was connected. Run `seo auth login` again and finish the browser steps within five minutes. Keep the terminal open while you sign in.");
|
|
@@ -4745,8 +4708,7 @@ function waitForCode(input) {
|
|
|
4745
4708
|
});
|
|
4746
4709
|
});
|
|
4747
4710
|
}
|
|
4748
|
-
|
|
4749
|
-
//#region packages/core/src/gsc/auth/user-email.ts
|
|
4711
|
+
// packages/core/src/gsc/auth/user-email.ts
|
|
4750
4712
|
function decodeJwtEmail(idToken) {
|
|
4751
4713
|
if (!idToken) return;
|
|
4752
4714
|
const [, payload] = idToken.split(".");
|
|
@@ -4762,8 +4724,7 @@ async function fetchUserEmail(accessToken) {
|
|
|
4762
4724
|
if (!response.ok) return;
|
|
4763
4725
|
return (await readJson(response, "Google user info")).email;
|
|
4764
4726
|
}
|
|
4765
|
-
|
|
4766
|
-
//#region packages/core/src/gsc/auth/loopback.ts
|
|
4727
|
+
// packages/core/src/gsc/auth/loopback.ts
|
|
4767
4728
|
function missingSharedClientLoginMessage() {
|
|
4768
4729
|
return "This build does not have the shared seo Google app configured. If this is an installed public package, report it at https://github.com/iannuttall/seo/issues. For local development, run `seo auth setup-client` or set SEO_GOOGLE_CLIENT_ID / SEO_GOOGLE_CLIENT_SECRET. Legacy GSC_CLIENT_ID / GSC_CLIENT_SECRET also work.";
|
|
4769
4730
|
}
|
|
@@ -4838,8 +4799,7 @@ async function loginWithLoopback(clientConfig = getClientConfig()) {
|
|
|
4838
4799
|
server.close();
|
|
4839
4800
|
}
|
|
4840
4801
|
}
|
|
4841
|
-
|
|
4842
|
-
//#region packages/core/src/gsc/client/fetch.ts
|
|
4802
|
+
// packages/core/src/gsc/client/fetch.ts
|
|
4843
4803
|
async function authedFetch(client, url, init) {
|
|
4844
4804
|
const accessToken = await client.getAccessToken();
|
|
4845
4805
|
if (!accessToken) throw new SeoError("AUTH_EXPIRED", "Could not obtain a Google access token. Run `seo auth login` again.");
|
|
@@ -4860,8 +4820,7 @@ function googleFetchFor(client) {
|
|
|
4860
4820
|
return (url, init) => authedFetch(client, url, init);
|
|
4861
4821
|
}
|
|
4862
4822
|
const LOCAL_SIGN_IN = { signIn: "Run `seo auth login` again." };
|
|
4863
|
-
|
|
4864
|
-
//#region packages/core/src/storage/cache-maintenance.ts
|
|
4823
|
+
// packages/core/src/storage/cache-maintenance.ts
|
|
4865
4824
|
const MEBIBYTE$2 = 1048576;
|
|
4866
4825
|
const CACHE_MAX_BYTES = 256 * MEBIBYTE$2;
|
|
4867
4826
|
const CACHE_MAX_AGE_MS = 2592e6;
|
|
@@ -5038,8 +4997,7 @@ function runCacheMaintenance(database, options = {}) {
|
|
|
5038
4997
|
compaction
|
|
5039
4998
|
};
|
|
5040
4999
|
}
|
|
5041
|
-
|
|
5042
|
-
//#region packages/core/src/storage/sqlite.ts
|
|
5000
|
+
// packages/core/src/storage/sqlite.ts
|
|
5043
5001
|
function withoutDriverMetadata(row) {
|
|
5044
5002
|
if (!row || typeof row !== "object" || Array.isArray(row)) return row;
|
|
5045
5003
|
const { _metadata: _driverMetadata, ...data } = row;
|
|
@@ -5084,8 +5042,7 @@ var SqliteDatabase = class {
|
|
|
5084
5042
|
return this;
|
|
5085
5043
|
}
|
|
5086
5044
|
};
|
|
5087
|
-
|
|
5088
|
-
//#region packages/core/src/storage/database.ts
|
|
5045
|
+
// packages/core/src/storage/database.ts
|
|
5089
5046
|
const PRIVATE_DATABASE_FILE_MODE = 384;
|
|
5090
5047
|
const DATABASE_FILE_SUFFIXES = [
|
|
5091
5048
|
"",
|
|
@@ -5368,8 +5325,7 @@ function clearCache(provider, olderThanMs) {
|
|
|
5368
5325
|
compactCacheDatabase(database, { allowFullVacuum: true });
|
|
5369
5326
|
return removed;
|
|
5370
5327
|
}
|
|
5371
|
-
|
|
5372
|
-
//#region packages/core/src/ga4/client.ts
|
|
5328
|
+
// packages/core/src/ga4/client.ts
|
|
5373
5329
|
function ga4RequestCanUseCache(body) {
|
|
5374
5330
|
return body.dateRanges.every((range) => /^\d{4}-\d{2}-\d{2}$/.test(range.startDate) && /^\d{4}-\d{2}-\d{2}$/.test(range.endDate));
|
|
5375
5331
|
}
|
|
@@ -5450,8 +5406,7 @@ function ga4ReportQualityWarnings(result, label = "Google Analytics report") {
|
|
|
5450
5406
|
})) warnings.push(`${label} was sampled by Google Analytics.`);
|
|
5451
5407
|
return warnings;
|
|
5452
5408
|
}
|
|
5453
|
-
|
|
5454
|
-
//#region packages/core/src/analyze/ai-referrals-sources.ts
|
|
5409
|
+
// packages/core/src/analyze/ai-referrals-sources.ts
|
|
5455
5410
|
const AI_REFERRAL_SOURCES_VERSION = "ai-referral-sources@1";
|
|
5456
5411
|
const AI_REFERRAL_SOURCES = [
|
|
5457
5412
|
{
|
|
@@ -5537,8 +5492,7 @@ function aiReferralSourceForValue(value) {
|
|
|
5537
5492
|
if (!hostname) return void 0;
|
|
5538
5493
|
return AI_REFERRAL_SOURCES.find((source) => source.domains.some((domain) => matchesDomain(hostname, domain)));
|
|
5539
5494
|
}
|
|
5540
|
-
|
|
5541
|
-
//#region packages/core/src/analyze/ai-referrals-analysis.ts
|
|
5495
|
+
// packages/core/src/analyze/ai-referrals-analysis.ts
|
|
5542
5496
|
function compareText$19(left, right) {
|
|
5543
5497
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
5544
5498
|
}
|
|
@@ -5710,13 +5664,12 @@ function analyzeAiReferralRows(input) {
|
|
|
5710
5664
|
]
|
|
5711
5665
|
};
|
|
5712
5666
|
}
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
const PAGE_SIZE$3 = 25e3;
|
|
5667
|
+
// packages/core/src/analyze/ai-referrals-query.ts
|
|
5668
|
+
const PAGE_SIZE$4 = 25e3;
|
|
5716
5669
|
async function fetchAiReferralRows(input) {
|
|
5717
|
-
const pageSize = input.pageSize ?? PAGE_SIZE$
|
|
5670
|
+
const pageSize = input.pageSize ?? PAGE_SIZE$4;
|
|
5718
5671
|
if (!Number.isInteger(input.maxRows) || input.maxRows < 1) throw new Error("maxRows must be a positive whole number.");
|
|
5719
|
-
if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > PAGE_SIZE$
|
|
5672
|
+
if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > PAGE_SIZE$4) throw new Error(`pageSize must be a whole number between 1 and ${PAGE_SIZE$4}.`);
|
|
5720
5673
|
const rows = [];
|
|
5721
5674
|
const warnings = /* @__PURE__ */ new Set();
|
|
5722
5675
|
const retainedKeys = /* @__PURE__ */ new Set();
|
|
@@ -5786,8 +5739,7 @@ function sessionSourceInListFilter(values) {
|
|
|
5786
5739
|
}
|
|
5787
5740
|
} };
|
|
5788
5741
|
}
|
|
5789
|
-
|
|
5790
|
-
//#region packages/core/src/analyze/ai-referrals.ts
|
|
5742
|
+
// packages/core/src/analyze/ai-referrals.ts
|
|
5791
5743
|
const DEFAULT_MAX_ROWS$1 = 1e5;
|
|
5792
5744
|
const MAX_ROWS$1 = 1e5;
|
|
5793
5745
|
const DEFAULT_RESULT_LIMIT = 25;
|
|
@@ -5964,8 +5916,7 @@ async function aiReferralsReport(input, dependencies = {}) {
|
|
|
5964
5916
|
totalUsersEvidence
|
|
5965
5917
|
});
|
|
5966
5918
|
}
|
|
5967
|
-
|
|
5968
|
-
//#region packages/core/src/robots-directives.ts
|
|
5919
|
+
// packages/core/src/robots-directives.ts
|
|
5969
5920
|
function normalizedDirective(value) {
|
|
5970
5921
|
const directive = value.trim().toLowerCase().split(/\s+/)[0];
|
|
5971
5922
|
if (directive === "none") return ["noindex", "nofollow"];
|
|
@@ -6224,8 +6175,7 @@ function resolveStreamedSegments($) {
|
|
|
6224
6175
|
function hasNextAppRouterPayload($) {
|
|
6225
6176
|
return $("script:not([src])").toArray().some((script) => $(script).text().includes("self.__next_f"));
|
|
6226
6177
|
}
|
|
6227
|
-
|
|
6228
|
-
//#region packages/core/src/extract/canonical.ts
|
|
6178
|
+
// packages/core/src/extract/canonical.ts
|
|
6229
6179
|
function headerValue$4(headers, name) {
|
|
6230
6180
|
const lower = name.toLowerCase();
|
|
6231
6181
|
return Object.entries(headers).find(([key]) => key.toLowerCase() === lower)?.[1];
|
|
@@ -6374,11 +6324,10 @@ function extractHttpCanonicalEvidence(headers, baseUrl) {
|
|
|
6374
6324
|
function extractCanonicalEvidence($, headers, baseUrl) {
|
|
6375
6325
|
return evidenceFromCandidates([...htmlCandidates($, baseUrl), ...headerCandidates(headers, baseUrl)]);
|
|
6376
6326
|
}
|
|
6377
|
-
|
|
6378
|
-
//#region packages/core/src/extract/content-sketch.ts
|
|
6327
|
+
// packages/core/src/extract/content-sketch.ts
|
|
6379
6328
|
const SHINGLE_SIZE = 5;
|
|
6380
6329
|
const MAX_HASHES = 32;
|
|
6381
|
-
function words$
|
|
6330
|
+
function words$3(value) {
|
|
6382
6331
|
return value.normalize("NFKC").toLocaleLowerCase("en").match(/[\p{L}\p{N}]+/gu) ?? [];
|
|
6383
6332
|
}
|
|
6384
6333
|
function sampleIndexes(total, limit) {
|
|
@@ -6389,7 +6338,7 @@ function digest(value) {
|
|
|
6389
6338
|
return createHash("sha256").update(value).digest("hex").slice(0, 16);
|
|
6390
6339
|
}
|
|
6391
6340
|
function contentSketch(value) {
|
|
6392
|
-
const tokens = words$
|
|
6341
|
+
const tokens = words$3(value);
|
|
6393
6342
|
const hashes = sampleIndexes(Math.max(0, tokens.length - SHINGLE_SIZE + 1), MAX_HASHES).map((index) => digest(tokens.slice(index, index + SHINGLE_SIZE).join(" ")));
|
|
6394
6343
|
return {
|
|
6395
6344
|
version: 1,
|
|
@@ -6399,8 +6348,7 @@ function contentSketch(value) {
|
|
|
6399
6348
|
hashes
|
|
6400
6349
|
};
|
|
6401
6350
|
}
|
|
6402
|
-
|
|
6403
|
-
//#region packages/core/src/extract/main-content.ts
|
|
6351
|
+
// packages/core/src/extract/main-content.ts
|
|
6404
6352
|
function parseDocument$1(html) {
|
|
6405
6353
|
const { document } = parseHTML(html);
|
|
6406
6354
|
if (document.documentElement?.localName === "html") return document;
|
|
@@ -6526,8 +6474,7 @@ async function extractMainMarkdown(fetchResult) {
|
|
|
6526
6474
|
truncated: true
|
|
6527
6475
|
};
|
|
6528
6476
|
}
|
|
6529
|
-
|
|
6530
|
-
//#region packages/core/src/extract/google-rich-results.ts
|
|
6477
|
+
// packages/core/src/extract/google-rich-results.ts
|
|
6531
6478
|
const SUPPORTED_TYPES = /* @__PURE__ */ new Set([
|
|
6532
6479
|
"Article",
|
|
6533
6480
|
"BlogPosting",
|
|
@@ -6804,8 +6751,7 @@ function selectGoogleRichResultAssessments(assessments, limit = 50) {
|
|
|
6804
6751
|
}
|
|
6805
6752
|
};
|
|
6806
6753
|
}
|
|
6807
|
-
|
|
6808
|
-
//#region packages/core/src/extract/structured-data.ts
|
|
6754
|
+
// packages/core/src/extract/structured-data.ts
|
|
6809
6755
|
function schemaTypeFromIri(value) {
|
|
6810
6756
|
try {
|
|
6811
6757
|
const iri = new URL(value);
|
|
@@ -7042,8 +6988,7 @@ function extractStructuredData($, baseUrl, options = {}) {
|
|
|
7042
6988
|
hasDate: parsed.hasDate || $("[itemprop~=\"datePublished\"], [itemprop~=\"dateModified\"], [property~=\"datePublished\"], [property~=\"dateModified\"]").toArray().some((element) => isValidStructuredDate($(element).attr("content") ?? $(element).attr("datetime") ?? $(element).text()))
|
|
7043
6989
|
};
|
|
7044
6990
|
}
|
|
7045
|
-
|
|
7046
|
-
//#region packages/core/src/extract/page-extractor.ts
|
|
6991
|
+
// packages/core/src/extract/page-extractor.ts
|
|
7047
6992
|
function metaRefresh($, finalUrl) {
|
|
7048
6993
|
for (const element of $("meta[http-equiv]").toArray()) {
|
|
7049
6994
|
if ($(element).attr("http-equiv")?.trim().toLowerCase() !== "refresh") continue;
|
|
@@ -7461,8 +7406,7 @@ async function extractPage(fetchResult, extractor = "defuddle", dependencies = {
|
|
|
7461
7406
|
]
|
|
7462
7407
|
};
|
|
7463
7408
|
}
|
|
7464
|
-
|
|
7465
|
-
//#region packages/core/src/fetch/crawler-identity.ts
|
|
7409
|
+
// packages/core/src/fetch/crawler-identity.ts
|
|
7466
7410
|
const SEO_CRAWLER_TOKEN = "SEO-Skill";
|
|
7467
7411
|
const SEO_CRAWLER_USER_AGENT = `${SEO_CRAWLER_TOKEN}/${SEO_VERSION} (+https://seoskill.dev)`;
|
|
7468
7412
|
const SEO_CRAWLER_IDENTITY = {
|
|
@@ -7472,8 +7416,7 @@ const SEO_CRAWLER_IDENTITY = {
|
|
|
7472
7416
|
userAgent: SEO_CRAWLER_USER_AGENT,
|
|
7473
7417
|
documentationUrl: "https://seoskill.dev/docs/crawler/"
|
|
7474
7418
|
};
|
|
7475
|
-
|
|
7476
|
-
//#region packages/core/src/fetch/access-block.ts
|
|
7419
|
+
// packages/core/src/fetch/access-block.ts
|
|
7477
7420
|
const CLOUDFLARE_SKIP_DOCS = "https://developers.cloudflare.com/waf/custom-rules/skip/";
|
|
7478
7421
|
function isHeadersLike(headers) {
|
|
7479
7422
|
return "get" in headers && typeof headers.get === "function";
|
|
@@ -7543,8 +7486,7 @@ function detectAccessBlock(input) {
|
|
|
7543
7486
|
}
|
|
7544
7487
|
};
|
|
7545
7488
|
}
|
|
7546
|
-
|
|
7547
|
-
//#region packages/core/src/fetch/http-client.ts
|
|
7489
|
+
// packages/core/src/fetch/http-client.ts
|
|
7548
7490
|
var ResponseSizeLimitError = class extends Error {
|
|
7549
7491
|
maxBytes;
|
|
7550
7492
|
constructor(maxBytes, label = "Response") {
|
|
@@ -7611,8 +7553,7 @@ async function readBoundedResponseText(response, maxBytes, label = "Response") {
|
|
|
7611
7553
|
}
|
|
7612
7554
|
return Buffer.concat(chunks, total).toString("utf8");
|
|
7613
7555
|
}
|
|
7614
|
-
|
|
7615
|
-
//#region packages/core/src/fetch/page-fetcher/rate-controls.ts
|
|
7556
|
+
// packages/core/src/fetch/page-fetcher/rate-controls.ts
|
|
7616
7557
|
const HOST_QUEUES = /* @__PURE__ */ new Map();
|
|
7617
7558
|
const HOST_HEALTH = /* @__PURE__ */ new Map();
|
|
7618
7559
|
var OriginBackpressureError = class extends Error {
|
|
@@ -7757,8 +7698,7 @@ function recordHostFetch(input) {
|
|
|
7757
7698
|
}
|
|
7758
7699
|
return snapshotForHost(input.host);
|
|
7759
7700
|
}
|
|
7760
|
-
|
|
7761
|
-
//#region packages/core/src/fetch/page-fetcher/redirects.ts
|
|
7701
|
+
// packages/core/src/fetch/page-fetcher/redirects.ts
|
|
7762
7702
|
async function fetchWithRedirectChain(url, signal) {
|
|
7763
7703
|
const redirectChain = [];
|
|
7764
7704
|
let currentUrl = url;
|
|
@@ -7783,8 +7723,7 @@ async function fetchWithRedirectChain(url, signal) {
|
|
|
7783
7723
|
}
|
|
7784
7724
|
throw new Error(`Too many redirects for ${url}`);
|
|
7785
7725
|
}
|
|
7786
|
-
|
|
7787
|
-
//#region packages/core/src/fetch/page-fetcher/robots.ts
|
|
7726
|
+
// packages/core/src/fetch/page-fetcher/robots.ts
|
|
7788
7727
|
const MAX_ROBOTS_RESPONSE_BYTES = 1048576;
|
|
7789
7728
|
var RobotsAccessError = class extends Error {
|
|
7790
7729
|
reason;
|
|
@@ -7935,8 +7874,7 @@ function createRobotsSession(input = {}) {
|
|
|
7935
7874
|
}
|
|
7936
7875
|
};
|
|
7937
7876
|
}
|
|
7938
|
-
|
|
7939
|
-
//#region packages/core/src/fetch/page-fetcher/robots-evidence.ts
|
|
7877
|
+
// packages/core/src/fetch/page-fetcher/robots-evidence.ts
|
|
7940
7878
|
function pageRobotsEvidence(robots) {
|
|
7941
7879
|
return {
|
|
7942
7880
|
url: robots.url,
|
|
@@ -7957,8 +7895,7 @@ function diagnosticRobotsEvidence(robots) {
|
|
|
7957
7895
|
error: robots.error
|
|
7958
7896
|
};
|
|
7959
7897
|
}
|
|
7960
|
-
|
|
7961
|
-
//#region packages/core/src/fetch/page-fetcher/plain.ts
|
|
7898
|
+
// packages/core/src/fetch/page-fetcher/plain.ts
|
|
7962
7899
|
const MAX_PAGE_RESPONSE_BYTES = 5242880;
|
|
7963
7900
|
function encodePageFetchCacheEvidence(result) {
|
|
7964
7901
|
return JSON.stringify({
|
|
@@ -8100,8 +8037,7 @@ async function fetchPlain(url, refresh = false, timeoutMs = 2e4, rate, signal, r
|
|
|
8100
8037
|
});
|
|
8101
8038
|
return result;
|
|
8102
8039
|
}
|
|
8103
|
-
|
|
8104
|
-
//#region packages/core/src/fetch/page-fetcher/browser-path.ts
|
|
8040
|
+
// packages/core/src/fetch/page-fetcher/browser-path.ts
|
|
8105
8041
|
const PLAYWRIGHT_CORE_VERSION = "1.61.1";
|
|
8106
8042
|
function systemBrowserCandidates(input) {
|
|
8107
8043
|
const macApplications = ["/Applications", join(input.homeDirectory, "Applications")];
|
|
@@ -8199,8 +8135,7 @@ function browserUnavailableMessage(resolution) {
|
|
|
8199
8135
|
"Set SEO_BROWSER_EXECUTABLE_PATH to use a managed browser executable."
|
|
8200
8136
|
].join(" ");
|
|
8201
8137
|
}
|
|
8202
|
-
|
|
8203
|
-
//#region packages/core/src/fetch/page-fetcher/rendered.ts
|
|
8138
|
+
// packages/core/src/fetch/page-fetcher/rendered.ts
|
|
8204
8139
|
const DEFAULT_RENDER_TIMEOUT_MS = 2e4;
|
|
8205
8140
|
const NETWORK_IDLE_TIMEOUT_MS = 3e3;
|
|
8206
8141
|
const MAX_RENDER_CONTEXTS = 4;
|
|
@@ -8430,8 +8365,7 @@ function looksLikeSpa(html) {
|
|
|
8430
8365
|
const lower = html.toLowerCase();
|
|
8431
8366
|
return /<div[^>]+id="root"[^>]*>\s*<\/div>/.test(lower) || /<div[^>]+id="__next"[^>]*>\s*<\/div>/.test(lower) || /<app-root[^>]*>\s*<\/app-root>/.test(lower) || /enable javascript/i.test(lower);
|
|
8432
8367
|
}
|
|
8433
|
-
|
|
8434
|
-
//#region packages/core/src/fetch/page-fetcher/rendering-difference.ts
|
|
8368
|
+
// packages/core/src/fetch/page-fetcher/rendering-difference.ts
|
|
8435
8369
|
const MAX_HEADINGS = 50;
|
|
8436
8370
|
const MAX_TEXT_VALUE_LENGTH = 500;
|
|
8437
8371
|
function safeText(value) {
|
|
@@ -8536,8 +8470,7 @@ function renderingDocumentDifference(raw, rendered) {
|
|
|
8536
8470
|
].filter((field) => JSON.stringify(rawDocument[field]) !== JSON.stringify(renderedDocument[field]))
|
|
8537
8471
|
};
|
|
8538
8472
|
}
|
|
8539
|
-
|
|
8540
|
-
//#region packages/core/src/fetch/page-fetcher/status-probe.ts
|
|
8473
|
+
// packages/core/src/fetch/page-fetcher/status-probe.ts
|
|
8541
8474
|
async function statusProbe(url, opts) {
|
|
8542
8475
|
const startedAt = Date.now();
|
|
8543
8476
|
const host = new URL(url).host;
|
|
@@ -8615,8 +8548,7 @@ async function fetchPageStatus(url, opts = {}) {
|
|
|
8615
8548
|
const rate = normalizeRateControls(opts.rate);
|
|
8616
8549
|
return await queueForHost(new URL(url).host, rate).add(() => statusProbe(url, opts));
|
|
8617
8550
|
}
|
|
8618
|
-
|
|
8619
|
-
//#region packages/core/src/fetch/page-fetcher.ts
|
|
8551
|
+
// packages/core/src/fetch/page-fetcher.ts
|
|
8620
8552
|
function normalizeJavaScriptRenderingMode(input) {
|
|
8621
8553
|
if (input === true) return "on";
|
|
8622
8554
|
if (input === false) return "off";
|
|
@@ -8693,8 +8625,7 @@ async function fetchPage(url, opts = {}, dependencies = {}) {
|
|
|
8693
8625
|
}
|
|
8694
8626
|
});
|
|
8695
8627
|
}
|
|
8696
|
-
|
|
8697
|
-
//#region packages/core/src/gsc/property-url.ts
|
|
8628
|
+
// packages/core/src/gsc/property-url.ts
|
|
8698
8629
|
function invalid(message) {
|
|
8699
8630
|
throw new SeoError("INVALID_INPUT", message);
|
|
8700
8631
|
}
|
|
@@ -8887,8 +8818,7 @@ function finalizeUrlInspectionQuota(input) {
|
|
|
8887
8818
|
WHERE credential_key = ? AND property_site_url = ? AND quota_date = ?`).run(dailyExhausted ? 1 : 0, consumed, row.count, input.outcome === "released" ? row.count : 0, exhausted ? 1 : 0, (input.blockedUntil ?? new Date(input.reservation.resetAt)).getTime(), exhausted ? 1 : 0, now.getTime(), now.getTime(), input.reservation.credentialKey, input.reservation.property, input.reservation.quotaDate);
|
|
8888
8819
|
}).immediate();
|
|
8889
8820
|
}
|
|
8890
|
-
|
|
8891
|
-
//#region packages/core/src/gsc/client/inspection.ts
|
|
8821
|
+
// packages/core/src/gsc/client/inspection.ts
|
|
8892
8822
|
function credentialKey(input) {
|
|
8893
8823
|
return createHash("sha256").update(`${input.clientId}\n${input.accountEmail.toLowerCase()}`).digest("base64url");
|
|
8894
8824
|
}
|
|
@@ -8973,8 +8903,7 @@ async function inspectUrl(input) {
|
|
|
8973
8903
|
if (!response.ok) throw inspectionResponseError(response, input);
|
|
8974
8904
|
return await readJson(response, "URL Inspection");
|
|
8975
8905
|
}
|
|
8976
|
-
|
|
8977
|
-
//#region packages/core/src/gsc/client/search-analytics.ts
|
|
8906
|
+
// packages/core/src/gsc/client/search-analytics.ts
|
|
8978
8907
|
function searchDateRange(days) {
|
|
8979
8908
|
return finalGscDateRange(days);
|
|
8980
8909
|
}
|
|
@@ -9140,8 +9069,7 @@ async function queryPagesTopQueriesBatch(site, pageUrls, range = 28) {
|
|
|
9140
9069
|
retainedRowLimitReached: result.rowsFetched >= retainedRowLimit
|
|
9141
9070
|
};
|
|
9142
9071
|
}
|
|
9143
|
-
|
|
9144
|
-
//#region packages/core/src/gsc/client/sites.ts
|
|
9072
|
+
// packages/core/src/gsc/client/sites.ts
|
|
9145
9073
|
async function listSites(_refresh = false, accountEmail) {
|
|
9146
9074
|
const entries = !accountEmail && cloudLinked() ? await allCloudSites() : await listSearchConsoleSites(googleFetchFor((await getAuthorized(accountEmail)).client), LOCAL_SIGN_IN);
|
|
9147
9075
|
writeToCache((database) => {
|
|
@@ -9160,8 +9088,7 @@ async function allCloudSites() {
|
|
|
9160
9088
|
}
|
|
9161
9089
|
return [...best.values()];
|
|
9162
9090
|
}
|
|
9163
|
-
|
|
9164
|
-
//#region packages/core/src/analyze/page-technical-signals.ts
|
|
9091
|
+
// packages/core/src/analyze/page-technical-signals.ts
|
|
9165
9092
|
function normalizePageUrl(value) {
|
|
9166
9093
|
try {
|
|
9167
9094
|
const parsed = new URL(value);
|
|
@@ -9194,8 +9121,7 @@ function pageTechnicalSignals(input) {
|
|
|
9194
9121
|
if (input.page?.canonical && !samePageUrl(input.page.finalUrl, absoluteUrl(input.page.canonical, input.page.finalUrl))) signals.push("canonical-mismatch");
|
|
9195
9122
|
return signals;
|
|
9196
9123
|
}
|
|
9197
|
-
|
|
9198
|
-
//#region packages/core/src/analyze/title-width.ts
|
|
9124
|
+
// packages/core/src/analyze/title-width.ts
|
|
9199
9125
|
const SERP_TITLE_REFERENCE_WIDTH_PX = 580;
|
|
9200
9126
|
const SERP_TITLE_WIDTH_PROFILE = {
|
|
9201
9127
|
id: "arial-20-v1",
|
|
@@ -9326,8 +9252,7 @@ function estimateSerpTitleWidth(title) {
|
|
|
9326
9252
|
fallbackGraphemes: fallbackGraphemes.slice(0, 10)
|
|
9327
9253
|
};
|
|
9328
9254
|
}
|
|
9329
|
-
|
|
9330
|
-
//#region packages/core/src/analyze/audit-page.ts
|
|
9255
|
+
// packages/core/src/analyze/audit-page.ts
|
|
9331
9256
|
const AUDIT_PAGE_RULE_IDS = [
|
|
9332
9257
|
"missing_title",
|
|
9333
9258
|
"title_too_wide",
|
|
@@ -9487,8 +9412,7 @@ async function auditPage(input, dependencies = defaultDependencies$12) {
|
|
|
9487
9412
|
report.recommendations = buildRecommendations(report);
|
|
9488
9413
|
return report;
|
|
9489
9414
|
}
|
|
9490
|
-
|
|
9491
|
-
//#region packages/core/src/analyze/content-coverage-analysis.ts
|
|
9415
|
+
// packages/core/src/analyze/content-coverage-analysis.ts
|
|
9492
9416
|
const STOPWORDS = /* @__PURE__ */ new Set([
|
|
9493
9417
|
"a",
|
|
9494
9418
|
"an",
|
|
@@ -9714,8 +9638,7 @@ async function verifyQueryContentWith(input) {
|
|
|
9714
9638
|
};
|
|
9715
9639
|
}
|
|
9716
9640
|
}
|
|
9717
|
-
|
|
9718
|
-
//#region packages/core/src/analyze/content-coverage.ts
|
|
9641
|
+
// packages/core/src/analyze/content-coverage.ts
|
|
9719
9642
|
function verifyQueryContent(input) {
|
|
9720
9643
|
return verifyQueryContentWith({
|
|
9721
9644
|
...input,
|
|
@@ -9723,8 +9646,7 @@ function verifyQueryContent(input) {
|
|
|
9723
9646
|
extract: input.extract ?? extractPage
|
|
9724
9647
|
});
|
|
9725
9648
|
}
|
|
9726
|
-
|
|
9727
|
-
//#region packages/core/src/brand.ts
|
|
9649
|
+
// packages/core/src/brand.ts
|
|
9728
9650
|
const COUNTRY_CODE_SECOND_LEVELS = /* @__PURE__ */ new Set([
|
|
9729
9651
|
"ac",
|
|
9730
9652
|
"co",
|
|
@@ -9832,8 +9754,7 @@ function shouldExcludeBrandQuery(input) {
|
|
|
9832
9754
|
const terms = input.brandTerms?.length ? input.brandTerms : deriveBrandTerms({ siteUrl: input.siteUrl });
|
|
9833
9755
|
return isBrandQuery(input.query, terms);
|
|
9834
9756
|
}
|
|
9835
|
-
|
|
9836
|
-
//#region packages/core/src/analyze/shared.ts
|
|
9757
|
+
// packages/core/src/analyze/shared.ts
|
|
9837
9758
|
const CTR_BASELINE = {
|
|
9838
9759
|
1: .27,
|
|
9839
9760
|
2: .15,
|
|
@@ -9868,8 +9789,7 @@ function jaccard(a, b) {
|
|
|
9868
9789
|
function defaultDateRange$1(days = 28, now = /* @__PURE__ */ new Date()) {
|
|
9869
9790
|
return finalGscDateRange(days, now);
|
|
9870
9791
|
}
|
|
9871
|
-
|
|
9872
|
-
//#region packages/core/src/analyze/opportunity-primitives.ts
|
|
9792
|
+
// packages/core/src/analyze/opportunity-primitives.ts
|
|
9873
9793
|
const MIN_BENCHMARK_URL_IMPRESSIONS = 30;
|
|
9874
9794
|
const MIN_BENCHMARK_IMPRESSIONS = 1e3;
|
|
9875
9795
|
const MIN_BENCHMARK_URL_SAMPLES = 5;
|
|
@@ -10282,8 +10202,7 @@ function queryOpportunityRecommendation(input) {
|
|
|
10282
10202
|
expectedImpact: `About ${Math.round(input.estimatedClickLift)} extra clicks in the selected GSC window if CTR reaches the benchmark.`
|
|
10283
10203
|
};
|
|
10284
10204
|
}
|
|
10285
|
-
|
|
10286
|
-
//#region packages/core/src/analyze/page-opportunities-evidence.ts
|
|
10205
|
+
// packages/core/src/analyze/page-opportunities-evidence.ts
|
|
10287
10206
|
function verificationFor(input) {
|
|
10288
10207
|
const signals = pageTechnicalSignals(input);
|
|
10289
10208
|
if (signals.length > 0) return {
|
|
@@ -10348,8 +10267,7 @@ function benchmarkDetails$2(benchmark, excludedTargetRows) {
|
|
|
10348
10267
|
excludedTargetRows
|
|
10349
10268
|
};
|
|
10350
10269
|
}
|
|
10351
|
-
|
|
10352
|
-
//#region packages/core/src/analyze/query-quality.ts
|
|
10270
|
+
// packages/core/src/analyze/query-quality.ts
|
|
10353
10271
|
function hasLetter(value) {
|
|
10354
10272
|
return /\p{L}/u.test(value);
|
|
10355
10273
|
}
|
|
@@ -10620,8 +10538,7 @@ function countQueryFlags(items) {
|
|
|
10620
10538
|
flaggedRows
|
|
10621
10539
|
};
|
|
10622
10540
|
}
|
|
10623
|
-
|
|
10624
|
-
//#region packages/core/src/analyze/page-opportunities-selection.ts
|
|
10541
|
+
// packages/core/src/analyze/page-opportunities-selection.ts
|
|
10625
10542
|
const DEFAULT_MIN_IMPRESSIONS$1 = 10;
|
|
10626
10543
|
const DEFAULT_LIMIT$3 = 25;
|
|
10627
10544
|
const MAX_LIMIT$3 = 100;
|
|
@@ -10714,8 +10631,7 @@ function selectBenchmarkRows$1(input) {
|
|
|
10714
10631
|
});
|
|
10715
10632
|
});
|
|
10716
10633
|
}
|
|
10717
|
-
|
|
10718
|
-
//#region packages/core/src/analyze/page-opportunities-analysis.ts
|
|
10634
|
+
// packages/core/src/analyze/page-opportunities-analysis.ts
|
|
10719
10635
|
function compareText$18(left, right) {
|
|
10720
10636
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
10721
10637
|
}
|
|
@@ -10814,8 +10730,7 @@ function analyzePageOpportunitiesFromRows(input) {
|
|
|
10814
10730
|
}
|
|
10815
10731
|
};
|
|
10816
10732
|
}
|
|
10817
|
-
|
|
10818
|
-
//#region packages/core/src/analyze/page-opportunities-presentation.ts
|
|
10733
|
+
// packages/core/src/analyze/page-opportunities-presentation.ts
|
|
10819
10734
|
const actionPriority = new Map([
|
|
10820
10735
|
"technical-check",
|
|
10821
10736
|
"content-gap",
|
|
@@ -10879,8 +10794,7 @@ function pageOpportunityRecommendations(analysis) {
|
|
|
10879
10794
|
if (gaps >= 3) recommendations.push(`Group the ${gaps} verified content gaps into useful sections rather than adding one thin paragraph per query.`);
|
|
10880
10795
|
return recommendations;
|
|
10881
10796
|
}
|
|
10882
|
-
|
|
10883
|
-
//#region packages/core/src/analyze/page-opportunities.ts
|
|
10797
|
+
// packages/core/src/analyze/page-opportunities.ts
|
|
10884
10798
|
const MAX_BENCHMARK_ROWS = 1e5;
|
|
10885
10799
|
const defaultDependencies$11 = {
|
|
10886
10800
|
searchAnalytics: querySearchAnalytics,
|
|
@@ -11081,8 +10995,7 @@ async function pageOpportunitiesReport(input, dependencies = defaultDependencies
|
|
|
11081
10995
|
recommendations: pageOpportunityRecommendations(analysis)
|
|
11082
10996
|
};
|
|
11083
10997
|
}
|
|
11084
|
-
|
|
11085
|
-
//#region packages/core/src/analyze/content-optimization.ts
|
|
10998
|
+
// packages/core/src/analyze/content-optimization.ts
|
|
11086
10999
|
function intentFor(query) {
|
|
11087
11000
|
const value = query.toLowerCase();
|
|
11088
11001
|
if (/\b(vs|versus|alternative|alternatives|compare|comparison|best)\b/.test(value)) return "comparison";
|
|
@@ -11230,8 +11143,7 @@ function contentOptimizationFromPageOpportunities(sourceReport) {
|
|
|
11230
11143
|
async function contentOptimizationReport(input) {
|
|
11231
11144
|
return contentOptimizationFromPageOpportunities(await pageOpportunitiesReport(input));
|
|
11232
11145
|
}
|
|
11233
|
-
|
|
11234
|
-
//#region packages/core/src/analyze/crawler/page-eligibility.ts
|
|
11146
|
+
// packages/core/src/analyze/crawler/page-eligibility.ts
|
|
11235
11147
|
function comparableUrl(value) {
|
|
11236
11148
|
if (!value) return void 0;
|
|
11237
11149
|
try {
|
|
@@ -11258,8 +11170,7 @@ function isAuditableHtmlPage(page) {
|
|
|
11258
11170
|
const contentAuditAllowed = page.contentAuditAllowed ?? (!page.blocked && page.robotsTxt?.allowed !== false);
|
|
11259
11171
|
return isSuccessfulResponse(page) && isHtmlPage(page) && !isRedirectedPage(page) && contentAuditAllowed;
|
|
11260
11172
|
}
|
|
11261
|
-
|
|
11262
|
-
//#region packages/core/src/analyze/crawler/audit.ts
|
|
11173
|
+
// packages/core/src/analyze/crawler/audit.ts
|
|
11263
11174
|
function sameUrl(a, b) {
|
|
11264
11175
|
if (!a || !b) return false;
|
|
11265
11176
|
try {
|
|
@@ -11733,8 +11644,7 @@ function auditCrawlPages(pages, opts = {}) {
|
|
|
11733
11644
|
}
|
|
11734
11645
|
return issues;
|
|
11735
11646
|
}
|
|
11736
|
-
|
|
11737
|
-
//#region packages/core/src/analyze/crawler/crawl-skip-reasons.ts
|
|
11647
|
+
// packages/core/src/analyze/crawler/crawl-skip-reasons.ts
|
|
11738
11648
|
const CRAWL_SKIP_REASONS = [
|
|
11739
11649
|
"asset-url",
|
|
11740
11650
|
"configured-exclusion",
|
|
@@ -11809,8 +11719,7 @@ function crawlSkipReasonCountsFromRecord(input = {}) {
|
|
|
11809
11719
|
count
|
|
11810
11720
|
})) }).skipReasons;
|
|
11811
11721
|
}
|
|
11812
|
-
|
|
11813
|
-
//#region packages/core/src/export/diagnosis-segment-csv.ts
|
|
11722
|
+
// packages/core/src/export/diagnosis-segment-csv.ts
|
|
11814
11723
|
const dimensions = [
|
|
11815
11724
|
"page",
|
|
11816
11725
|
"query",
|
|
@@ -11911,8 +11820,7 @@ function diagnosisSegmentCsvFiles(report) {
|
|
|
11911
11820
|
}))
|
|
11912
11821
|
];
|
|
11913
11822
|
}
|
|
11914
|
-
|
|
11915
|
-
//#region packages/core/src/export/csv.ts
|
|
11823
|
+
// packages/core/src/export/csv.ts
|
|
11916
11824
|
const CSV_SCHEMAS = {
|
|
11917
11825
|
"priorities.csv": [
|
|
11918
11826
|
"rank",
|
|
@@ -12377,8 +12285,7 @@ function refreshPrioritiesCsvFiles(report) {
|
|
|
12377
12285
|
...prefixCsvFiles("diagnosis", diagnoseCsvFiles(report.output.diagnosis))
|
|
12378
12286
|
]);
|
|
12379
12287
|
}
|
|
12380
|
-
|
|
12381
|
-
//#region packages/core/src/analyze/crawler/top-fixes.ts
|
|
12288
|
+
// packages/core/src/analyze/crawler/top-fixes.ts
|
|
12382
12289
|
const SEVERITY_SCORE = {
|
|
12383
12290
|
high: 5e3,
|
|
12384
12291
|
medium: 1200,
|
|
@@ -12529,8 +12436,7 @@ function reviewObservations(report, filters = {}) {
|
|
|
12529
12436
|
recommendation: "review"
|
|
12530
12437
|
});
|
|
12531
12438
|
}
|
|
12532
|
-
|
|
12533
|
-
//#region packages/core/src/analyze/crawler/export.ts
|
|
12439
|
+
// packages/core/src/analyze/crawler/export.ts
|
|
12534
12440
|
const issueHeaders = [
|
|
12535
12441
|
"rule_id",
|
|
12536
12442
|
"title",
|
|
@@ -12842,8 +12748,7 @@ function escapeHtml(value) {
|
|
|
12842
12748
|
function escapeXml(value) {
|
|
12843
12749
|
return escapeHtml(value);
|
|
12844
12750
|
}
|
|
12845
|
-
|
|
12846
|
-
//#region packages/core/src/analyze/crawler/report.ts
|
|
12751
|
+
// packages/core/src/analyze/crawler/report.ts
|
|
12847
12752
|
const MAX_CRAWL_PAGES = 1e4;
|
|
12848
12753
|
const MAX_CRAWL_DEPTH = 64;
|
|
12849
12754
|
const MAX_CRAWL_CONCURRENCY = 16;
|
|
@@ -13282,8 +13187,7 @@ function normalizeLoadedCrawlReport(report) {
|
|
|
13282
13187
|
caveats: sanitizeMessages(report.caveats).sort(compareText$17)
|
|
13283
13188
|
};
|
|
13284
13189
|
}
|
|
13285
|
-
|
|
13286
|
-
//#region packages/core/src/analyze/crawler/site-checks-schema.ts
|
|
13190
|
+
// packages/core/src/analyze/crawler/site-checks-schema.ts
|
|
13287
13191
|
const crawlSiteChecksSchema = z.object({ soft404: z.object({
|
|
13288
13192
|
status: z.enum([
|
|
13289
13193
|
"pass",
|
|
@@ -13301,8 +13205,7 @@ const crawlSiteChecksSchema = z.object({ soft404: z.object({
|
|
|
13301
13205
|
probeLimit: z.number().int().positive(),
|
|
13302
13206
|
complete: z.boolean()
|
|
13303
13207
|
}) });
|
|
13304
|
-
|
|
13305
|
-
//#region packages/core/src/analyze/crawler/site-crawl-ai-schema.ts
|
|
13208
|
+
// packages/core/src/analyze/crawler/site-crawl-ai-schema.ts
|
|
13306
13209
|
const crawlAiAvailabilitySchema = z.enum([
|
|
13307
13210
|
"available",
|
|
13308
13211
|
"absent",
|
|
@@ -13387,8 +13290,7 @@ const crawlAiSignalsSchema = z.object({
|
|
|
13387
13290
|
contentSignal: z.string().optional()
|
|
13388
13291
|
}).optional()
|
|
13389
13292
|
});
|
|
13390
|
-
|
|
13391
|
-
//#region packages/core/src/analyze/crawler/schemas.ts
|
|
13293
|
+
// packages/core/src/analyze/crawler/schemas.ts
|
|
13392
13294
|
const crawlerRuleSeveritySchema = z.enum([
|
|
13393
13295
|
"low",
|
|
13394
13296
|
"medium",
|
|
@@ -14346,8 +14248,7 @@ const crawlerJsonSchemas = {
|
|
|
14346
14248
|
requestObservation: z.toJSONSchema(crawlRequestObservationSchema),
|
|
14347
14249
|
pageSnapshot: z.toJSONSchema(crawlPageSnapshotSchema)
|
|
14348
14250
|
};
|
|
14349
|
-
|
|
14350
|
-
//#region packages/core/src/analyze/crawler/agent-ready.ts
|
|
14251
|
+
// packages/core/src/analyze/crawler/agent-ready.ts
|
|
14351
14252
|
function needsSignIn(page) {
|
|
14352
14253
|
return page.status === 401 || page.status === 403 || Boolean(page.softAuthenticationGate);
|
|
14353
14254
|
}
|
|
@@ -14398,8 +14299,7 @@ function agentReadyIssues(input) {
|
|
|
14398
14299
|
}
|
|
14399
14300
|
return issues;
|
|
14400
14301
|
}
|
|
14401
|
-
|
|
14402
|
-
//#region packages/core/src/analyze/crawler/crawl-caveats.ts
|
|
14302
|
+
// packages/core/src/analyze/crawler/crawl-caveats.ts
|
|
14403
14303
|
function crawlCaveats(input) {
|
|
14404
14304
|
const caveats = input.cancelled ? ["Crawl cancelled before all queued URLs finished."] : input.pageLimitReached ? [`Stopped after reaching maxPages (${input.maxPages}).`] : [];
|
|
14405
14305
|
if (input.queueSafetySkippedUrls > 0) caveats.push(input.queueSafetyInventoryCapped ? `Left at least ${input.queueSafetySkippedUrls} unique eligible same-origin URLs unqueued to keep this crawl bounded. The excluded-URL inventory reached its reporting cap. Increase --max-pages to inspect more.` : `Left ${input.queueSafetySkippedUrls} unique eligible same-origin URLs unqueued to keep this crawl bounded. Increase --max-pages to inspect more.`);
|
|
@@ -14407,8 +14307,7 @@ function crawlCaveats(input) {
|
|
|
14407
14307
|
if (input.memoryPressureSkippedUrls > 0) caveats.push(`Left ${input.memoryPressureSkippedUrls} eligible URL${input.memoryPressureSkippedUrls === 1 ? "" : "s"} unchecked after the local memory safety limit was reached. Start with --health or lower --max-pages.`);
|
|
14408
14308
|
return caveats;
|
|
14409
14309
|
}
|
|
14410
|
-
|
|
14411
|
-
//#region packages/core/src/analyze/crawler/crawl-control.ts
|
|
14310
|
+
// packages/core/src/analyze/crawler/crawl-control.ts
|
|
14412
14311
|
const CRAWL_CANCELLED = Symbol("crawl-cancelled");
|
|
14413
14312
|
function abortController(input) {
|
|
14414
14313
|
const controller = new AbortController();
|
|
@@ -14430,8 +14329,7 @@ function cancellationRace(signal) {
|
|
|
14430
14329
|
signal.addEventListener("abort", () => resolve(CRAWL_CANCELLED), { once: true });
|
|
14431
14330
|
});
|
|
14432
14331
|
}
|
|
14433
|
-
|
|
14434
|
-
//#region packages/core/src/analyze/crawler/crawl-memory.ts
|
|
14332
|
+
// packages/core/src/analyze/crawler/crawl-memory.ts
|
|
14435
14333
|
const MEBIBYTE$1 = 1048576;
|
|
14436
14334
|
const MIN_CRAWL_MEMORY_LIMIT = 384 * MEBIBYTE$1;
|
|
14437
14335
|
const MAX_CRAWL_MEMORY_LIMIT = 640 * MEBIBYTE$1;
|
|
@@ -14450,8 +14348,7 @@ function crawlRssLimitBytes(totalMemoryBytes) {
|
|
|
14450
14348
|
function crawlMemoryPressure(input) {
|
|
14451
14349
|
return input.memoryUsage.heapUsed + input.memoryUsage.external >= crawlMemoryLimitBytes(input.totalMemoryBytes) || input.memoryUsage.rss >= crawlRssLimitBytes(input.totalMemoryBytes);
|
|
14452
14350
|
}
|
|
14453
|
-
|
|
14454
|
-
//#region packages/core/src/analyze/crawler/crawl-url-queue.ts
|
|
14351
|
+
// packages/core/src/analyze/crawler/crawl-url-queue.ts
|
|
14455
14352
|
function before(left, right) {
|
|
14456
14353
|
return left.depth < right.depth || left.depth === right.depth && left.url < right.url;
|
|
14457
14354
|
}
|
|
@@ -14530,8 +14427,7 @@ var CrawlUrlQueue = class {
|
|
|
14530
14427
|
}
|
|
14531
14428
|
}
|
|
14532
14429
|
};
|
|
14533
|
-
|
|
14534
|
-
//#region packages/core/src/analyze/crawler/external-link-checks.ts
|
|
14430
|
+
// packages/core/src/analyze/crawler/external-link-checks.ts
|
|
14535
14431
|
const EXTERNAL_LINK_SELECTION_LIMIT = 200;
|
|
14536
14432
|
const EXTERNAL_LINK_STATES = [
|
|
14537
14433
|
"available",
|
|
@@ -14719,8 +14615,7 @@ async function verifyExternalLinks(input) {
|
|
|
14719
14615
|
warnings
|
|
14720
14616
|
};
|
|
14721
14617
|
}
|
|
14722
|
-
|
|
14723
|
-
//#region packages/core/src/analyze/crawler/link-graph.ts
|
|
14618
|
+
// packages/core/src/analyze/crawler/link-graph.ts
|
|
14724
14619
|
function resolveFetchedAlias(url, aliases) {
|
|
14725
14620
|
const visited = /* @__PURE__ */ new Set();
|
|
14726
14621
|
let current = url;
|
|
@@ -14740,8 +14635,7 @@ function resolveLinkCountAliases(counts, aliases) {
|
|
|
14740
14635
|
}
|
|
14741
14636
|
return resolved;
|
|
14742
14637
|
}
|
|
14743
|
-
|
|
14744
|
-
//#region packages/core/src/analyze/crawler/request-evidence.ts
|
|
14638
|
+
// packages/core/src/analyze/crawler/request-evidence.ts
|
|
14745
14639
|
function observationFromPage(requestedUrl, page) {
|
|
14746
14640
|
const observation = {
|
|
14747
14641
|
requestedUrl,
|
|
@@ -14764,8 +14658,7 @@ function observationFromPage(requestedUrl, page) {
|
|
|
14764
14658
|
extraction: page.extractionStatus ?? "complete"
|
|
14765
14659
|
};
|
|
14766
14660
|
}
|
|
14767
|
-
|
|
14768
|
-
//#region packages/core/src/analyze/crawler/site-checks.ts
|
|
14661
|
+
// packages/core/src/analyze/crawler/site-checks.ts
|
|
14769
14662
|
const PROBE_COUNT = 2;
|
|
14770
14663
|
function probeUrls(startUrl) {
|
|
14771
14664
|
const origin = new URL(startUrl).origin;
|
|
@@ -14833,8 +14726,7 @@ async function collectCrawlSiteChecks(input) {
|
|
|
14833
14726
|
}]
|
|
14834
14727
|
};
|
|
14835
14728
|
}
|
|
14836
|
-
|
|
14837
|
-
//#region packages/core/src/analyze/crawler/site-crawl-ai-signals.ts
|
|
14729
|
+
// packages/core/src/analyze/crawler/site-crawl-ai-signals.ts
|
|
14838
14730
|
const MAX_AUXILIARY_RESPONSE_BYTES = 2097152;
|
|
14839
14731
|
const LLMS_LINK_LIMIT = 20;
|
|
14840
14732
|
const AI_BOTS = [
|
|
@@ -14936,7 +14828,7 @@ function llmsLinks(text, base) {
|
|
|
14936
14828
|
} catch {}
|
|
14937
14829
|
return links;
|
|
14938
14830
|
}
|
|
14939
|
-
async function checkLlmsTxt(input) {
|
|
14831
|
+
async function checkLlmsTxt$1(input) {
|
|
14940
14832
|
const result = await get$1(input, new URL("/llms.txt", input.url).toString());
|
|
14941
14833
|
const availability = result.availability === "available" && isHtml(result.contentType) ? "absent" : result.availability;
|
|
14942
14834
|
const exists = availability === "available";
|
|
@@ -15064,7 +14956,7 @@ async function collectCrawlAiSignals(input) {
|
|
|
15064
14956
|
const allows = input.respectRobots ? robots.allows : () => true;
|
|
15065
14957
|
const startAllowed = allows(input.url);
|
|
15066
14958
|
const [llmsTxt, agentResources, markdownNegotiation] = await Promise.all([
|
|
15067
|
-
checkLlmsTxt(input),
|
|
14959
|
+
checkLlmsTxt$1(input),
|
|
15068
14960
|
checkAgentResources(input),
|
|
15069
14961
|
startAllowed ? checkMarkdownNegotiation(input) : Promise.resolve({
|
|
15070
14962
|
url: input.url,
|
|
@@ -15128,8 +15020,7 @@ function linkRelations(header) {
|
|
|
15128
15020
|
for (const match of (header ?? "").matchAll(/rel\s*=\s*"?([^";,]+)"?/gi)) for (const value of (match[1] ?? "").trim().toLowerCase().split(/\s+/)) if (value) relations.add(value);
|
|
15129
15021
|
return [...relations].sort();
|
|
15130
15022
|
}
|
|
15131
|
-
|
|
15132
|
-
//#region packages/core/src/analyze/crawler/site-crawl-providers.ts
|
|
15023
|
+
// packages/core/src/analyze/crawler/site-crawl-providers.ts
|
|
15133
15024
|
function crawlMetricsWindow(now) {
|
|
15134
15025
|
return {
|
|
15135
15026
|
...defaultDateRange$2(28, now),
|
|
@@ -15325,8 +15216,7 @@ async function joinSearchMetrics(input) {
|
|
|
15325
15216
|
...warning ? { warning } : {}
|
|
15326
15217
|
};
|
|
15327
15218
|
}
|
|
15328
|
-
|
|
15329
|
-
//#region packages/core/src/analyze/crawler/site-crawl-data-sources.ts
|
|
15219
|
+
// packages/core/src/analyze/crawler/site-crawl-data-sources.ts
|
|
15330
15220
|
function crawlProviderLimits(input) {
|
|
15331
15221
|
const normalize = (value, fallback, label) => {
|
|
15332
15222
|
const limit = value ?? fallback;
|
|
@@ -15396,8 +15286,7 @@ async function crawlDataSources(input) {
|
|
|
15396
15286
|
});
|
|
15397
15287
|
return dataSources;
|
|
15398
15288
|
}
|
|
15399
|
-
|
|
15400
|
-
//#region packages/core/src/analyze/monitoring/crawl-page.ts
|
|
15289
|
+
// packages/core/src/analyze/monitoring/crawl-page.ts
|
|
15401
15290
|
function resolvedUrl(href, base) {
|
|
15402
15291
|
if (!href) return void 0;
|
|
15403
15292
|
try {
|
|
@@ -15591,6 +15480,9 @@ async function crawlOne(url, opts = {}) {
|
|
|
15591
15480
|
warning: `${url}: ${message}`
|
|
15592
15481
|
};
|
|
15593
15482
|
}
|
|
15483
|
+
return snapshotFetchedPage(url, fetched);
|
|
15484
|
+
}
|
|
15485
|
+
async function snapshotFetchedPage(url, fetched) {
|
|
15594
15486
|
const contentType = headerValue(fetched.headers, "content-type");
|
|
15595
15487
|
if (fetched.diagnostics.accessBlock) {
|
|
15596
15488
|
const page = unextractedPage(fetched, contentType);
|
|
@@ -15793,8 +15685,7 @@ async function crawlStatusOnly(url, opts = {}) {
|
|
|
15793
15685
|
};
|
|
15794
15686
|
}
|
|
15795
15687
|
}
|
|
15796
|
-
|
|
15797
|
-
//#region packages/core/src/analyze/monitoring/sitemaps.ts
|
|
15688
|
+
// packages/core/src/analyze/monitoring/sitemaps.ts
|
|
15798
15689
|
const INVALID_LOC_SAMPLE_LIMIT = 10;
|
|
15799
15690
|
const LASTMOD_SAMPLE_LIMIT = 10;
|
|
15800
15691
|
const MAX_SITEMAP_BYTES = 52428800;
|
|
@@ -16205,8 +16096,7 @@ async function fetchSitemapUrls(input) {
|
|
|
16205
16096
|
warnings
|
|
16206
16097
|
};
|
|
16207
16098
|
}
|
|
16208
|
-
|
|
16209
|
-
//#region packages/core/src/providers/cache.ts
|
|
16099
|
+
// packages/core/src/providers/cache.ts
|
|
16210
16100
|
function stableValue(value) {
|
|
16211
16101
|
if (Array.isArray(value)) return value.map(stableValue);
|
|
16212
16102
|
if (!value || typeof value !== "object") return value;
|
|
@@ -16292,8 +16182,7 @@ function writeProviderCache(key, input, options = {}) {
|
|
|
16292
16182
|
taskIds: normalizedTaskIds
|
|
16293
16183
|
};
|
|
16294
16184
|
}
|
|
16295
|
-
|
|
16296
|
-
//#region packages/core/src/storage/provider-secrets.ts
|
|
16185
|
+
// packages/core/src/storage/provider-secrets.ts
|
|
16297
16186
|
const KEYRING_SERVICE = "seo";
|
|
16298
16187
|
const PRIVATE_FILE_MODE$1 = 384;
|
|
16299
16188
|
const PROVIDER_SECRET_NAMES = {
|
|
@@ -16323,7 +16212,7 @@ function tightenFilePermissions(path) {
|
|
|
16323
16212
|
if (error.code !== "ENOENT") throw error;
|
|
16324
16213
|
}
|
|
16325
16214
|
}
|
|
16326
|
-
function readFile$
|
|
16215
|
+
function readFile$2() {
|
|
16327
16216
|
const path = getSeoCliPaths().providerSecretsFile;
|
|
16328
16217
|
tightenFilePermissions(path);
|
|
16329
16218
|
const raw = readJsonFile(path);
|
|
@@ -16344,7 +16233,7 @@ function writeFile$1(file) {
|
|
|
16344
16233
|
writeJsonAtomic(path, file, PRIVATE_FILE_MODE$1);
|
|
16345
16234
|
}
|
|
16346
16235
|
function deleteFileSecret(name) {
|
|
16347
|
-
const file = readFile$
|
|
16236
|
+
const file = readFile$2();
|
|
16348
16237
|
if (!(name in file.secrets)) return;
|
|
16349
16238
|
delete file.secrets[name];
|
|
16350
16239
|
writeFile$1(file);
|
|
@@ -16363,7 +16252,7 @@ async function readProviderSecret(input) {
|
|
|
16363
16252
|
source: "keychain"
|
|
16364
16253
|
};
|
|
16365
16254
|
} catch {}
|
|
16366
|
-
const value = readFile$
|
|
16255
|
+
const value = readFile$2().secrets[input.name];
|
|
16367
16256
|
return value ? {
|
|
16368
16257
|
value,
|
|
16369
16258
|
source: "file"
|
|
@@ -16377,7 +16266,7 @@ async function writeProviderSecret(name, value) {
|
|
|
16377
16266
|
deleteFileSecret(name);
|
|
16378
16267
|
return "keychain";
|
|
16379
16268
|
} catch {}
|
|
16380
|
-
const file = readFile$
|
|
16269
|
+
const file = readFile$2();
|
|
16381
16270
|
file.secrets[name] = normalized;
|
|
16382
16271
|
writeFile$1(file);
|
|
16383
16272
|
return "file";
|
|
@@ -16398,8 +16287,7 @@ async function deleteManagedProviderSecrets() {
|
|
|
16398
16287
|
const failed = results.flatMap((result, index) => result.status === "rejected" ? [MANAGED_PROVIDER_SECRET_NAMES[index]] : []);
|
|
16399
16288
|
if (failed.length > 0) throw new Error(`Reset could not remove ${failed.length} saved provider credential record${failed.length === 1 ? "" : "s"}. Unlock the keychain or repair the local provider secrets file, then run \`seo reset --yes\` again.`, { cause: new AggregateError(results.flatMap((result) => result.status === "rejected" ? [result.reason] : [])) });
|
|
16400
16289
|
}
|
|
16401
|
-
|
|
16402
|
-
//#region packages/core/src/clicky/credentials.ts
|
|
16290
|
+
// packages/core/src/clicky/credentials.ts
|
|
16403
16291
|
const CLICKY_SITEKEY_ENV = "SEO_CLICKY_SITEKEY";
|
|
16404
16292
|
const CLICKY_SITE_KEYS_SECRET = PROVIDER_SECRET_NAMES.clickySiteKeys;
|
|
16405
16293
|
const storedSiteKeysSchema = z.object({
|
|
@@ -16485,8 +16373,7 @@ async function deleteClickySiteKey(siteIdValue) {
|
|
|
16485
16373
|
siteKeys: stored.siteKeys
|
|
16486
16374
|
}));
|
|
16487
16375
|
}
|
|
16488
|
-
|
|
16489
|
-
//#region packages/core/src/provider-extensions/contracts.ts
|
|
16376
|
+
// packages/core/src/provider-extensions/contracts.ts
|
|
16490
16377
|
const identifierPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
|
|
16491
16378
|
const packageNamePattern = /^(?:@[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*|[a-z0-9][a-z0-9._-]*)$/u;
|
|
16492
16379
|
const exactVersionPattern = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u;
|
|
@@ -16637,8 +16524,7 @@ const providerRegistrationSchema = z.object({
|
|
|
16637
16524
|
actionIds.add(action.id);
|
|
16638
16525
|
}
|
|
16639
16526
|
});
|
|
16640
|
-
|
|
16641
|
-
//#region packages/core/src/provider-extensions/credentials.ts
|
|
16527
|
+
// packages/core/src/provider-extensions/credentials.ts
|
|
16642
16528
|
const storedCredentialsSchema = z.object({
|
|
16643
16529
|
version: z.literal(1),
|
|
16644
16530
|
providers: z.record(providerExtensionIdSchema, z.record(z.string().length(64).regex(/^[a-f0-9]+$/u), z.record(z.string().min(1).max(64), z.string().min(1).max(4096))))
|
|
@@ -16708,8 +16594,7 @@ async function deleteProviderExtensionCredentials(input) {
|
|
|
16708
16594
|
}
|
|
16709
16595
|
await writeProviderSecret(PROVIDER_SECRET_NAMES.providerExtensionCredentials, JSON.stringify(stored));
|
|
16710
16596
|
}
|
|
16711
|
-
|
|
16712
|
-
//#region packages/core/src/providers/transport.ts
|
|
16597
|
+
// packages/core/src/providers/transport.ts
|
|
16713
16598
|
function schemaIssueSummary(error) {
|
|
16714
16599
|
const issues = error.issues.slice(0, 3).map((issue) => {
|
|
16715
16600
|
return `${issue.path.flatMap((segment) => {
|
|
@@ -16826,8 +16711,7 @@ async function providerRequestJson(input) {
|
|
|
16826
16711
|
}
|
|
16827
16712
|
return parsed.data;
|
|
16828
16713
|
}
|
|
16829
|
-
|
|
16830
|
-
//#region packages/core/src/provider-extensions/registry.ts
|
|
16714
|
+
// packages/core/src/provider-extensions/registry.ts
|
|
16831
16715
|
var ProviderExtensionRegistry = class {
|
|
16832
16716
|
#providers = /* @__PURE__ */ new Map();
|
|
16833
16717
|
hostFor(input) {
|
|
@@ -16860,8 +16744,7 @@ var ProviderExtensionRegistry = class {
|
|
|
16860
16744
|
return [...this.#providers.values()].sort((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0);
|
|
16861
16745
|
}
|
|
16862
16746
|
};
|
|
16863
|
-
|
|
16864
|
-
//#region packages/core/src/provider-extensions/store.ts
|
|
16747
|
+
// packages/core/src/provider-extensions/store.ts
|
|
16865
16748
|
const PRIVATE_FILE_MODE = 384;
|
|
16866
16749
|
function emptyStore() {
|
|
16867
16750
|
return {
|
|
@@ -16912,8 +16795,7 @@ function removeInstalledProviderPackage(id) {
|
|
|
16912
16795
|
});
|
|
16913
16796
|
return removed;
|
|
16914
16797
|
}
|
|
16915
|
-
|
|
16916
|
-
//#region packages/core/src/provider-extensions/loader.ts
|
|
16798
|
+
// packages/core/src/provider-extensions/loader.ts
|
|
16917
16799
|
const installedPackageJsonSchema = z.object({
|
|
16918
16800
|
name: z.string(),
|
|
16919
16801
|
version: z.string(),
|
|
@@ -16992,8 +16874,7 @@ async function loadInstalledProviderExtensions(options = {}) {
|
|
|
16992
16874
|
failures
|
|
16993
16875
|
};
|
|
16994
16876
|
}
|
|
16995
|
-
|
|
16996
|
-
//#region packages/core/src/provider-extensions/runtime.ts
|
|
16877
|
+
// packages/core/src/provider-extensions/runtime.ts
|
|
16997
16878
|
const DEFAULT_MAX_REQUESTS = 8;
|
|
16998
16879
|
const MAX_RESPONSE_BYTES$1 = 10485760;
|
|
16999
16880
|
const MAX_REQUEST_BODY_BYTES = 65536;
|
|
@@ -17064,8 +16945,7 @@ async function verifyProviderExtension(input) {
|
|
|
17064
16945
|
throw safeProviderAdapterError(input.providerId, error, input.credentials);
|
|
17065
16946
|
}
|
|
17066
16947
|
}
|
|
17067
|
-
|
|
17068
|
-
//#region packages/core/src/provider-extensions/analytics.ts
|
|
16948
|
+
// packages/core/src/provider-extensions/analytics.ts
|
|
17069
16949
|
const CACHE_TTL_MS$1 = 864e5;
|
|
17070
16950
|
const landingPageResultSchema = z.object({
|
|
17071
16951
|
metric: z.literal("landing-page-visits"),
|
|
@@ -17146,8 +17026,7 @@ async function runAnalyticsProviderLandingPages(input) {
|
|
|
17146
17026
|
});
|
|
17147
17027
|
return parsed;
|
|
17148
17028
|
}
|
|
17149
|
-
|
|
17150
|
-
//#region packages/core/src/analyze/workflows/analytics-value.ts
|
|
17029
|
+
// packages/core/src/analyze/workflows/analytics-value.ts
|
|
17151
17030
|
const defaultDependencies$10 = {
|
|
17152
17031
|
runGa4Report,
|
|
17153
17032
|
analyticsLandingPages: runAnalyticsProviderLandingPages
|
|
@@ -17374,8 +17253,7 @@ async function fetchLandingPageValues(input, dependencies = defaultDependencies$
|
|
|
17374
17253
|
}
|
|
17375
17254
|
}[connection.provider](connection);
|
|
17376
17255
|
}
|
|
17377
|
-
|
|
17378
|
-
//#region packages/core/src/analyze/crawler/site-crawl-dependencies.ts
|
|
17256
|
+
// packages/core/src/analyze/crawler/site-crawl-dependencies.ts
|
|
17379
17257
|
function resolveCrawlSiteDependencies(dependencies = {}) {
|
|
17380
17258
|
const hasInjectedPerPageSearchProvider = Boolean(dependencies.queryPageMetrics || dependencies.queryPageTopQuery);
|
|
17381
17259
|
const hasInjectedLegacyBulkSearchProvider = Boolean(dependencies.queryPagesMetrics || dependencies.queryPagesTopQueries);
|
|
@@ -17400,8 +17278,7 @@ function resolveCrawlSiteDependencies(dependencies = {}) {
|
|
|
17400
17278
|
totalMemory: dependencies.totalMemory ?? totalmem
|
|
17401
17279
|
};
|
|
17402
17280
|
}
|
|
17403
|
-
|
|
17404
|
-
//#region packages/core/src/analyze/crawler/sitemap-discovery.ts
|
|
17281
|
+
// packages/core/src/analyze/crawler/sitemap-discovery.ts
|
|
17405
17282
|
const MAX_SITEMAP_ROOTS = 20;
|
|
17406
17283
|
function isSameOrigin(url, origin) {
|
|
17407
17284
|
try {
|
|
@@ -17489,8 +17366,7 @@ async function sitemapSeeds(input) {
|
|
|
17489
17366
|
}
|
|
17490
17367
|
};
|
|
17491
17368
|
}
|
|
17492
|
-
|
|
17493
|
-
//#region packages/core/src/analyze/crawler/site-crawl.ts
|
|
17369
|
+
// packages/core/src/analyze/crawler/site-crawl.ts
|
|
17494
17370
|
function normalizeUrl$1(value, base) {
|
|
17495
17371
|
try {
|
|
17496
17372
|
const url = base ? new URL(value, base) : new URL(value);
|
|
@@ -18094,8 +17970,7 @@ async function crawlSite(input, dependencies) {
|
|
|
18094
17970
|
await renderer?.close();
|
|
18095
17971
|
}
|
|
18096
17972
|
}
|
|
18097
|
-
|
|
18098
|
-
//#region packages/core/src/brand-evidence.ts
|
|
17973
|
+
// packages/core/src/brand-evidence.ts
|
|
18099
17974
|
const NAVIGATIONAL_STEM_THRESHOLDS = {
|
|
18100
17975
|
maximumPosition: 2,
|
|
18101
17976
|
minimumCtr: .2,
|
|
@@ -18227,8 +18102,7 @@ function resolveReportBrandTerms(input) {
|
|
|
18227
18102
|
heuristic: true
|
|
18228
18103
|
};
|
|
18229
18104
|
}
|
|
18230
|
-
|
|
18231
|
-
//#region packages/core/src/analyze/min-impressions-policy.ts
|
|
18105
|
+
// packages/core/src/analyze/min-impressions-policy.ts
|
|
18232
18106
|
const POSITION_MIN_IMPRESSIONS_SCALE = {
|
|
18233
18107
|
share: 2e-4,
|
|
18234
18108
|
floor: 10,
|
|
@@ -18280,8 +18154,7 @@ function minImpressionsCaveat(input) {
|
|
|
18280
18154
|
const oneIn = Math.round(1 / policy.scale.share).toLocaleString("en-US");
|
|
18281
18155
|
return `Minimum impressions per query/page row: ${policy.effective}, scaled from ${policy.basisImpressions} retained impressions (1 in ${oneIn}, between ${policy.scale.floor} and ${policy.scale.ceiling}); ${below}. This scaling is a heuristic.`;
|
|
18282
18156
|
}
|
|
18283
|
-
|
|
18284
|
-
//#region packages/core/src/analyze/page-fragments.ts
|
|
18157
|
+
// packages/core/src/analyze/page-fragments.ts
|
|
18285
18158
|
function compareCodepoints(left, right) {
|
|
18286
18159
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
18287
18160
|
}
|
|
@@ -18357,8 +18230,7 @@ function collapseFragmentPageRows(rows, options = {}) {
|
|
|
18357
18230
|
}
|
|
18358
18231
|
};
|
|
18359
18232
|
}
|
|
18360
|
-
|
|
18361
|
-
//#region packages/core/src/analyze/site-ctr-curve.ts
|
|
18233
|
+
// packages/core/src/analyze/site-ctr-curve.ts
|
|
18362
18234
|
const SITE_CTR_CURVE_FALLBACK_SOURCE = "builtin_position_ctr_curve_v1";
|
|
18363
18235
|
const SITE_CTR_CURVE_METHOD = {
|
|
18364
18236
|
method: "position_bucket_url_p75_v2",
|
|
@@ -18487,8 +18359,7 @@ function createSiteCtrCurve(rows) {
|
|
|
18487
18359
|
fallbackSource: SITE_CTR_CURVE_FALLBACK_SOURCE
|
|
18488
18360
|
});
|
|
18489
18361
|
}
|
|
18490
|
-
|
|
18491
|
-
//#region packages/core/src/analyze/ctr-underperformers-analysis.ts
|
|
18362
|
+
// packages/core/src/analyze/ctr-underperformers-analysis.ts
|
|
18492
18363
|
const CTR_MAX_MIN_IMPRESSIONS = 1e9;
|
|
18493
18364
|
const CTR_DEFAULT_LIMIT = 25;
|
|
18494
18365
|
const CTR_MAX_LIMIT = 100;
|
|
@@ -18626,8 +18497,7 @@ function analyzeCtrUnderperformersFromRows(input) {
|
|
|
18626
18497
|
benchmarkByPosition: benchmarkContext.byPosition
|
|
18627
18498
|
};
|
|
18628
18499
|
}
|
|
18629
|
-
|
|
18630
|
-
//#region packages/core/src/analyze/ctr-underperformers-report.ts
|
|
18500
|
+
// packages/core/src/analyze/ctr-underperformers-report.ts
|
|
18631
18501
|
const MAX_GSC_ROWS$3 = 1e5;
|
|
18632
18502
|
const DEFAULT_DAYS$3 = 28;
|
|
18633
18503
|
const MAX_DAYS$2 = 548;
|
|
@@ -18761,8 +18631,7 @@ async function ctrUnderperformersReportWith(input, dependencies) {
|
|
|
18761
18631
|
] : dataStatus === "partial" ? ["No CTR-only action is recommended from the validated retained rows. Refresh or inspect the provider evidence before treating this as an all-clear."] : ["No CTR-only action is recommended from this report. Use striking-distance or page-opportunities if you want more growth ideas."]
|
|
18762
18632
|
};
|
|
18763
18633
|
}
|
|
18764
|
-
|
|
18765
|
-
//#region packages/core/src/analyze/ctr-underperformers.ts
|
|
18634
|
+
// packages/core/src/analyze/ctr-underperformers.ts
|
|
18766
18635
|
const defaultDependencies$9 = {
|
|
18767
18636
|
searchAnalytics: querySearchAnalytics,
|
|
18768
18637
|
now: () => /* @__PURE__ */ new Date()
|
|
@@ -18770,16 +18639,14 @@ const defaultDependencies$9 = {
|
|
|
18770
18639
|
function ctrUnderperformersReport(input, dependencies = defaultDependencies$9) {
|
|
18771
18640
|
return ctrUnderperformersReportWith(input, dependencies);
|
|
18772
18641
|
}
|
|
18773
|
-
|
|
18774
|
-
//#region packages/core/src/phrasing.ts
|
|
18642
|
+
// packages/core/src/phrasing.ts
|
|
18775
18643
|
function plural(count, singular, pluralLabel = `${singular}s`) {
|
|
18776
18644
|
return count === 1 ? singular : pluralLabel;
|
|
18777
18645
|
}
|
|
18778
18646
|
function countLabel(count, singular, pluralLabel = `${singular}s`) {
|
|
18779
18647
|
return `${count.toLocaleString("en-GB")} ${plural(count, singular, pluralLabel)}`;
|
|
18780
18648
|
}
|
|
18781
|
-
|
|
18782
|
-
//#region packages/core/src/analyze/diagnosis-priorities.ts
|
|
18649
|
+
// packages/core/src/analyze/diagnosis-priorities.ts
|
|
18783
18650
|
function topDelta(report) {
|
|
18784
18651
|
const top = report.items[0];
|
|
18785
18652
|
if (!top) return "No segment movement found.";
|
|
@@ -18837,8 +18704,7 @@ function buildDiagnosisPriorities(input) {
|
|
|
18837
18704
|
}
|
|
18838
18705
|
return priorities.slice(0, 6);
|
|
18839
18706
|
}
|
|
18840
|
-
|
|
18841
|
-
//#region packages/core/src/analyze/diagnosis-status.ts
|
|
18707
|
+
// packages/core/src/analyze/diagnosis-status.ts
|
|
18842
18708
|
function diagnosisPartialReasons(report) {
|
|
18843
18709
|
const skippedSections = new Set(report.skippedSections?.map((section) => section.section));
|
|
18844
18710
|
const segmentReasons = [
|
|
@@ -18890,8 +18756,7 @@ function diagnosisDataStatus(input) {
|
|
|
18890
18756
|
if (input.criticalStatuses.length > 0 && input.criticalStatuses.every((status) => status === "skipped")) return "unavailable";
|
|
18891
18757
|
return input.skippedSections || input.partialReasons ? "partial" : "complete";
|
|
18892
18758
|
}
|
|
18893
|
-
|
|
18894
|
-
//#region packages/core/src/analyze/segment-impact-rows.ts
|
|
18759
|
+
// packages/core/src/analyze/segment-impact-rows.ts
|
|
18895
18760
|
function fixed(value) {
|
|
18896
18761
|
return Number(value.toFixed(3));
|
|
18897
18762
|
}
|
|
@@ -18984,8 +18849,7 @@ function compareRetainedSegmentRows(input) {
|
|
|
18984
18849
|
unmatched
|
|
18985
18850
|
};
|
|
18986
18851
|
}
|
|
18987
|
-
|
|
18988
|
-
//#region packages/core/src/analyze/segment-impact-analysis.ts
|
|
18852
|
+
// packages/core/src/analyze/segment-impact-analysis.ts
|
|
18989
18853
|
function verdict$4(input) {
|
|
18990
18854
|
if (input.status === "empty") return "Search Console returned no retained segment rows for either window.";
|
|
18991
18855
|
if (!input.matchedRows) return "No segment was retained in both windows, so no movement delta was inferred.";
|
|
@@ -19096,8 +18960,7 @@ function analyzeSegmentRows(input) {
|
|
|
19096
18960
|
]
|
|
19097
18961
|
};
|
|
19098
18962
|
}
|
|
19099
|
-
|
|
19100
|
-
//#region packages/core/src/analyze/segment-impact-input.ts
|
|
18963
|
+
// packages/core/src/analyze/segment-impact-input.ts
|
|
19101
18964
|
const DAY_MS$1 = 864e5;
|
|
19102
18965
|
const MAX_WINDOW_DAYS = 240;
|
|
19103
18966
|
function subtractDays(value, days) {
|
|
@@ -19152,8 +19015,7 @@ function resolveSegmentRanges(input) {
|
|
|
19152
19015
|
function validateSegmentSite(site) {
|
|
19153
19016
|
if (!site.trim()) throw new SeoError("INVALID_INPUT", "site must not be empty.");
|
|
19154
19017
|
}
|
|
19155
|
-
|
|
19156
|
-
//#region packages/core/src/analyze/segment-impact-fallback.ts
|
|
19018
|
+
// packages/core/src/analyze/segment-impact-fallback.ts
|
|
19157
19019
|
function unavailableSegmentImpactReport(input) {
|
|
19158
19020
|
const days = segmentRangeDays(input.after);
|
|
19159
19021
|
const before = segmentComparisonRange(input.after, days);
|
|
@@ -19232,8 +19094,7 @@ function unavailableSegmentImpactReport(input) {
|
|
|
19232
19094
|
caveats: ["No segment movement conclusion was produced because the provider evidence was unavailable."]
|
|
19233
19095
|
};
|
|
19234
19096
|
}
|
|
19235
|
-
|
|
19236
|
-
//#region packages/core/src/analyze/segment-impact.ts
|
|
19097
|
+
// packages/core/src/analyze/segment-impact.ts
|
|
19237
19098
|
const DEFAULT_MAX_ROWS = 1e5;
|
|
19238
19099
|
const defaultDependencies$8 = {
|
|
19239
19100
|
searchAnalytics: querySearchAnalytics,
|
|
@@ -19342,8 +19203,7 @@ async function segmentImpact(input, dependencies = defaultDependencies$8) {
|
|
|
19342
19203
|
generatedAt: now.toISOString()
|
|
19343
19204
|
});
|
|
19344
19205
|
}
|
|
19345
|
-
|
|
19346
|
-
//#region packages/core/src/imports/records.ts
|
|
19206
|
+
// packages/core/src/imports/records.ts
|
|
19347
19207
|
const DEFAULT_IMPORT_STREAM_BYTES = 5e7;
|
|
19348
19208
|
async function importFile(input) {
|
|
19349
19209
|
const path = resolve(input.file);
|
|
@@ -19550,8 +19410,7 @@ async function readJsonArray(input) {
|
|
|
19550
19410
|
if (!Array.isArray(parsed)) throw new SeoError("INVALID_INPUT", `${input.label} JSON must be an array of objects.`);
|
|
19551
19411
|
return parsed;
|
|
19552
19412
|
}
|
|
19553
|
-
|
|
19554
|
-
//#region packages/core/src/providers/imports/research-columns.ts
|
|
19413
|
+
// packages/core/src/providers/imports/research-columns.ts
|
|
19555
19414
|
const RESEARCH_IMPORT_COLUMN_KEYS = [
|
|
19556
19415
|
"keyword",
|
|
19557
19416
|
"url",
|
|
@@ -20166,8 +20025,7 @@ async function importResearchRows(source, now = /* @__PURE__ */ new Date()) {
|
|
|
20166
20025
|
warnings
|
|
20167
20026
|
};
|
|
20168
20027
|
}
|
|
20169
|
-
|
|
20170
|
-
//#region packages/core/src/providers/imports/research-provider.ts
|
|
20028
|
+
// packages/core/src/providers/imports/research-provider.ts
|
|
20171
20029
|
function compareText$11(left, right) {
|
|
20172
20030
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
20173
20031
|
}
|
|
@@ -20208,8 +20066,7 @@ async function combineResearchRows(sources, now = /* @__PURE__ */ new Date()) {
|
|
|
20208
20066
|
warnings
|
|
20209
20067
|
};
|
|
20210
20068
|
}
|
|
20211
|
-
|
|
20212
|
-
//#region packages/core/src/analyze/page-patterns.ts
|
|
20069
|
+
// packages/core/src/analyze/page-patterns.ts
|
|
20213
20070
|
const LOCAL_QUERY_TERMS = [
|
|
20214
20071
|
"near me",
|
|
20215
20072
|
"today",
|
|
@@ -20380,8 +20237,7 @@ function dominantTemplateFamily(rows) {
|
|
|
20380
20237
|
share: top.count / rows.length
|
|
20381
20238
|
};
|
|
20382
20239
|
}
|
|
20383
|
-
|
|
20384
|
-
//#region packages/core/src/analyze/site-diagnostics/cannibal-analysis-primitives.ts
|
|
20240
|
+
// packages/core/src/analyze/site-diagnostics/cannibal-analysis-primitives.ts
|
|
20385
20241
|
function compareCannibalText(left, right) {
|
|
20386
20242
|
const leftPoints = [...left].map((value) => value.codePointAt(0) ?? 0);
|
|
20387
20243
|
const rightPoints = [...right].map((value) => value.codePointAt(0) ?? 0);
|
|
@@ -20426,8 +20282,7 @@ function addCannibalRow(group, url, row) {
|
|
|
20426
20282
|
position: weightedPosition / impressions
|
|
20427
20283
|
});
|
|
20428
20284
|
}
|
|
20429
|
-
|
|
20430
|
-
//#region packages/core/src/analyze/site-diagnostics/cannibal-item.ts
|
|
20285
|
+
// packages/core/src/analyze/site-diagnostics/cannibal-item.ts
|
|
20431
20286
|
function cannibalReviewContext(query, rows) {
|
|
20432
20287
|
const context = [];
|
|
20433
20288
|
if (isQuotedQuery(query)) context.push("quoted-query");
|
|
@@ -20505,8 +20360,7 @@ function createCannibalItem(input) {
|
|
|
20505
20360
|
function compareCannibalItems(left, right) {
|
|
20506
20361
|
return right.priority.score - left.priority.score || right.priority.demandImpressions - left.priority.demandImpressions || compareCannibalText(left.query, right.query);
|
|
20507
20362
|
}
|
|
20508
|
-
|
|
20509
|
-
//#region packages/core/src/analyze/site-diagnostics/cannibal-import.ts
|
|
20363
|
+
// packages/core/src/analyze/site-diagnostics/cannibal-import.ts
|
|
20510
20364
|
const MAX_SUPPRESSIONS$1 = 100;
|
|
20511
20365
|
function siteScope(site) {
|
|
20512
20366
|
const withoutScDomain = site.trim().replace(/^sc-domain:/iu, "");
|
|
@@ -20784,8 +20638,7 @@ async function cannibalImportReport(input, dependencies = {}) {
|
|
|
20784
20638
|
recommendations: analysis.items.length ? ["Review the highest-volume candidates first. Confirm whether the URLs serve the same intent and inspect technical state before changing canonicals, redirects, or content.", "Run this report against Search Console for the same site to confirm real query exposure before consolidating URLs."] : partialEvidence ? ["No multi-URL action is recommended from the retained imported rows. Inspect or re-export the provider data before treating this result as an all-clear."] : ["No multi-URL action is recommended from the retained imported rows."]
|
|
20785
20639
|
};
|
|
20786
20640
|
}
|
|
20787
|
-
|
|
20788
|
-
//#region packages/core/src/storage/ledger.ts
|
|
20641
|
+
// packages/core/src/storage/ledger.ts
|
|
20789
20642
|
var SessionLedger = class {
|
|
20790
20643
|
entries = /* @__PURE__ */ new Map();
|
|
20791
20644
|
gscCalls = 0;
|
|
@@ -20815,8 +20668,7 @@ var SessionLedger = class {
|
|
|
20815
20668
|
return providerBits.join(" · ");
|
|
20816
20669
|
}
|
|
20817
20670
|
};
|
|
20818
|
-
|
|
20819
|
-
//#region packages/core/src/analyze/site-diagnostics/cannibal-analysis.ts
|
|
20671
|
+
// packages/core/src/analyze/site-diagnostics/cannibal-analysis.ts
|
|
20820
20672
|
const MINIMUM_PAGE_SHARE = .1;
|
|
20821
20673
|
const MINIMUM_PAGE_IMPRESSIONS = 10;
|
|
20822
20674
|
const MAXIMUM_DOMINANT_SHARE = .8;
|
|
@@ -20978,10 +20830,20 @@ function analyzeCannibalRows(input) {
|
|
|
20978
20830
|
templates: summarizeTemplates(returnedItems.flatMap((item) => item.pages))
|
|
20979
20831
|
};
|
|
20980
20832
|
}
|
|
20833
|
+
/**
|
|
20834
|
+
* Empty, partial, filtered or complete. Partial evidence (a possibly capped
|
|
20835
|
+
* source, invalid rows, or a candidate with no query-level demand row) never
|
|
20836
|
+
* becomes filtered, so it cannot read as an all-clear.
|
|
20837
|
+
*/
|
|
20838
|
+
function cannibalDataStatus(selection, possiblyTruncated) {
|
|
20839
|
+
if (selection.sourceRows === 0) return "empty";
|
|
20840
|
+
if (possiblyTruncated || selection.invalidRows + selection.propertyInvalidRows > 0 || selection.missingPropertyQueries > 0) return "partial";
|
|
20841
|
+
return selection.eligibleClusters === 0 ? "filtered" : "complete";
|
|
20842
|
+
}
|
|
20843
|
+
const CANNIBAL_MINIMUM_PAGE_IMPRESSIONS = MINIMUM_PAGE_IMPRESSIONS;
|
|
20981
20844
|
const CANNIBAL_MINIMUM_PAGE_SHARE = MINIMUM_PAGE_SHARE;
|
|
20982
20845
|
const CANNIBAL_MAXIMUM_DOMINANT_SHARE = MAXIMUM_DOMINANT_SHARE;
|
|
20983
|
-
|
|
20984
|
-
//#region packages/core/src/analyze/site-diagnostics/cannibal-report.ts
|
|
20846
|
+
// packages/core/src/analyze/site-diagnostics/cannibal-report.ts
|
|
20985
20847
|
const MAX_GSC_ROWS$2 = 1e5;
|
|
20986
20848
|
function verdict$3(input) {
|
|
20987
20849
|
if (input.status === "empty") return "Search Console returned no retained query/page rows for this window.";
|
|
@@ -21050,7 +20912,7 @@ async function cannibalReportWith(input, dependencies) {
|
|
|
21050
20912
|
const possiblyTruncated = pageTruncated || propertyTruncated;
|
|
21051
20913
|
const partialValidation = analysis.selection.invalidRows + analysis.selection.propertyInvalidRows > 0;
|
|
21052
20914
|
const completeness = possiblyTruncated && partialValidation ? "partial-and-possibly-truncated" : possiblyTruncated ? "possibly-truncated" : partialValidation ? "partial" : "complete";
|
|
21053
|
-
const dataStatus = analysis.selection
|
|
20915
|
+
const dataStatus = cannibalDataStatus(analysis.selection, possiblyTruncated);
|
|
21054
20916
|
return {
|
|
21055
20917
|
schemaVersion: 1,
|
|
21056
20918
|
dataSource: "search-console-api",
|
|
@@ -21134,8 +20996,7 @@ async function cannibalReportWith(input, dependencies) {
|
|
|
21134
20996
|
ledgerSummary: ledger.summary()
|
|
21135
20997
|
};
|
|
21136
20998
|
}
|
|
21137
|
-
|
|
21138
|
-
//#region packages/core/src/analyze/site-diagnostics/cannibal.ts
|
|
20999
|
+
// packages/core/src/analyze/site-diagnostics/cannibal.ts
|
|
21139
21000
|
const defaultDependencies$7 = {
|
|
21140
21001
|
searchAnalytics: querySearchAnalytics,
|
|
21141
21002
|
now: () => /* @__PURE__ */ new Date()
|
|
@@ -21160,8 +21021,7 @@ async function cannibalReport(input, dependencies = defaultDependencies$7) {
|
|
|
21160
21021
|
}
|
|
21161
21022
|
return cannibalReportWith(input, dependencies);
|
|
21162
21023
|
}
|
|
21163
|
-
|
|
21164
|
-
//#region packages/core/src/analyze/site-diagnostics/decay-analysis-primitives.ts
|
|
21024
|
+
// packages/core/src/analyze/site-diagnostics/decay-analysis-primitives.ts
|
|
21165
21025
|
function compareDecayText(left, right) {
|
|
21166
21026
|
const leftPoints = [...left].map((value) => value.codePointAt(0) ?? 0);
|
|
21167
21027
|
const rightPoints = [...right].map((value) => value.codePointAt(0) ?? 0);
|
|
@@ -21300,8 +21160,7 @@ function boundedDecayNumber(value, fallback, min, max) {
|
|
|
21300
21160
|
if (value === void 0 || !Number.isFinite(value)) return fallback;
|
|
21301
21161
|
return Math.min(max, Math.max(min, value));
|
|
21302
21162
|
}
|
|
21303
|
-
|
|
21304
|
-
//#region packages/core/src/analyze/site-diagnostics/decay-analysis.ts
|
|
21163
|
+
// packages/core/src/analyze/site-diagnostics/decay-analysis.ts
|
|
21305
21164
|
function analyzeDecay(input) {
|
|
21306
21165
|
const minDropPct = boundedDecayNumber(input.minDropPct, 20, 0, 100);
|
|
21307
21166
|
const minPreviousClicks = boundedDecayNumber(input.minPreviousClicks, 2, 0, 1e9);
|
|
@@ -21424,8 +21283,7 @@ function analyzeDecay(input) {
|
|
|
21424
21283
|
templates: summarizeTemplates(returned)
|
|
21425
21284
|
};
|
|
21426
21285
|
}
|
|
21427
|
-
|
|
21428
|
-
//#region packages/core/src/analyze/site-diagnostics/decaying-report.ts
|
|
21286
|
+
// packages/core/src/analyze/site-diagnostics/decaying-report.ts
|
|
21429
21287
|
const MAX_GSC_ROWS$1 = 1e5;
|
|
21430
21288
|
function verdict$2(report) {
|
|
21431
21289
|
if (report.dataStatus === "empty") return "Search Console returned no retained query/page rows for either comparison window.";
|
|
@@ -21596,8 +21454,7 @@ async function decayingReportWith(input, dependencies) {
|
|
|
21596
21454
|
]
|
|
21597
21455
|
};
|
|
21598
21456
|
}
|
|
21599
|
-
|
|
21600
|
-
//#region packages/core/src/analyze/site-diagnostics/decaying.ts
|
|
21457
|
+
// packages/core/src/analyze/site-diagnostics/decaying.ts
|
|
21601
21458
|
const defaultDependencies$6 = {
|
|
21602
21459
|
searchAnalytics: querySearchAnalytics,
|
|
21603
21460
|
now: () => /* @__PURE__ */ new Date()
|
|
@@ -21605,8 +21462,7 @@ const defaultDependencies$6 = {
|
|
|
21605
21462
|
function decayingReport(input, dependencies = defaultDependencies$6) {
|
|
21606
21463
|
return decayingReportWith(input, dependencies);
|
|
21607
21464
|
}
|
|
21608
|
-
|
|
21609
|
-
//#region packages/core/src/analyze/site-diagnostics/quick-win-groups.ts
|
|
21465
|
+
// packages/core/src/analyze/site-diagnostics/quick-win-groups.ts
|
|
21610
21466
|
function normalizedQuery(value) {
|
|
21611
21467
|
return value.normalize("NFKC").toLocaleLowerCase("und").replace(/\s+/gu, " ").trim();
|
|
21612
21468
|
}
|
|
@@ -21651,8 +21507,7 @@ function groupQuickWins(items) {
|
|
|
21651
21507
|
return group;
|
|
21652
21508
|
}).filter((group) => group !== void 0).sort((left, right) => right.totalEstimatedCtrClickShortfall - left.totalEstimatedCtrClickShortfall || right.totalImpressions - left.totalImpressions || compareText$10(left.id, right.id));
|
|
21653
21509
|
}
|
|
21654
|
-
|
|
21655
|
-
//#region packages/core/src/analyze/site-diagnostics/quick-wins-analysis-primitives.ts
|
|
21510
|
+
// packages/core/src/analyze/site-diagnostics/quick-wins-analysis-primitives.ts
|
|
21656
21511
|
function boundedInteger$3(value, fallback, minimum, maximum) {
|
|
21657
21512
|
if (value === void 0 || !Number.isFinite(value)) return fallback;
|
|
21658
21513
|
return Math.min(maximum, Math.max(minimum, Math.floor(value)));
|
|
@@ -21752,8 +21607,7 @@ function compareQuickWinItems(left, right) {
|
|
|
21752
21607
|
function roundedSum(values) {
|
|
21753
21608
|
return Number(values.reduce((sum, value) => sum + value, 0).toFixed(2));
|
|
21754
21609
|
}
|
|
21755
|
-
|
|
21756
|
-
//#region packages/core/src/analyze/site-diagnostics/quick-wins-analysis.ts
|
|
21610
|
+
// packages/core/src/analyze/site-diagnostics/quick-wins-analysis.ts
|
|
21757
21611
|
const MAX_MIN_IMPRESSIONS$2 = 1e9;
|
|
21758
21612
|
const DEFAULT_LIMIT$2 = 25;
|
|
21759
21613
|
const MAX_LIMIT$2 = 100;
|
|
@@ -21860,8 +21714,7 @@ function analyzeQuickWinsFromRows(input) {
|
|
|
21860
21714
|
benchmarkByPosition: benchmarkContext.byPosition
|
|
21861
21715
|
};
|
|
21862
21716
|
}
|
|
21863
|
-
|
|
21864
|
-
//#region packages/core/src/analyze/site-diagnostics/quick-wins-templates.ts
|
|
21717
|
+
// packages/core/src/analyze/site-diagnostics/quick-wins-templates.ts
|
|
21865
21718
|
function compareText$8(left, right) {
|
|
21866
21719
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
21867
21720
|
}
|
|
@@ -21910,8 +21763,7 @@ function quickWinTemplateSummaries(items) {
|
|
|
21910
21763
|
sampleUrls: [...item.urls].sort(compareText$8).slice(0, 3)
|
|
21911
21764
|
})).sort((left, right) => right.urlCount - left.urlCount || right.rowCount - left.rowCount || compareText$8(left.id, right.id)).slice(0, 5);
|
|
21912
21765
|
}
|
|
21913
|
-
|
|
21914
|
-
//#region packages/core/src/analyze/site-diagnostics/quick-wins-report.ts
|
|
21766
|
+
// packages/core/src/analyze/site-diagnostics/quick-wins-report.ts
|
|
21915
21767
|
const DEFAULT_DAYS$2 = 28;
|
|
21916
21768
|
const MAX_DAYS$1 = 548;
|
|
21917
21769
|
const DEFAULT_VERIFY_LIMIT$1 = 5;
|
|
@@ -22097,8 +21949,7 @@ async function quickWinsReportWith(input, dependencies) {
|
|
|
22097
21949
|
warnings
|
|
22098
21950
|
};
|
|
22099
21951
|
}
|
|
22100
|
-
|
|
22101
|
-
//#region packages/core/src/analyze/site-diagnostics/quick-wins.ts
|
|
21952
|
+
// packages/core/src/analyze/site-diagnostics/quick-wins.ts
|
|
22102
21953
|
const defaultDependencies$5 = {
|
|
22103
21954
|
searchAnalytics: querySearchAnalytics,
|
|
22104
21955
|
fetch: fetchPage,
|
|
@@ -22108,8 +21959,7 @@ const defaultDependencies$5 = {
|
|
|
22108
21959
|
function quickWinsReport(input, dependencies = defaultDependencies$5) {
|
|
22109
21960
|
return quickWinsReportWith(input, dependencies);
|
|
22110
21961
|
}
|
|
22111
|
-
|
|
22112
|
-
//#region packages/core/src/analyze/striking-distance-analysis-primitives.ts
|
|
21962
|
+
// packages/core/src/analyze/striking-distance-analysis-primitives.ts
|
|
22113
21963
|
function compareText$7(left, right) {
|
|
22114
21964
|
const leftPoints = [...left].map((character) => character.codePointAt(0) ?? 0);
|
|
22115
21965
|
const rightPoints = [...right].map((character) => character.codePointAt(0) ?? 0);
|
|
@@ -22216,8 +22066,7 @@ function groupStrikingDistanceItems(items) {
|
|
|
22216
22066
|
};
|
|
22217
22067
|
}).filter((group) => group !== void 0).sort((left, right) => right.totalImpressions - left.totalImpressions || right.uniqueUrls - left.uniqueUrls || compareText$7(left.id, right.id));
|
|
22218
22068
|
}
|
|
22219
|
-
|
|
22220
|
-
//#region packages/core/src/analyze/striking-distance-analysis.ts
|
|
22069
|
+
// packages/core/src/analyze/striking-distance-analysis.ts
|
|
22221
22070
|
const MAX_MIN_IMPRESSIONS$1 = 1e9;
|
|
22222
22071
|
const DEFAULT_LIMIT$1 = 25;
|
|
22223
22072
|
const MAX_LIMIT$1 = 100;
|
|
@@ -22356,8 +22205,7 @@ function analyzeStrikingDistanceRows(input) {
|
|
|
22356
22205
|
items
|
|
22357
22206
|
};
|
|
22358
22207
|
}
|
|
22359
|
-
|
|
22360
|
-
//#region packages/core/src/analyze/striking-distance-report.ts
|
|
22208
|
+
// packages/core/src/analyze/striking-distance-report.ts
|
|
22361
22209
|
const DEFAULT_DAYS$1 = 28;
|
|
22362
22210
|
const MAX_DAYS = 548;
|
|
22363
22211
|
const DEFAULT_VERIFY_LIMIT = 5;
|
|
@@ -22512,8 +22360,7 @@ async function strikingDistanceWith(input, dependencies) {
|
|
|
22512
22360
|
recommendations: analysis.groups.length ? analysis.groups.slice(0, 5).map((group) => group.recommendation.action) : ["No action is recommended from this report. Lower the minimum impressions or widen the date window if you need long-tail inspection."]
|
|
22513
22361
|
};
|
|
22514
22362
|
}
|
|
22515
|
-
|
|
22516
|
-
//#region packages/core/src/analyze/striking-distance.ts
|
|
22363
|
+
// packages/core/src/analyze/striking-distance.ts
|
|
22517
22364
|
const defaultDependencies$4 = {
|
|
22518
22365
|
searchAnalytics: querySearchAnalytics,
|
|
22519
22366
|
verifyContent: verifyQueryContent,
|
|
@@ -22522,8 +22369,7 @@ const defaultDependencies$4 = {
|
|
|
22522
22369
|
function strikingDistance(input, dependencies = defaultDependencies$4) {
|
|
22523
22370
|
return strikingDistanceWith(input, dependencies);
|
|
22524
22371
|
}
|
|
22525
|
-
|
|
22526
|
-
//#region packages/core/src/updates/search-status.ts
|
|
22372
|
+
// packages/core/src/updates/search-status.ts
|
|
22527
22373
|
const INCIDENTS_URL = "https://status.search.google.com/incidents.json";
|
|
22528
22374
|
async function fetchSearchStatusIncidents() {
|
|
22529
22375
|
const response = await fetch$1(INCIDENTS_URL, { signal: AbortSignal.timeout(8e3) });
|
|
@@ -22533,8 +22379,7 @@ async function fetchSearchStatusIncidents() {
|
|
|
22533
22379
|
async function listSearchUpdates(input = {}) {
|
|
22534
22380
|
return searchUpdatesFromIncidents(withRankingHistory(await fetchSearchStatusIncidents()), input);
|
|
22535
22381
|
}
|
|
22536
|
-
|
|
22537
|
-
//#region packages/core/src/analyze/traffic-anomaly-analysis.ts
|
|
22382
|
+
// packages/core/src/analyze/traffic-anomaly-analysis.ts
|
|
22538
22383
|
/** The step-change heuristic and its thresholds, returned with the report. */
|
|
22539
22384
|
const STEP_CHANGE_RULES = {
|
|
22540
22385
|
method: "best-split",
|
|
@@ -22890,8 +22735,7 @@ function analyzeTrafficAnomalyRows(input) {
|
|
|
22890
22735
|
}
|
|
22891
22736
|
};
|
|
22892
22737
|
}
|
|
22893
|
-
|
|
22894
|
-
//#region packages/core/src/analyze/traffic-anomaly-report.ts
|
|
22738
|
+
// packages/core/src/analyze/traffic-anomaly-report.ts
|
|
22895
22739
|
async function trafficAnomalyWith(input, dependencies) {
|
|
22896
22740
|
const days = input.days ?? 90;
|
|
22897
22741
|
const recentDays = input.recentDays ?? 7;
|
|
@@ -22934,8 +22778,7 @@ async function trafficAnomalyWith(input, dependencies) {
|
|
|
22934
22778
|
detection: analyzed.detection
|
|
22935
22779
|
};
|
|
22936
22780
|
}
|
|
22937
|
-
|
|
22938
|
-
//#region packages/core/src/analyze/traffic-anomaly.ts
|
|
22781
|
+
// packages/core/src/analyze/traffic-anomaly.ts
|
|
22939
22782
|
const defaultDependencies$3 = {
|
|
22940
22783
|
searchAnalytics: querySearchAnalytics,
|
|
22941
22784
|
now: () => /* @__PURE__ */ new Date()
|
|
@@ -22982,8 +22825,7 @@ async function updateCorrelation(input) {
|
|
|
22982
22825
|
})
|
|
22983
22826
|
};
|
|
22984
22827
|
}
|
|
22985
|
-
|
|
22986
|
-
//#region packages/core/src/analyze/diagnose-property.ts
|
|
22828
|
+
// packages/core/src/analyze/diagnose-property.ts
|
|
22987
22829
|
function errorReason(error) {
|
|
22988
22830
|
return error instanceof Error ? error.message : String(error);
|
|
22989
22831
|
}
|
|
@@ -23519,8 +23361,7 @@ async function diagnoseProperty(input, dependencies = {}) {
|
|
|
23519
23361
|
quickWins
|
|
23520
23362
|
};
|
|
23521
23363
|
}
|
|
23522
|
-
|
|
23523
|
-
//#region packages/core/src/decision-model.ts
|
|
23364
|
+
// packages/core/src/decision-model.ts
|
|
23524
23365
|
var DecisionModelError = class extends Error {
|
|
23525
23366
|
kind;
|
|
23526
23367
|
retryAfterMs;
|
|
@@ -23673,8 +23514,7 @@ var DecisionRunner = class {
|
|
|
23673
23514
|
return null;
|
|
23674
23515
|
}
|
|
23675
23516
|
};
|
|
23676
|
-
|
|
23677
|
-
//#region packages/core/src/analyze/internal-links-index.ts
|
|
23517
|
+
// packages/core/src/analyze/internal-links-index.ts
|
|
23678
23518
|
const PAGE_TERMS = 300;
|
|
23679
23519
|
const VECTOR_TERMS = 100;
|
|
23680
23520
|
var SiteIndex = class {
|
|
@@ -23801,7 +23641,7 @@ const GENERIC_ANCHORS = /^(click here|here|read more|learn more|more|this|this p
|
|
|
23801
23641
|
function normalise(value) {
|
|
23802
23642
|
return value.normalize("NFKD").replace(/\p{Diacritic}/gu, "").normalize("NFC").replace(/([\p{L}\p{N}])[’'`]([\p{L}\p{N}])/gu, "$1$2").toLowerCase().replace(/&/g, " and ").replace(/[^\p{L}\p{N}]+/gu, " ").replace(/\s+/g, " ").trim();
|
|
23803
23643
|
}
|
|
23804
|
-
const words$
|
|
23644
|
+
const words$2 = (value) => normalise(value).split(" ").filter(Boolean);
|
|
23805
23645
|
const FUNCTION_WORDS = new Set("a an and are as at be but by for from has have how if in into is it its of on or that the their then there these they this to was we were what when where which who why will with you your our can do does not just more most also than too very about after before over under again all any each other some such no nor only own same so up out off one two both every few several many much should would could because while since although though until unless whether however".split(" "));
|
|
23806
23646
|
const NOT_PLURAL = /* @__PURE__ */ new Set([
|
|
23807
23647
|
"always",
|
|
@@ -23862,7 +23702,7 @@ function stem(word) {
|
|
|
23862
23702
|
if (one.length > 6 && one.endsWith("ing")) return one.slice(0, -3);
|
|
23863
23703
|
return one;
|
|
23864
23704
|
}
|
|
23865
|
-
const terms = (text) => words$
|
|
23705
|
+
const terms = (text) => words$2(text).filter((word) => word.length > 1 && !FUNCTION_WORDS.has(word) && !/^\d+$/.test(word)).map(stem);
|
|
23866
23706
|
const slugWords = (url) => {
|
|
23867
23707
|
try {
|
|
23868
23708
|
return new URL(url).pathname.split(/[/._-]+/).filter(Boolean).join(" ");
|
|
@@ -24148,8 +23988,7 @@ function placementPage(input) {
|
|
|
24148
23988
|
sentences: pageSentences(input.html)
|
|
24149
23989
|
};
|
|
24150
23990
|
}
|
|
24151
|
-
|
|
24152
|
-
//#region packages/core/src/analyze/internal-links-crawl.ts
|
|
23991
|
+
// packages/core/src/analyze/internal-links-crawl.ts
|
|
24153
23992
|
const DEFAULT_MAX_PAGES = 500;
|
|
24154
23993
|
const MAX_MAX_PAGES = 5e3;
|
|
24155
23994
|
const FETCH_CONCURRENCY = 6;
|
|
@@ -24521,8 +24360,7 @@ async function crawl(input, robots, sentences, renderer, warnings) {
|
|
|
24521
24360
|
warnings
|
|
24522
24361
|
};
|
|
24523
24362
|
}
|
|
24524
|
-
|
|
24525
|
-
//#region packages/core/src/analyze/internal-links-judge.ts
|
|
24363
|
+
// packages/core/src/analyze/internal-links-judge.ts
|
|
24526
24364
|
const PAGE_TYPES = {
|
|
24527
24365
|
pillar_guide: "A long, broad guide that covers a whole topic and would naturally link out to many narrower pages",
|
|
24528
24366
|
support_article: "A focused article, post, tutorial, or explainer about one narrower subject",
|
|
@@ -24746,8 +24584,7 @@ function decideWithoutModel(placement, similarity) {
|
|
|
24746
24584
|
anchor: placement.anchors.find((option) => option.sentence === sentence?.id)?.text ?? null
|
|
24747
24585
|
};
|
|
24748
24586
|
}
|
|
24749
|
-
|
|
24750
|
-
//#region packages/core/src/analyze/internal-links-report.ts
|
|
24587
|
+
// packages/core/src/analyze/internal-links-report.ts
|
|
24751
24588
|
const DAY$1 = 864e5;
|
|
24752
24589
|
const ISO_DAY = /^\d{4}-\d{2}-\d{2}$/;
|
|
24753
24590
|
function internalLinksReportRange(input, fallbackDays, now) {
|
|
@@ -24775,8 +24612,7 @@ function internalLinksVerdict(report) {
|
|
|
24775
24612
|
const capped = report.crawl.capReached ? ` The crawl stopped at its limit of ${report.crawl.pageLimit} pages, so pages beyond it were not checked.` : "";
|
|
24776
24613
|
return `${judged} ${scope}${counts.heldBack ? `; ${counts.heldBack} held back by the evidence floors` : ""}.${capped}`;
|
|
24777
24614
|
}
|
|
24778
|
-
|
|
24779
|
-
//#region packages/core/src/analyze/internal-links.ts
|
|
24615
|
+
// packages/core/src/analyze/internal-links.ts
|
|
24780
24616
|
const GSC_DAYS = 90;
|
|
24781
24617
|
const MAX_GSC_ROWS = 1e5;
|
|
24782
24618
|
const MAX_PAGES_PER_QUERY = 25;
|
|
@@ -25285,8 +25121,7 @@ function caveats$1(report) {
|
|
|
25285
25121
|
"Similarity, sentence match and anchors are text heuristics, and a model probability is a judgement of reader value. None of them predicts rankings or traffic."
|
|
25286
25122
|
];
|
|
25287
25123
|
}
|
|
25288
|
-
|
|
25289
|
-
//#region packages/core/src/analyze/url-templates.ts
|
|
25124
|
+
// packages/core/src/analyze/url-templates.ts
|
|
25290
25125
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
25291
25126
|
const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
25292
25127
|
function compareText$6(left, right) {
|
|
@@ -25415,8 +25250,7 @@ function clusterPseoTemplates(urls, opts = {}) {
|
|
|
25415
25250
|
sampleUrls: clusterUrls.sort(compareText$6).slice(0, sampleSize)
|
|
25416
25251
|
})).filter((cluster) => cluster.shape.variableSegments.length > 0 && cluster.urlCount >= minUrls && cluster.share >= minShare).sort((a, b) => b.urlCount - a.urlCount || compareText$6(a.signature, b.signature)).slice(0, opts.limit ?? 50);
|
|
25417
25252
|
}
|
|
25418
|
-
|
|
25419
|
-
//#region packages/core/src/analyze/monitoring/index-coverage.ts
|
|
25253
|
+
// packages/core/src/analyze/monitoring/index-coverage.ts
|
|
25420
25254
|
const DEFAULT_ITEMS_PER_SECTION = 100;
|
|
25421
25255
|
const MAX_ITEMS_PER_SECTION = 1e3;
|
|
25422
25256
|
const DEFAULT_TEMPLATE_LIMIT = 50;
|
|
@@ -25759,8 +25593,7 @@ function analyzeIndexCoverageSignals(input) {
|
|
|
25759
25593
|
})
|
|
25760
25594
|
};
|
|
25761
25595
|
}
|
|
25762
|
-
|
|
25763
|
-
//#region packages/core/src/analyze/monitoring/index-coverage-report.ts
|
|
25596
|
+
// packages/core/src/analyze/monitoring/index-coverage-report.ts
|
|
25764
25597
|
const MAX_SITEMAP_DOCUMENTS = 20;
|
|
25765
25598
|
const MAX_SEARCH_ROWS = 25e4;
|
|
25766
25599
|
const MAX_SITEMAP_URLS = 25e4;
|
|
@@ -25890,8 +25723,7 @@ async function indexCoverageSignals(input, dependencies = defaultDependencies$1)
|
|
|
25890
25723
|
}
|
|
25891
25724
|
};
|
|
25892
25725
|
}
|
|
25893
|
-
|
|
25894
|
-
//#region packages/core/src/analyze/monitoring/redirect-trace.ts
|
|
25726
|
+
// packages/core/src/analyze/monitoring/redirect-trace.ts
|
|
25895
25727
|
function resolveNextUrl(location, baseUrl) {
|
|
25896
25728
|
try {
|
|
25897
25729
|
const next = new URL(location, baseUrl);
|
|
@@ -25993,8 +25825,7 @@ async function redirectTrace(input) {
|
|
|
25993
25825
|
warnings
|
|
25994
25826
|
};
|
|
25995
25827
|
}
|
|
25996
|
-
|
|
25997
|
-
//#region packages/core/src/analyze/monitoring/link-recover.ts
|
|
25828
|
+
// packages/core/src/analyze/monitoring/link-recover.ts
|
|
25998
25829
|
function primaryIssue(issues) {
|
|
25999
25830
|
return [
|
|
26000
25831
|
"final-5xx",
|
|
@@ -26131,8 +25962,7 @@ async function linkRecover(input) {
|
|
|
26131
25962
|
warnings: [...new Set(warnings)].slice(0, 50)
|
|
26132
25963
|
};
|
|
26133
25964
|
}
|
|
26134
|
-
|
|
26135
|
-
//#region packages/core/src/analyze/performance-analysis.ts
|
|
25965
|
+
// packages/core/src/analyze/performance-analysis.ts
|
|
26136
25966
|
const FIELD_THRESHOLDS = {
|
|
26137
25967
|
CLS: {
|
|
26138
25968
|
good: .1,
|
|
@@ -26340,8 +26170,7 @@ function performanceActions(report) {
|
|
|
26340
26170
|
});
|
|
26341
26171
|
return actions;
|
|
26342
26172
|
}
|
|
26343
|
-
|
|
26344
|
-
//#region packages/core/src/analyze/performance-crux.ts
|
|
26173
|
+
// packages/core/src/analyze/performance-crux.ts
|
|
26345
26174
|
const CRUX_METRICS = [
|
|
26346
26175
|
"cumulative_layout_shift",
|
|
26347
26176
|
"interaction_to_next_paint",
|
|
@@ -26470,8 +26299,7 @@ async function fetchCruxFieldData(input) {
|
|
|
26470
26299
|
};
|
|
26471
26300
|
}
|
|
26472
26301
|
}
|
|
26473
|
-
|
|
26474
|
-
//#region packages/core/src/analyze/performance-lighthouse.ts
|
|
26302
|
+
// packages/core/src/analyze/performance-lighthouse.ts
|
|
26475
26303
|
const execFileAsync$1 = promisify(execFile);
|
|
26476
26304
|
const INSIGHT_BYTE_BUDGET = 24e3;
|
|
26477
26305
|
function compactRecord(value) {
|
|
@@ -26662,8 +26490,7 @@ async function runLighthouse(input) {
|
|
|
26662
26490
|
}
|
|
26663
26491
|
return report;
|
|
26664
26492
|
}
|
|
26665
|
-
|
|
26666
|
-
//#region packages/core/src/analyze/performance.ts
|
|
26493
|
+
// packages/core/src/analyze/performance.ts
|
|
26667
26494
|
const CACHE_TTL_MS = 864e5;
|
|
26668
26495
|
function reportId(input) {
|
|
26669
26496
|
return `perf_${createHash("sha256").update(JSON.stringify({
|
|
@@ -26853,8 +26680,7 @@ async function performanceAudit(input) {
|
|
|
26853
26680
|
})) cacheSet(report, now.getTime());
|
|
26854
26681
|
return report;
|
|
26855
26682
|
}
|
|
26856
|
-
|
|
26857
|
-
//#region packages/core/src/analyze/query-cluster-primitives.ts
|
|
26683
|
+
// packages/core/src/analyze/query-cluster-primitives.ts
|
|
26858
26684
|
const CLUSTER_THRESHOLD = .5;
|
|
26859
26685
|
const MAX_TOKEN_BUCKET = 250;
|
|
26860
26686
|
const MAX_TOKEN_SHARE = .08;
|
|
@@ -26970,8 +26796,7 @@ function* iterateQueryClusters(rows) {
|
|
|
26970
26796
|
yield group;
|
|
26971
26797
|
}
|
|
26972
26798
|
}
|
|
26973
|
-
|
|
26974
|
-
//#region packages/core/src/analyze/query-cluster-analysis.ts
|
|
26799
|
+
// packages/core/src/analyze/query-cluster-analysis.ts
|
|
26975
26800
|
function quotedLabel(label) {
|
|
26976
26801
|
return label.includes("\"") ? `'${label}'` : `"${label}"`;
|
|
26977
26802
|
}
|
|
@@ -27191,8 +27016,7 @@ function analyzeQueryClustersFromRows(input) {
|
|
|
27191
27016
|
limit
|
|
27192
27017
|
};
|
|
27193
27018
|
}
|
|
27194
|
-
|
|
27195
|
-
//#region packages/core/src/analyze/query-cluster.ts
|
|
27019
|
+
// packages/core/src/analyze/query-cluster.ts
|
|
27196
27020
|
function* retainedQueryClusterRows(input) {
|
|
27197
27021
|
for (const row of input.rows) {
|
|
27198
27022
|
const query = row.keys[0] ?? "";
|
|
@@ -27299,15 +27123,13 @@ function buildQueryClusterReportFromRows(input) {
|
|
|
27299
27123
|
recommendations: clusters.filter((cluster) => cluster.recommendation).slice(0, 5).map((cluster) => cluster.recommendation ?? "")
|
|
27300
27124
|
};
|
|
27301
27125
|
}
|
|
27302
|
-
|
|
27303
|
-
//#region packages/core/src/analyze/reports/dates.ts
|
|
27126
|
+
// packages/core/src/analyze/reports/dates.ts
|
|
27304
27127
|
function rangeDays(range) {
|
|
27305
27128
|
const start = Date.parse(`${range.startDate}T00:00:00Z`);
|
|
27306
27129
|
const end = Date.parse(`${range.endDate}T00:00:00Z`);
|
|
27307
27130
|
return Math.floor((end - start) / 864e5) + 1;
|
|
27308
27131
|
}
|
|
27309
|
-
|
|
27310
|
-
//#region packages/core/src/analyze/reports/markdown.ts
|
|
27132
|
+
// packages/core/src/analyze/reports/markdown.ts
|
|
27311
27133
|
function renderMarkdown(report) {
|
|
27312
27134
|
const lines = [
|
|
27313
27135
|
`# SEO report: ${report.site}`,
|
|
@@ -27331,8 +27153,7 @@ function renderMarkdown(report) {
|
|
|
27331
27153
|
for (const priority of report.priorities) lines.push(`- ${priority.title} (${priority.confidence}): ${priority.action}`);
|
|
27332
27154
|
return lines.join("\n");
|
|
27333
27155
|
}
|
|
27334
|
-
|
|
27335
|
-
//#region packages/core/src/analyze/reports/sections.ts
|
|
27156
|
+
// packages/core/src/analyze/reports/sections.ts
|
|
27336
27157
|
function skippedReason(report, section) {
|
|
27337
27158
|
return report.skippedSections?.find((item) => item.section === section)?.reason;
|
|
27338
27159
|
}
|
|
@@ -27467,8 +27288,7 @@ function contentOpportunityBullets(report) {
|
|
|
27467
27288
|
if (!bullets.length) bullets.push("No material content opportunity stood out in this window. Keep monitoring, but do not force content edits from this report alone.");
|
|
27468
27289
|
return bullets;
|
|
27469
27290
|
}
|
|
27470
|
-
|
|
27471
|
-
//#region packages/core/src/analyze/reports/narrative.ts
|
|
27291
|
+
// packages/core/src/analyze/reports/narrative.ts
|
|
27472
27292
|
function reportCaveats(input) {
|
|
27473
27293
|
return [
|
|
27474
27294
|
`Date window: ${input.period.startDate} to ${input.period.endDate}.`,
|
|
@@ -27550,8 +27370,7 @@ async function reportNarrative(input) {
|
|
|
27550
27370
|
markdown: renderMarkdown(report)
|
|
27551
27371
|
};
|
|
27552
27372
|
}
|
|
27553
|
-
|
|
27554
|
-
//#region packages/core/src/analyze/second-page-analysis.ts
|
|
27373
|
+
// packages/core/src/analyze/second-page-analysis.ts
|
|
27555
27374
|
const DEFAULT_MIN_IMPRESSIONS = 50;
|
|
27556
27375
|
const MAX_MIN_IMPRESSIONS = 1e9;
|
|
27557
27376
|
const DEFAULT_LIMIT = 10;
|
|
@@ -27755,8 +27574,7 @@ function analyzeSecondPageRows(input) {
|
|
|
27755
27574
|
items
|
|
27756
27575
|
};
|
|
27757
27576
|
}
|
|
27758
|
-
|
|
27759
|
-
//#region packages/core/src/analyze/second-page-report.ts
|
|
27577
|
+
// packages/core/src/analyze/second-page-report.ts
|
|
27760
27578
|
const MAX_SOURCE_ROWS = 1e5;
|
|
27761
27579
|
function integerOption(input) {
|
|
27762
27580
|
if (input.value === void 0) return input.fallback;
|
|
@@ -27973,8 +27791,7 @@ async function secondPageWith(input, dependencies) {
|
|
|
27973
27791
|
warnings: uniqueWarnings(warnings)
|
|
27974
27792
|
};
|
|
27975
27793
|
}
|
|
27976
|
-
|
|
27977
|
-
//#region packages/core/src/analyze/second-page.ts
|
|
27794
|
+
// packages/core/src/analyze/second-page.ts
|
|
27978
27795
|
const defaultDependencies = {
|
|
27979
27796
|
searchAnalytics: querySearchAnalytics,
|
|
27980
27797
|
fetch: fetchPage,
|
|
@@ -27984,8 +27801,7 @@ const defaultDependencies = {
|
|
|
27984
27801
|
function secondPage(input, dependencies = defaultDependencies) {
|
|
27985
27802
|
return secondPageWith(input, dependencies);
|
|
27986
27803
|
}
|
|
27987
|
-
|
|
27988
|
-
//#region packages/core/src/analyze/workflows/priority-scoring.ts
|
|
27804
|
+
// packages/core/src/analyze/workflows/priority-scoring.ts
|
|
27989
27805
|
const SOURCE_WEIGHT = {
|
|
27990
27806
|
decay: 1.25,
|
|
27991
27807
|
"quick-win": 1.15,
|
|
@@ -28054,8 +27870,7 @@ function scorePriority(input) {
|
|
|
28054
27870
|
final: Number(final.toFixed(2))
|
|
28055
27871
|
};
|
|
28056
27872
|
}
|
|
28057
|
-
|
|
28058
|
-
//#region packages/core/src/analyze/workflows/report.ts
|
|
27873
|
+
// packages/core/src/analyze/workflows/report.ts
|
|
28059
27874
|
function workflowReport(input) {
|
|
28060
27875
|
return {
|
|
28061
27876
|
workflow: input.workflow,
|
|
@@ -28067,8 +27882,7 @@ function workflowReport(input) {
|
|
|
28067
27882
|
output: input.output
|
|
28068
27883
|
};
|
|
28069
27884
|
}
|
|
28070
|
-
|
|
28071
|
-
//#region packages/core/src/analyze/workflows/crawl-implementation-queue.ts
|
|
27885
|
+
// packages/core/src/analyze/workflows/crawl-implementation-queue.ts
|
|
28072
27886
|
const CATEGORY_TO_PRIORITY = {
|
|
28073
27887
|
canonical: "technical",
|
|
28074
27888
|
content: "content",
|
|
@@ -28204,8 +28018,7 @@ async function crawlImplementationQueueWorkflow(input, dependencies) {
|
|
|
28204
28018
|
}
|
|
28205
28019
|
});
|
|
28206
28020
|
}
|
|
28207
|
-
|
|
28208
|
-
//#region packages/core/src/analyze/workflows/diagnose-property.ts
|
|
28021
|
+
// packages/core/src/analyze/workflows/diagnose-property.ts
|
|
28209
28022
|
async function diagnosePropertyWorkflow(input) {
|
|
28210
28023
|
const narrative = await reportNarrative(input);
|
|
28211
28024
|
const skippedCount = narrative.diagnosis.skippedSections?.length ?? 0;
|
|
@@ -28227,8 +28040,7 @@ async function diagnosePropertyWorkflow(input) {
|
|
|
28227
28040
|
output: { narrative }
|
|
28228
28041
|
});
|
|
28229
28042
|
}
|
|
28230
|
-
|
|
28231
|
-
//#region packages/core/src/markdown.ts
|
|
28043
|
+
// packages/core/src/markdown.ts
|
|
28232
28044
|
function tableCell(value) {
|
|
28233
28045
|
return String(value ?? "-").replaceAll("\n", " ").replaceAll("|", "\\|");
|
|
28234
28046
|
}
|
|
@@ -28255,8 +28067,7 @@ function renderPresentationTablesMarkdown(tables) {
|
|
|
28255
28067
|
];
|
|
28256
28068
|
});
|
|
28257
28069
|
}
|
|
28258
|
-
|
|
28259
|
-
//#region packages/core/src/analyze/workflows/presentation.ts
|
|
28070
|
+
// packages/core/src/analyze/workflows/presentation.ts
|
|
28260
28071
|
function isRecord(value) {
|
|
28261
28072
|
return typeof value === "object" && value !== null;
|
|
28262
28073
|
}
|
|
@@ -28391,8 +28202,7 @@ function workflowPresentation(report, options = {}) {
|
|
|
28391
28202
|
}] : []
|
|
28392
28203
|
};
|
|
28393
28204
|
}
|
|
28394
|
-
|
|
28395
|
-
//#region packages/core/src/analyze/workflows/markdown.ts
|
|
28205
|
+
// packages/core/src/analyze/workflows/markdown.ts
|
|
28396
28206
|
function renderWorkflowMarkdown(report, options = {}) {
|
|
28397
28207
|
const presentation = workflowPresentation(report, options);
|
|
28398
28208
|
return [
|
|
@@ -28406,8 +28216,7 @@ function renderWorkflowMarkdown(report, options = {}) {
|
|
|
28406
28216
|
...renderPresentationTablesMarkdown(presentation.tables)
|
|
28407
28217
|
].join("\n").trim();
|
|
28408
28218
|
}
|
|
28409
|
-
|
|
28410
|
-
//#region packages/core/src/analyze/workflows/priority-grouping.ts
|
|
28219
|
+
// packages/core/src/analyze/workflows/priority-grouping.ts
|
|
28411
28220
|
const GROUPABLE_SOURCES = /* @__PURE__ */ new Set(["quick-win", "striking-distance"]);
|
|
28412
28221
|
function queueTemplateFamilyId(template) {
|
|
28413
28222
|
if (!template) return "no-template";
|
|
@@ -28503,8 +28312,7 @@ function groupBy(items, keyFor) {
|
|
|
28503
28312
|
function groupPriorityQueue(items) {
|
|
28504
28313
|
return groupBy(groupBy(items, targetGroupKey), groupKey).sort((a, b) => b.score - a.score);
|
|
28505
28314
|
}
|
|
28506
|
-
|
|
28507
|
-
//#region packages/core/src/analyze/workflows/refresh-priorities.ts
|
|
28315
|
+
// packages/core/src/analyze/workflows/refresh-priorities.ts
|
|
28508
28316
|
function templateCount(input) {
|
|
28509
28317
|
const template = input.templates?.find((item) => item.id === input.id);
|
|
28510
28318
|
return template?.urlCount ?? template?.count;
|
|
@@ -28711,8 +28519,7 @@ async function refreshPrioritiesWorkflow(input) {
|
|
|
28711
28519
|
}
|
|
28712
28520
|
});
|
|
28713
28521
|
}
|
|
28714
|
-
|
|
28715
|
-
//#region packages/core/src/analyze/workflows/segments.ts
|
|
28522
|
+
// packages/core/src/analyze/workflows/segments.ts
|
|
28716
28523
|
function compareText$3(left, right) {
|
|
28717
28524
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
28718
28525
|
}
|
|
@@ -28740,8 +28547,7 @@ function splitSegments(report) {
|
|
|
28740
28547
|
losers
|
|
28741
28548
|
};
|
|
28742
28549
|
}
|
|
28743
|
-
|
|
28744
|
-
//#region packages/core/src/analyze/workflows/update-postmortem.ts
|
|
28550
|
+
// packages/core/src/analyze/workflows/update-postmortem.ts
|
|
28745
28551
|
function formatMagnitude(value) {
|
|
28746
28552
|
return Math.abs(value).toLocaleString("en-GB");
|
|
28747
28553
|
}
|
|
@@ -29013,8 +28819,7 @@ async function updatePostmortemWorkflow(input, dependencies = {}) {
|
|
|
29013
28819
|
}
|
|
29014
28820
|
});
|
|
29015
28821
|
}
|
|
29016
|
-
|
|
29017
|
-
//#region packages/core/src/brand-detect.ts
|
|
28822
|
+
// packages/core/src/brand-detect.ts
|
|
29018
28823
|
function defaultDateRange(days = 28) {
|
|
29019
28824
|
return finalGscDateRange(days);
|
|
29020
28825
|
}
|
|
@@ -29081,8 +28886,7 @@ async function detectBrandTerms(input) {
|
|
|
29081
28886
|
candidates: ranked
|
|
29082
28887
|
};
|
|
29083
28888
|
}
|
|
29084
|
-
|
|
29085
|
-
//#region packages/core/src/clicky/api.ts
|
|
28889
|
+
// packages/core/src/clicky/api.ts
|
|
29086
28890
|
const ENDPOINT$1 = "https://api.clicky.com/api/stats/4";
|
|
29087
28891
|
const MAX_BYTES$3 = 5e6;
|
|
29088
28892
|
var ClickyError = class extends Error {
|
|
@@ -29136,8 +28940,7 @@ async function clickyDomain(site) {
|
|
|
29136
28940
|
return null;
|
|
29137
28941
|
}
|
|
29138
28942
|
}
|
|
29139
|
-
|
|
29140
|
-
//#region packages/core/src/analyze/ai-queries.ts
|
|
28943
|
+
// packages/core/src/analyze/ai-queries.ts
|
|
29141
28944
|
const RULES = [
|
|
29142
28945
|
["agent_prompt", "^(search the web for|browse the web|find sources)|do not invent|return the [0-9]+ most relevant|include source urls|context: location|do not include location|question: |reply only with|the model you are using"],
|
|
29143
28946
|
["tracker", "\\. ?my location is |\\?\\. ?my( |$)|^evaluate the .+ on "],
|
|
@@ -29179,7 +28982,7 @@ function aiQueryKind(query) {
|
|
|
29179
28982
|
return MATCHERS.find(([, pattern]) => pattern.test(text))?.[0] ?? null;
|
|
29180
28983
|
}
|
|
29181
28984
|
const STOP = new Set("the and for with that this what which who how why when where are is was can does do did you your our from about into than then them they their there have has had will would should could not but its it’s me my i am of to in on at by or an as be if so".split(" "));
|
|
29182
|
-
function words(query) {
|
|
28985
|
+
function words$1(query) {
|
|
29183
28986
|
return new Set(query.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((word) => word.length >= 3 && !STOP.has(word)).map((word) => word.length > 4 && word.endsWith("s") ? word.slice(0, -1) : word));
|
|
29184
28987
|
}
|
|
29185
28988
|
const SAME_QUESTION = .6;
|
|
@@ -29222,7 +29025,7 @@ async function aiQueriesReport(input, searchAnalytics) {
|
|
|
29222
29025
|
const rows = [...matched.rows].sort((a, b) => b.impressions - a.impressions || String(a.keys[0]).localeCompare(String(b.keys[0])) || String(a.keys[1]).localeCompare(String(b.keys[1])));
|
|
29223
29026
|
const usage = /* @__PURE__ */ new Map();
|
|
29224
29027
|
const unique = new Set(rows.map((row) => String(row.keys[0] ?? "")));
|
|
29225
|
-
for (const query of unique) for (const word of words(query)) usage.set(word, (usage.get(word) ?? 0) + 1);
|
|
29028
|
+
for (const query of unique) for (const word of words$1(query)) usage.set(word, (usage.get(word) ?? 0) + 1);
|
|
29226
29029
|
const weight = (word) => Math.log((unique.size + 1) / (usage.get(word) ?? 1));
|
|
29227
29030
|
const groups = [];
|
|
29228
29031
|
const byWord = /* @__PURE__ */ new Map();
|
|
@@ -29242,7 +29045,7 @@ async function aiQueriesReport(input, searchAnalytics) {
|
|
|
29242
29045
|
continue;
|
|
29243
29046
|
}
|
|
29244
29047
|
const text = query.trim().toLowerCase();
|
|
29245
|
-
const own = words(query);
|
|
29048
|
+
const own = words$1(query);
|
|
29246
29049
|
let group = byText.get(text);
|
|
29247
29050
|
if (!group) {
|
|
29248
29051
|
let checked = 0;
|
|
@@ -29357,8 +29160,7 @@ async function aiQueriesReport(input, searchAnalytics) {
|
|
|
29357
29160
|
]
|
|
29358
29161
|
};
|
|
29359
29162
|
}
|
|
29360
|
-
|
|
29361
|
-
//#region packages/core/src/commands/research-providers.ts
|
|
29163
|
+
// packages/core/src/commands/research-providers.ts
|
|
29362
29164
|
function need(headers, provider) {
|
|
29363
29165
|
const values = provider === "dataforseo" ? [headers.get("x-dataforseo-login"), headers.get("x-dataforseo-password")] : [headers.get("x-ahrefs-key")];
|
|
29364
29166
|
if (values.some((value) => !value)) throw new CommandError("NO_KEY", `No ${provider} key. Hosted MCP: save it at https://seoskill.dev/account#keys. CLI and local MCP: run \`seo login\` to use the account's keys, or \`seo providers ${provider} connect\`.`);
|
|
@@ -29950,8 +29752,7 @@ async function runAiAnswers(input, headers, deps, more) {
|
|
|
29950
29752
|
}
|
|
29951
29753
|
};
|
|
29952
29754
|
}
|
|
29953
|
-
|
|
29954
|
-
//#region packages/core/src/commands/analytics.ts
|
|
29755
|
+
// packages/core/src/commands/analytics.ts
|
|
29955
29756
|
async function analyticsSignIn(accounts, property, signIn) {
|
|
29956
29757
|
const only = accounts.length === 1 ? accounts[0] : void 0;
|
|
29957
29758
|
if (only) return only.google;
|
|
@@ -30013,8 +29814,7 @@ async function runAnalyticsRows(input, google, options) {
|
|
|
30013
29814
|
}
|
|
30014
29815
|
};
|
|
30015
29816
|
}
|
|
30016
|
-
|
|
30017
|
-
//#region packages/core/src/ga4/matching.ts
|
|
29817
|
+
// packages/core/src/ga4/matching.ts
|
|
30018
29818
|
function hostnameFromUrl(value) {
|
|
30019
29819
|
try {
|
|
30020
29820
|
const url = new URL(value);
|
|
@@ -30056,8 +29856,7 @@ function ga4MatchReason(match, site) {
|
|
|
30056
29856
|
const location = match.match === "domain" ? "is inside" : "matches";
|
|
30057
29857
|
return `Matched Google Analytics web stream ${stream} (${match.defaultUri}): its hostname ${location} ${site}.`;
|
|
30058
29858
|
}
|
|
30059
|
-
|
|
30060
|
-
//#region packages/core/src/commands/analytics-site.ts
|
|
29859
|
+
// packages/core/src/commands/analytics-site.ts
|
|
30061
29860
|
const STREAM_CHECKS = 30;
|
|
30062
29861
|
const AT_ONCE = 5;
|
|
30063
29862
|
const TIED = 3;
|
|
@@ -30363,8 +30162,7 @@ async function analyticsBeside(access, site, range, before, pages) {
|
|
|
30363
30162
|
};
|
|
30364
30163
|
}
|
|
30365
30164
|
}
|
|
30366
|
-
|
|
30367
|
-
//#region packages/core/src/commands/audit.ts
|
|
30165
|
+
// packages/core/src/commands/audit.ts
|
|
30368
30166
|
const AUDIT_SECTION = "weekly-audit";
|
|
30369
30167
|
const AUDIT_SECTION_TEXT = "What the weekly crawl of the site found (broken and redirected internal links, missing or duplicate titles, descriptions and H1s, noindex, canonicals to another page, sitemap URLs that do not return 200, thin pages, tags that JavaScript changes), with what is new, back or resolved since the crawl before. Only for sites added to the weekly site audit beta.";
|
|
30370
30168
|
const AUDIT_RULES = {
|
|
@@ -30382,18 +30180,18 @@ const AUDIT_RULES = {
|
|
|
30382
30180
|
thin_page: "Pages under 100 words (heuristic)",
|
|
30383
30181
|
js_changed_tags: "Pages whose raw HTML has another title, canonical or robots tag than the page after JavaScript runs"
|
|
30384
30182
|
};
|
|
30385
|
-
const PAGE_SIZE$
|
|
30386
|
-
const SUMMARY_ROWS = 20;
|
|
30387
|
-
const day$
|
|
30388
|
-
function relative$
|
|
30183
|
+
const PAGE_SIZE$2 = 50;
|
|
30184
|
+
const SUMMARY_ROWS$1 = 20;
|
|
30185
|
+
const day$2 = (ms) => new Date(ms).toISOString().slice(0, 10);
|
|
30186
|
+
function relative$3(value, origin) {
|
|
30389
30187
|
return origin && value.startsWith(origin) ? value.slice(origin.length) || "/" : value;
|
|
30390
30188
|
}
|
|
30391
30189
|
function evidence(finding, origin) {
|
|
30392
30190
|
const details = JSON.parse(finding.details);
|
|
30393
30191
|
const parts = [];
|
|
30394
30192
|
if (typeof details.status === "number") parts.push(`${details.status}`);
|
|
30395
|
-
if (typeof details.location === "string") parts.push(`to ${relative$
|
|
30396
|
-
if (typeof details.canonical === "string") parts.push(`canonical ${relative$
|
|
30193
|
+
if (typeof details.location === "string") parts.push(`to ${relative$3(details.location, origin)}`);
|
|
30194
|
+
if (typeof details.canonical === "string") parts.push(`canonical ${relative$3(details.canonical, origin)}`);
|
|
30397
30195
|
if (typeof details.words === "number") parts.push(`${details.words} words`);
|
|
30398
30196
|
if (details.raw && typeof details.raw === "object") {
|
|
30399
30197
|
const raw = details.raw;
|
|
@@ -30403,13 +30201,13 @@ function evidence(finding, origin) {
|
|
|
30403
30201
|
"robots"
|
|
30404
30202
|
]) if (key in raw) {
|
|
30405
30203
|
const value = raw[key];
|
|
30406
|
-
parts.push(`raw ${key} ${typeof value === "string" ? key === "canonical" ? relative$
|
|
30204
|
+
parts.push(`raw ${key} ${typeof value === "string" ? key === "canonical" ? relative$3(value, origin) : value : "none"}`);
|
|
30407
30205
|
}
|
|
30408
30206
|
}
|
|
30409
30207
|
const linking = typeof details.linking_pages === "number";
|
|
30410
30208
|
const count = linking ? details.linking_pages : details.pages;
|
|
30411
30209
|
if (typeof count === "number") {
|
|
30412
|
-
const shown = (Array.isArray(details.examples) ? details.examples.map((url) => relative$
|
|
30210
|
+
const shown = (Array.isArray(details.examples) ? details.examples.map((url) => relative$3(url, origin)) : []).slice(0, 3);
|
|
30413
30211
|
const noun = `${linking ? "linking page" : "page"}${count === 1 ? "" : "s"}`;
|
|
30414
30212
|
parts.push(`${count} ${noun}${shown.length ? ` (${shown.join(", ")}${count > shown.length ? ", and more" : ""})` : ""}`);
|
|
30415
30213
|
}
|
|
@@ -30419,13 +30217,13 @@ function state(finding, since) {
|
|
|
30419
30217
|
if (finding.state === "resolved") return "resolved";
|
|
30420
30218
|
if (finding.reopened_at && finding.reopened_at >= since) return "back";
|
|
30421
30219
|
if (finding.first_seen_at >= since) return "new";
|
|
30422
|
-
return `open since ${day$
|
|
30220
|
+
return `open since ${day$2(finding.first_seen_at)}`;
|
|
30423
30221
|
}
|
|
30424
30222
|
function table(rows, origin, since) {
|
|
30425
30223
|
return ["state | finding | subject | evidence", ...rows.map((finding) => [
|
|
30426
30224
|
state(finding, since),
|
|
30427
30225
|
finding.rule,
|
|
30428
|
-
relative$
|
|
30226
|
+
relative$3(finding.subject, origin),
|
|
30429
30227
|
evidence(finding, origin)
|
|
30430
30228
|
].join(" | "))].join("\n");
|
|
30431
30229
|
}
|
|
@@ -30490,8 +30288,8 @@ function runAuditReport(input, snapshot, call) {
|
|
|
30490
30288
|
const pages = summary.pages;
|
|
30491
30289
|
const coverage = summary.complete ? `${pages.fetched} pages, the whole site the crawl could reach` : summary.status === "failed" ? `${pages.fetched} pages before the crawl stopped on errors, so it is partial` : `${pages.fetched} pages; it stopped at the 5,000-page limit${pages.links_past_cap ? ` with ${pages.links_past_cap} more links past it` : ""}, so it is partial`;
|
|
30492
30290
|
const head = [
|
|
30493
|
-
`Weekly site audit of ${site}, crawled ${day$
|
|
30494
|
-
previous ? `Since the crawl of ${day$
|
|
30291
|
+
`Weekly site audit of ${site}, crawled ${day$2(snapshot.finished_at)}: ${coverage}.${pages.blocked ? ` ${pages.blocked === 1 ? "1 URL" : `${pages.blocked} URLs`} blocked by robots.txt ${pages.blocked === 1 ? "was" : "were"} not fetched.` : ""}`,
|
|
30292
|
+
previous ? `Since the crawl of ${day$2(previous.finished_at)}: ${summary.findings.new} new, ${summary.findings.reopened} back, ${summary.findings.resolved} resolved. ${summary.findings.open} open now.` : `This is the first crawl, so every finding is new: ${summary.findings.open} open.`,
|
|
30495
30293
|
renderingText(summary.rendering),
|
|
30496
30294
|
summary.findings.resolution === "crawled-pages-only" ? summary.complete ? "Because some pages could not be rendered, a finding counts as resolved only when this crawl rendered its page; a finding not seen here is not an all-clear." : "Because the crawl is partial, a finding counts as resolved only when this crawl fetched its page again; a finding not seen here is not an all-clear." : null,
|
|
30497
30295
|
summary.findings.capped_rules.length ? `Capped at 1,000 findings each, so these counts are lower bounds: ${summary.findings.capped_rules.join(", ")}.` : null,
|
|
@@ -30503,8 +30301,8 @@ function runAuditReport(input, snapshot, call) {
|
|
|
30503
30301
|
"change | url | date | reason now | top page",
|
|
30504
30302
|
...index.rows.map((row) => [
|
|
30505
30303
|
row.change === "dropped" ? "dropped out" : "back in",
|
|
30506
|
-
relative$
|
|
30507
|
-
day$
|
|
30304
|
+
relative$3(row.url, origin),
|
|
30305
|
+
day$2(row.at),
|
|
30508
30306
|
row.coverage ?? "",
|
|
30509
30307
|
row.top ? "yes" : "no"
|
|
30510
30308
|
].join(" | ")),
|
|
@@ -30512,7 +30310,7 @@ function runAuditReport(input, snapshot, call) {
|
|
|
30512
30310
|
].filter(Boolean).join("\n") : null;
|
|
30513
30311
|
if (detail === "rows") {
|
|
30514
30312
|
const offset = input.offset ?? 0;
|
|
30515
|
-
const slice = findings.slice(offset, offset + PAGE_SIZE$
|
|
30313
|
+
const slice = findings.slice(offset, offset + PAGE_SIZE$2);
|
|
30516
30314
|
const end = offset + slice.length;
|
|
30517
30315
|
return {
|
|
30518
30316
|
text: [
|
|
@@ -30546,7 +30344,7 @@ function runAuditReport(input, snapshot, call) {
|
|
|
30546
30344
|
head,
|
|
30547
30345
|
indexText,
|
|
30548
30346
|
byRule.length ? ["finding | open | what it is", ...byRule.map(([rule, count]) => `${rule} | ${count} | ${AUDIT_RULES[rule] ?? rule}`)].join("\n") : "No findings in this crawl.",
|
|
30549
|
-
fresh.length ? `New, back or resolved this week, 1 to ${Math.min(SUMMARY_ROWS, fresh.length)} of ${fresh.length}:\n${table(fresh.slice(0, SUMMARY_ROWS), origin, since)}` : previous ? "Nothing new, back or resolved since the crawl before." : null,
|
|
30347
|
+
fresh.length ? `New, back or resolved this week, 1 to ${Math.min(SUMMARY_ROWS$1, fresh.length)} of ${fresh.length}:\n${table(fresh.slice(0, SUMMARY_ROWS$1), origin, since)}` : previous ? "Nothing new, back or resolved since the crawl before." : null,
|
|
30550
30348
|
changes.compared ? `Between the two crawls: ${changes.new_urls} new URLs, ${changes.gone_urls} gone, ${changes.status_changed} changed status, ${changes.title_changed} changed title.` : null,
|
|
30551
30349
|
`More: ${call("report", {
|
|
30552
30350
|
site,
|
|
@@ -30560,7 +30358,7 @@ function runAuditReport(input, snapshot, call) {
|
|
|
30560
30358
|
site,
|
|
30561
30359
|
finished_at: snapshot.finished_at,
|
|
30562
30360
|
summary,
|
|
30563
|
-
fresh: fresh.slice(0, SUMMARY_ROWS)
|
|
30361
|
+
fresh: fresh.slice(0, SUMMARY_ROWS$1)
|
|
30564
30362
|
}
|
|
30565
30363
|
};
|
|
30566
30364
|
}
|
|
@@ -30568,12 +30366,12 @@ function auditPreview(snapshot) {
|
|
|
30568
30366
|
if (!snapshot?.summary || snapshot.finished_at === null) return snapshot ? "The first weekly crawl has not finished." : null;
|
|
30569
30367
|
const { findings } = snapshot.summary;
|
|
30570
30368
|
const index = snapshot.index_changes;
|
|
30571
|
-
return `${index && (index.dropped || index.returned) ? `${index.dropped} URLs dropped out of Google's index this week and ${index.returned} came back. ` : ""}Last crawl ${day$
|
|
30369
|
+
return `${index && (index.dropped || index.returned) ? `${index.dropped} URLs dropped out of Google's index this week and ${index.returned} came back. ` : ""}Last crawl ${day$2(snapshot.finished_at)}: ${findings.open} open findings, ${findings.new + findings.reopened} new or back, ${findings.resolved} resolved.`;
|
|
30572
30370
|
}
|
|
30573
30371
|
const WEEK_MS = 6048e5;
|
|
30574
|
-
const day = (ms) => ms === null ? "" : new Date(ms).toISOString().slice(0, 10);
|
|
30372
|
+
const day$1 = (ms) => ms === null ? "" : new Date(ms).toISOString().slice(0, 10);
|
|
30575
30373
|
const count$2 = (value) => value.toLocaleString("en-US");
|
|
30576
|
-
function relative$
|
|
30374
|
+
function relative$2(value, origin) {
|
|
30577
30375
|
if (!value) return "";
|
|
30578
30376
|
return origin && value.startsWith(origin) ? value.slice(origin.length) || "/" : value;
|
|
30579
30377
|
}
|
|
@@ -30587,15 +30385,15 @@ function rowsTable(rows, checks) {
|
|
|
30587
30385
|
const since = checks.as_of - WEEK_MS;
|
|
30588
30386
|
const changed = (row) => row.changed_at !== null && row.changed_at > since;
|
|
30589
30387
|
return ["url | index change | reason now | before | changed | checked | clicks (28 days before) | in_sitemap | google_canonical | error", ...rows.map((row) => [
|
|
30590
|
-
relative$
|
|
30591
|
-
row.transition && row.transition_at !== null && row.transition_at > since ? `${row.transition === "dropped" ? "dropped out" : "back in"} ${day(row.transition_at)}` : "",
|
|
30388
|
+
relative$2(row.url, checks.origin),
|
|
30389
|
+
row.transition && row.transition_at !== null && row.transition_at > since ? `${row.transition === "dropped" ? "dropped out" : "back in"} ${day$1(row.transition_at)}` : "",
|
|
30592
30390
|
row.coverage ?? (row.checked_at === null ? "not checked yet" : ""),
|
|
30593
30391
|
changed(row) ? row.prev_coverage ?? "" : "",
|
|
30594
|
-
changed(row) ? day(row.changed_at) : "",
|
|
30595
|
-
day(row.checked_at),
|
|
30392
|
+
changed(row) ? day$1(row.changed_at) : "",
|
|
30393
|
+
day$1(row.checked_at),
|
|
30596
30394
|
`${count$2(row.clicks)} (${count$2(row.previous_clicks)})${row.lost ? " lost" : ""}`,
|
|
30597
30395
|
row.in_sitemap ? "yes" : "no",
|
|
30598
|
-
row.google_canonical && row.google_canonical !== row.url ? relative$
|
|
30396
|
+
row.google_canonical && row.google_canonical !== row.url ? relative$2(row.google_canonical, checks.origin) : "",
|
|
30599
30397
|
row.error ?? ""
|
|
30600
30398
|
].join(" | "))].join("\n");
|
|
30601
30399
|
}
|
|
@@ -30677,8 +30475,7 @@ function runAuditChecks(input, checks, call) {
|
|
|
30677
30475
|
}
|
|
30678
30476
|
};
|
|
30679
30477
|
}
|
|
30680
|
-
|
|
30681
|
-
//#region packages/core/src/commands/bing.ts
|
|
30478
|
+
// packages/core/src/commands/bing.ts
|
|
30682
30479
|
const BING_LIMITS = ["Bing and Google count clicks, impressions and positions their own way. Their numbers sit side by side here and are never added together.", "Bing lists only its top queries and pages, grouped by week. A query or page missing from Bing’s list is unknown, not zero."];
|
|
30683
30480
|
const CLI_LINK = "If you have a shell, link the CLI to the same account once: run `npx -y seo login`, show the person the link it prints, and after they approve it run `npx -y seo login` again. `npx -y seo` commands then use the same Google accounts, Bing and keys.";
|
|
30684
30481
|
const LOCAL_BING = `Bing blocks the hosted server, so Bing data comes from the CLI on a machine. ${CLI_LINK} Connect Bing on the account page if it is not connected, then run \`npx -y seo report --site <site> --section bing --json\`.`;
|
|
@@ -30994,8 +30791,7 @@ function bingOverviewText(report, heading, origin, rowsPerList) {
|
|
|
30994
30791
|
`Limits:\n${report.caveats.map((line) => `- ${line}`).join("\n")}`
|
|
30995
30792
|
].filter(Boolean).join("\n\n");
|
|
30996
30793
|
}
|
|
30997
|
-
|
|
30998
|
-
//#region packages/core/src/commands/clicky.ts
|
|
30794
|
+
// packages/core/src/commands/clicky.ts
|
|
30999
30795
|
const CLICKY_KEY_PREFIX = "x-clicky-";
|
|
31000
30796
|
function clickySites(keys) {
|
|
31001
30797
|
const sites = [];
|
|
@@ -31149,8 +30945,7 @@ async function runClickyRows(input, keys, options) {
|
|
|
31149
30945
|
}
|
|
31150
30946
|
};
|
|
31151
30947
|
}
|
|
31152
|
-
|
|
31153
|
-
//#region packages/core/src/commands/domain-rating.ts
|
|
30948
|
+
// packages/core/src/commands/domain-rating.ts
|
|
31154
30949
|
const DR_ATTRIBUTION = "domain_rating: Domain Rating by Ahrefs (https://ahrefs.com/).";
|
|
31155
30950
|
const ENDPOINT = "https://api.ahrefs.com/v3/public/domain-rating-free";
|
|
31156
30951
|
const MAX_DOMAINS = 25;
|
|
@@ -31411,9 +31206,9 @@ function indexingRow(e, sources, inspectedAt) {
|
|
|
31411
31206
|
...live.first.error ? { live_error: live.first.error } : {}
|
|
31412
31207
|
};
|
|
31413
31208
|
}
|
|
31414
|
-
const byText = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
31209
|
+
const byText$1 = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
31415
31210
|
function sortRows(rows) {
|
|
31416
|
-
return [...rows].sort((a, b) => byText(a.reason, b.reason) || byText(a.url, b.url));
|
|
31211
|
+
return [...rows].sort((a, b) => byText$1(a.reason, b.reason) || byText$1(a.url, b.url));
|
|
31417
31212
|
}
|
|
31418
31213
|
function byReason(rows) {
|
|
31419
31214
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -31427,7 +31222,7 @@ function byReason(rows) {
|
|
|
31427
31222
|
counts[row.label] += 1;
|
|
31428
31223
|
groups.set(row.reason, counts);
|
|
31429
31224
|
}
|
|
31430
|
-
return [...groups].sort((a, b) => byText(a[0], b[0])).map(([reason, counts]) => ({
|
|
31225
|
+
return [...groups].sort((a, b) => byText$1(a[0], b[0])).map(([reason, counts]) => ({
|
|
31431
31226
|
reason,
|
|
31432
31227
|
urls: counts.fix + counts.check + counts.expected + counts.indexed,
|
|
31433
31228
|
...counts
|
|
@@ -31441,7 +31236,7 @@ function knownUrls(input) {
|
|
|
31441
31236
|
if (!sources.includes(source)) sources.push(source);
|
|
31442
31237
|
found.set(url, sources);
|
|
31443
31238
|
};
|
|
31444
|
-
const pages = input.pages.filter((page) => page.impressions > 0 && !page.url.includes("#")).sort((a, b) => b.clicks - a.clicks || b.impressions - a.impressions || byText(a.url, b.url));
|
|
31239
|
+
const pages = input.pages.filter((page) => page.impressions > 0 && !page.url.includes("#")).sort((a, b) => b.clicks - a.clicks || b.impressions - a.impressions || byText$1(a.url, b.url));
|
|
31445
31240
|
for (const url of input.lost) add(url, "lost_clicks");
|
|
31446
31241
|
for (const page of pages) add(page.url, "search_console");
|
|
31447
31242
|
for (const url of input.sitemap.urls) add(url, "sitemap");
|
|
@@ -31532,7 +31327,7 @@ async function inspectKnown(site, known, deps, asked = 100) {
|
|
|
31532
31327
|
inspected: rows.length,
|
|
31533
31328
|
fromSaved: reused.length,
|
|
31534
31329
|
oldestSaved: oldest,
|
|
31535
|
-
failed: failed.sort((a, b) => byText(a.url, b.url)),
|
|
31330
|
+
failed: failed.sort((a, b) => byText$1(a.url, b.url)),
|
|
31536
31331
|
stoppedBy: quotaStop || quotaShort ? "quota" : known.urls.length > wanted.length ? "limit" : null,
|
|
31537
31332
|
quota
|
|
31538
31333
|
};
|
|
@@ -31599,8 +31394,441 @@ function indexingText(site, run, page, origin, askMore, now) {
|
|
|
31599
31394
|
if (!page.summary && more) lines.push(`More rows: ${page.more(end)}.`);
|
|
31600
31395
|
return lines;
|
|
31601
31396
|
}
|
|
31602
|
-
|
|
31603
|
-
|
|
31397
|
+
// packages/core/src/commands/issues.ts
|
|
31398
|
+
const ISSUES_SECTION = "issues";
|
|
31399
|
+
const ISSUES_SECTION_TEXT = "A list kept for the site between sessions: issues the striking-distance, low-ctr and quick-wins checks found (and the weekly crawl and Google index checks for an audited site), each with its id, the latest check and what the person marked it (open, fixed or ignored). Asking for it runs those checks again. To mark issues, add mark and issues instead of section. The hosted server keeps the list only for sites in the weekly site audit; for any other site it returns the list as JSON for the agent to save and pass back as saved.";
|
|
31400
|
+
const ISSUE_MARKS = [
|
|
31401
|
+
"open",
|
|
31402
|
+
"fixed",
|
|
31403
|
+
"ignored"
|
|
31404
|
+
];
|
|
31405
|
+
const SAVED_MAX = 6e5;
|
|
31406
|
+
const SUBJECT_MAX = 300;
|
|
31407
|
+
const EVIDENCE_MAX = 200;
|
|
31408
|
+
const PAGE_SIZE$1 = 50;
|
|
31409
|
+
const SUMMARY_ROWS = 20;
|
|
31410
|
+
const SEARCH_CHECKS = [
|
|
31411
|
+
"striking-distance",
|
|
31412
|
+
"low-ctr",
|
|
31413
|
+
"quick-wins"
|
|
31414
|
+
];
|
|
31415
|
+
function issueId(kind, subject) {
|
|
31416
|
+
let hash = 2166136261;
|
|
31417
|
+
for (const char of `${kind}\n${subject}`) {
|
|
31418
|
+
hash ^= char.codePointAt(0) ?? 0;
|
|
31419
|
+
hash = Math.imul(hash, 16777619) >>> 0;
|
|
31420
|
+
}
|
|
31421
|
+
return hash.toString(16).padStart(8, "0");
|
|
31422
|
+
}
|
|
31423
|
+
const clip = (value, max) => value.length > max ? `${value.slice(0, max - 1)}…` : value;
|
|
31424
|
+
const num = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
31425
|
+
const pctText = (value) => {
|
|
31426
|
+
const n = num(value);
|
|
31427
|
+
return n === null ? null : `${Math.round(n * 1e3) / 10}%`;
|
|
31428
|
+
};
|
|
31429
|
+
function searchEvidence(item) {
|
|
31430
|
+
const position = num(item.position);
|
|
31431
|
+
const ctr = pctText(item.actualCtr ?? item.ctr);
|
|
31432
|
+
const expected = pctText(item.expectedCtr ?? item.targetCtr);
|
|
31433
|
+
return [
|
|
31434
|
+
position === null ? null : `position ${Math.round(position * 10) / 10}`,
|
|
31435
|
+
num(item.impressions) === null ? null : `${item.impressions} impressions`,
|
|
31436
|
+
num(item.clicks) === null ? null : `${item.clicks} clicks`,
|
|
31437
|
+
ctr ? `CTR ${ctr}${expected ? ` against ${expected} expected` : ""}` : null
|
|
31438
|
+
].filter(Boolean).join(", ");
|
|
31439
|
+
}
|
|
31440
|
+
function searchComplete(report) {
|
|
31441
|
+
const status = report.dataStatus;
|
|
31442
|
+
if (status === "partial" || status === "unavailable") return `data ${status}`;
|
|
31443
|
+
if (report.source?.possiblyTruncated) return "Search Console rows possibly cut short";
|
|
31444
|
+
const selection = report.selection ?? {};
|
|
31445
|
+
const limited = Object.entries(selection).find(([key, value]) => key.startsWith("limited") && Number(value) > 0);
|
|
31446
|
+
if (limited) return `capped, ${limited[1]} more rows not returned`;
|
|
31447
|
+
return null;
|
|
31448
|
+
}
|
|
31449
|
+
function searchRun(kind, report) {
|
|
31450
|
+
const base = {
|
|
31451
|
+
kind,
|
|
31452
|
+
covers: (k) => k === kind
|
|
31453
|
+
};
|
|
31454
|
+
if (report instanceof Error) return {
|
|
31455
|
+
...base,
|
|
31456
|
+
found: [],
|
|
31457
|
+
complete: false,
|
|
31458
|
+
addLimit: 0,
|
|
31459
|
+
status: `${kind}: could not run (${clip(report.message, 120)})`
|
|
31460
|
+
};
|
|
31461
|
+
const found = (report.items ?? []).filter((item) => typeof item.query === "string" && typeof item.url === "string").map((item) => ({
|
|
31462
|
+
kind,
|
|
31463
|
+
issue: {
|
|
31464
|
+
subject: `"${item.query}" on ${item.url}`,
|
|
31465
|
+
evidence: searchEvidence(item)
|
|
31466
|
+
}
|
|
31467
|
+
}));
|
|
31468
|
+
const short = searchComplete(report);
|
|
31469
|
+
return {
|
|
31470
|
+
...base,
|
|
31471
|
+
found,
|
|
31472
|
+
complete: !short,
|
|
31473
|
+
addLimit: 10,
|
|
31474
|
+
status: `${kind}: ${found.length} ${found.length === 1 ? "row" : "rows"}${short ? ` (${short}, so an issue it did not return is not checked)` : ""}`
|
|
31475
|
+
};
|
|
31476
|
+
}
|
|
31477
|
+
function auditEvidence(details) {
|
|
31478
|
+
try {
|
|
31479
|
+
const parsed = JSON.parse(details);
|
|
31480
|
+
return Object.entries(parsed).filter(([, value]) => ["string", "number"].includes(typeof value)).map(([key, value]) => `${key} ${value}`).join(", ");
|
|
31481
|
+
} catch {
|
|
31482
|
+
return "";
|
|
31483
|
+
}
|
|
31484
|
+
}
|
|
31485
|
+
const AUDIT_KIND = "weekly-audit:";
|
|
31486
|
+
const INDEX_KIND = "index-drop";
|
|
31487
|
+
function auditRuns(snapshot) {
|
|
31488
|
+
const crawl = (k) => k.startsWith(AUDIT_KIND);
|
|
31489
|
+
const index = (k) => k === INDEX_KIND;
|
|
31490
|
+
if (snapshot === void 0) return [];
|
|
31491
|
+
const summary = snapshot?.summary;
|
|
31492
|
+
if (!snapshot || !summary || snapshot.finished_at === null) {
|
|
31493
|
+
const why = snapshot ? "the first crawl has not finished" : "the site is not in the weekly site audit";
|
|
31494
|
+
return [{
|
|
31495
|
+
kind: "weekly-audit",
|
|
31496
|
+
covers: crawl,
|
|
31497
|
+
found: [],
|
|
31498
|
+
complete: false,
|
|
31499
|
+
addLimit: 0,
|
|
31500
|
+
status: `weekly-audit: not checked, ${why}`
|
|
31501
|
+
}, {
|
|
31502
|
+
kind: INDEX_KIND,
|
|
31503
|
+
covers: index,
|
|
31504
|
+
found: [],
|
|
31505
|
+
complete: false,
|
|
31506
|
+
addLimit: 0,
|
|
31507
|
+
status: `${INDEX_KIND}: not checked, ${why}`
|
|
31508
|
+
}];
|
|
31509
|
+
}
|
|
31510
|
+
const open = snapshot.findings.filter((f) => f.state === "open");
|
|
31511
|
+
const gone = new Set(snapshot.findings.filter((f) => f.state === "resolved").map((f) => issueId(`${AUDIT_KIND}${f.rule}`, f.subject)));
|
|
31512
|
+
const capped = new Set(summary.findings.capped_rules.map((rule) => `${AUDIT_KIND}${rule}`));
|
|
31513
|
+
const complete = summary.complete && summary.findings.resolution === "all";
|
|
31514
|
+
const day = new Date(snapshot.finished_at).toISOString().slice(0, 10);
|
|
31515
|
+
const runs = [{
|
|
31516
|
+
kind: "weekly-audit",
|
|
31517
|
+
covers: crawl,
|
|
31518
|
+
found: open.map((f) => ({
|
|
31519
|
+
kind: `${AUDIT_KIND}${f.rule}`,
|
|
31520
|
+
issue: {
|
|
31521
|
+
subject: f.subject,
|
|
31522
|
+
evidence: auditEvidence(f.details)
|
|
31523
|
+
}
|
|
31524
|
+
})),
|
|
31525
|
+
complete,
|
|
31526
|
+
addLimit: 50,
|
|
31527
|
+
gone,
|
|
31528
|
+
partialKinds: capped,
|
|
31529
|
+
status: `weekly-audit: crawl of ${day}, ${open.length} open findings${complete ? "" : " (partial crawl, so a finding it did not see again is not checked)"}${capped.size ? `; capped rules, not checked when absent: ${[...capped].map((k) => k.slice(13)).join(", ")}` : ""}`
|
|
31530
|
+
}];
|
|
31531
|
+
const changes = snapshot.index_changes;
|
|
31532
|
+
if (changes) {
|
|
31533
|
+
const dropped = changes.rows.filter((row) => row.change === "dropped");
|
|
31534
|
+
runs.push({
|
|
31535
|
+
kind: INDEX_KIND,
|
|
31536
|
+
covers: index,
|
|
31537
|
+
found: dropped.map((row) => ({
|
|
31538
|
+
kind: INDEX_KIND,
|
|
31539
|
+
issue: {
|
|
31540
|
+
subject: row.url,
|
|
31541
|
+
evidence: `dropped out ${new Date(row.at).toISOString().slice(0, 10)}${row.coverage ? `, ${row.coverage}` : ""}${row.top ? ", top page" : ""}`
|
|
31542
|
+
}
|
|
31543
|
+
})),
|
|
31544
|
+
complete: false,
|
|
31545
|
+
addLimit: dropped.length,
|
|
31546
|
+
gone: new Set(changes.rows.filter((row) => row.change === "returned").map((row) => issueId(INDEX_KIND, row.url))),
|
|
31547
|
+
status: `${INDEX_KIND}: ${changes.dropped} dropped out of Google's index and ${changes.returned} came back in the last 7 days${changes.rows.length < changes.dropped + changes.returned ? `, ${changes.rows.length} rows read` : ""}`
|
|
31548
|
+
});
|
|
31549
|
+
}
|
|
31550
|
+
return runs;
|
|
31551
|
+
}
|
|
31552
|
+
function mergeIssues(stored, runs, now) {
|
|
31553
|
+
const byId = new Map(stored.map((issue) => [issue.id, { ...issue }]));
|
|
31554
|
+
for (const run of runs) {
|
|
31555
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
31556
|
+
let added = 0;
|
|
31557
|
+
for (const { kind, issue } of run.found) {
|
|
31558
|
+
const id = issueId(kind, issue.subject);
|
|
31559
|
+
if (seenIds.has(id)) continue;
|
|
31560
|
+
seenIds.add(id);
|
|
31561
|
+
const evidence = clip(issue.evidence, EVIDENCE_MAX);
|
|
31562
|
+
const existing = byId.get(id);
|
|
31563
|
+
if (existing) {
|
|
31564
|
+
Object.assign(existing, {
|
|
31565
|
+
evidence,
|
|
31566
|
+
last_found: now,
|
|
31567
|
+
seen: "found",
|
|
31568
|
+
checked_at: now
|
|
31569
|
+
});
|
|
31570
|
+
continue;
|
|
31571
|
+
}
|
|
31572
|
+
if (added >= run.addLimit) continue;
|
|
31573
|
+
added += 1;
|
|
31574
|
+
byId.set(id, {
|
|
31575
|
+
id,
|
|
31576
|
+
kind,
|
|
31577
|
+
subject: clip(issue.subject, SUBJECT_MAX),
|
|
31578
|
+
evidence,
|
|
31579
|
+
mark: "open",
|
|
31580
|
+
marked_at: null,
|
|
31581
|
+
note: null,
|
|
31582
|
+
first_found: now,
|
|
31583
|
+
last_found: now,
|
|
31584
|
+
seen: "found",
|
|
31585
|
+
checked_at: now
|
|
31586
|
+
});
|
|
31587
|
+
}
|
|
31588
|
+
for (const issue of byId.values()) {
|
|
31589
|
+
if (!run.covers(issue.kind) || seenIds.has(issue.id)) continue;
|
|
31590
|
+
if (run.gone?.has(issue.id)) Object.assign(issue, {
|
|
31591
|
+
seen: "not-found",
|
|
31592
|
+
checked_at: now
|
|
31593
|
+
});
|
|
31594
|
+
else if (run.complete && !run.partialKinds?.has(issue.kind)) Object.assign(issue, {
|
|
31595
|
+
seen: "not-found",
|
|
31596
|
+
checked_at: now
|
|
31597
|
+
});
|
|
31598
|
+
else issue.seen = "not-checked";
|
|
31599
|
+
}
|
|
31600
|
+
}
|
|
31601
|
+
return prune([...byId.values()]);
|
|
31602
|
+
}
|
|
31603
|
+
function prune(issues) {
|
|
31604
|
+
if (issues.length <= 500) return sortIssues(issues);
|
|
31605
|
+
return sortIssues([...issues].sort((a, b) => Number(b.mark !== "open") - Number(a.mark !== "open") || (b.last_found ?? "").localeCompare(a.last_found ?? "") || a.id.localeCompare(b.id)).slice(0, 500));
|
|
31606
|
+
}
|
|
31607
|
+
const MARK_ORDER = {
|
|
31608
|
+
open: 0,
|
|
31609
|
+
fixed: 1,
|
|
31610
|
+
ignored: 2
|
|
31611
|
+
};
|
|
31612
|
+
const SEEN_ORDER = {
|
|
31613
|
+
found: 0,
|
|
31614
|
+
"not-checked": 1,
|
|
31615
|
+
"not-found": 2
|
|
31616
|
+
};
|
|
31617
|
+
function sortIssues(issues) {
|
|
31618
|
+
return [...issues].sort((a, b) => MARK_ORDER[a.mark] - MARK_ORDER[b.mark] || SEEN_ORDER[a.seen] - SEEN_ORDER[b.seen] || a.kind.localeCompare(b.kind) || (a.subject < b.subject ? -1 : a.subject > b.subject ? 1 : 0) || a.id.localeCompare(b.id));
|
|
31619
|
+
}
|
|
31620
|
+
const day = (iso) => iso ? iso.slice(0, 10) : "";
|
|
31621
|
+
function relative$1(subject, origin) {
|
|
31622
|
+
return subject.split(origin).join("") || subject;
|
|
31623
|
+
}
|
|
31624
|
+
function issuesTable(rows, origin) {
|
|
31625
|
+
return ["id | mark | latest check | kind | subject | evidence when last found | first found", ...rows.map((issue) => [
|
|
31626
|
+
issue.id,
|
|
31627
|
+
issue.mark === "open" ? "open" : `${issue.mark} ${day(issue.marked_at)}${issue.note ? ` (${issue.note})` : ""}`,
|
|
31628
|
+
issue.seen === "not-checked" ? `not checked${issue.checked_at ? `, last ${day(issue.checked_at)}` : ""}` : `${issue.seen === "found" ? "found" : "not found"} ${day(issue.checked_at)}`,
|
|
31629
|
+
issue.kind,
|
|
31630
|
+
relative$1(issue.subject, origin),
|
|
31631
|
+
issue.evidence,
|
|
31632
|
+
day(issue.first_found)
|
|
31633
|
+
].join(" | "))].join("\n");
|
|
31634
|
+
}
|
|
31635
|
+
function counts(issues) {
|
|
31636
|
+
const by = (mark) => issues.filter((i) => i.mark === mark).length;
|
|
31637
|
+
return {
|
|
31638
|
+
open: by("open"),
|
|
31639
|
+
fixed: by("fixed"),
|
|
31640
|
+
ignored: by("ignored")
|
|
31641
|
+
};
|
|
31642
|
+
}
|
|
31643
|
+
function issuesPreview(issues, call, site) {
|
|
31644
|
+
const more = `${call("report", {
|
|
31645
|
+
site,
|
|
31646
|
+
section: ISSUES_SECTION
|
|
31647
|
+
})} checks again and lists them.`;
|
|
31648
|
+
if (!issues) return `This server is not storing an issues list for this site. ${call("report", {
|
|
31649
|
+
site,
|
|
31650
|
+
section: ISSUES_SECTION
|
|
31651
|
+
})} checks and returns the list to save.`;
|
|
31652
|
+
if (!issues.length) return `No issues kept for this site yet. ${more}`;
|
|
31653
|
+
const c = counts(issues);
|
|
31654
|
+
return `${issues.length} issues kept: ${c.open} open, ${c.fixed} fixed, ${c.ignored} ignored, as of the last check. ${more}`;
|
|
31655
|
+
}
|
|
31656
|
+
const SEEN = /* @__PURE__ */ new Set([
|
|
31657
|
+
"found",
|
|
31658
|
+
"not-found",
|
|
31659
|
+
"not-checked"
|
|
31660
|
+
]);
|
|
31661
|
+
const optionalText = (value) => value === null || value === void 0 || typeof value === "string";
|
|
31662
|
+
function parseSaved(text, site) {
|
|
31663
|
+
const bad = (why) => new CommandError("INVALID_INPUT", `saved is not a saved issues list: ${why}. Pass the JSON the issues section returned, {"version":1,"sites":{"${site}":[...]}}, unchanged. Nothing was changed.`);
|
|
31664
|
+
let file;
|
|
31665
|
+
try {
|
|
31666
|
+
file = JSON.parse(text);
|
|
31667
|
+
} catch {
|
|
31668
|
+
throw bad("it is not JSON");
|
|
31669
|
+
}
|
|
31670
|
+
const { version, sites } = file ?? {};
|
|
31671
|
+
if (version !== 1 || !sites || typeof sites !== "object") throw bad("it needs version 1 and sites");
|
|
31672
|
+
const list = sites[site];
|
|
31673
|
+
if (list === void 0) throw bad(`it has no list for ${site} (it has ${Object.keys(sites).join(", ") || "none"})`);
|
|
31674
|
+
if (!Array.isArray(list)) throw bad(`sites["${site}"] is not a list`);
|
|
31675
|
+
if (list.length > 500) throw bad(`it has more than 500`);
|
|
31676
|
+
return list.map((entry, index) => {
|
|
31677
|
+
const issue = entry ?? {};
|
|
31678
|
+
if (!(typeof issue.id === "string" && /^[0-9a-f]{8}$/.test(issue.id) && typeof issue.kind === "string" && typeof issue.subject === "string" && ISSUE_MARKS.includes(issue.mark) && typeof issue.first_found === "string" && [
|
|
31679
|
+
"evidence",
|
|
31680
|
+
"marked_at",
|
|
31681
|
+
"note",
|
|
31682
|
+
"last_found",
|
|
31683
|
+
"checked_at"
|
|
31684
|
+
].every((key) => optionalText(issue[key])) && (issue.seen === void 0 || SEEN.has(issue.seen)))) throw bad(`issue ${index + 1} is not in the saved shape`);
|
|
31685
|
+
return {
|
|
31686
|
+
id: issue.id,
|
|
31687
|
+
kind: clip(issue.kind, 100),
|
|
31688
|
+
subject: clip(issue.subject, SUBJECT_MAX),
|
|
31689
|
+
evidence: clip(issue.evidence ?? "", EVIDENCE_MAX),
|
|
31690
|
+
mark: issue.mark,
|
|
31691
|
+
marked_at: issue.marked_at ?? null,
|
|
31692
|
+
note: issue.note ? clip(issue.note, 300) : null,
|
|
31693
|
+
first_found: issue.first_found,
|
|
31694
|
+
last_found: issue.last_found ?? null,
|
|
31695
|
+
seen: issue.seen ?? "not-checked",
|
|
31696
|
+
checked_at: issue.checked_at ?? null
|
|
31697
|
+
};
|
|
31698
|
+
});
|
|
31699
|
+
}
|
|
31700
|
+
const asFile = (site, issues) => ({
|
|
31701
|
+
version: 1,
|
|
31702
|
+
sites: { [site]: issues }
|
|
31703
|
+
});
|
|
31704
|
+
function saveText(site, issues, call) {
|
|
31705
|
+
return [`This server is not storing this list for ${site}: the hosted server keeps it only for sites in the weekly site audit. Save the JSON below in a file in the person's project, such as .seo/issues.json, replacing the old one. Next time, pass the file's contents as saved (${call("report", {
|
|
31706
|
+
site,
|
|
31707
|
+
section: ISSUES_SECTION,
|
|
31708
|
+
saved: "<file contents>"
|
|
31709
|
+
})}, or with mark and issues), so marks, notes and first-found dates carry over. Without saved, the list starts again.`, `\`\`\`json\n${JSON.stringify(asFile(site, issues))}\n\`\`\``].join("\n");
|
|
31710
|
+
}
|
|
31711
|
+
async function startingList(site, saved, deps) {
|
|
31712
|
+
const kept = deps.issues ? await deps.issues.read(site) : null;
|
|
31713
|
+
if (kept) return {
|
|
31714
|
+
issues: kept,
|
|
31715
|
+
stored: true,
|
|
31716
|
+
note: saved ? "saved was not used: this surface keeps the list itself." : null
|
|
31717
|
+
};
|
|
31718
|
+
return {
|
|
31719
|
+
issues: saved ? parseSaved(saved, site) : [],
|
|
31720
|
+
stored: false,
|
|
31721
|
+
note: saved ? null : "No saved list was passed, so this is a new list."
|
|
31722
|
+
};
|
|
31723
|
+
}
|
|
31724
|
+
async function runIssues(input, deps, build) {
|
|
31725
|
+
const { site } = input;
|
|
31726
|
+
const now = (deps.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
|
|
31727
|
+
const range = dateWindow(input, 28);
|
|
31728
|
+
const start = await startingList(site, input.saved, deps);
|
|
31729
|
+
const [snapshot, ...search] = await Promise.all([deps.audit ? deps.audit.snapshot(site) : Promise.resolve(void 0), ...SEARCH_CHECKS.map((kind) => build(kind, range).catch((error) => error instanceof Error ? error : new Error(String(error))))]);
|
|
31730
|
+
const runs = [...SEARCH_CHECKS.map((kind, i) => searchRun(kind, search[i])), ...auditRuns(snapshot)];
|
|
31731
|
+
const issues = mergeIssues(start.issues, runs, now);
|
|
31732
|
+
if (start.stored) await deps.issues?.write(site, issues);
|
|
31733
|
+
return issuesResult(input, issues, runs, range, deps.call, start);
|
|
31734
|
+
}
|
|
31735
|
+
function issuesResult(input, issues, runs, range, call, start) {
|
|
31736
|
+
const { site } = input;
|
|
31737
|
+
const detail = input.detail ?? "summary";
|
|
31738
|
+
const origin = originOf(site);
|
|
31739
|
+
if (detail === "full") {
|
|
31740
|
+
const file = asFile(site, issues);
|
|
31741
|
+
return {
|
|
31742
|
+
text: JSON.stringify(file),
|
|
31743
|
+
data: file
|
|
31744
|
+
};
|
|
31745
|
+
}
|
|
31746
|
+
const c = counts(issues);
|
|
31747
|
+
const stillFound = issues.filter((i) => i.mark === "fixed" && i.seen === "found");
|
|
31748
|
+
const head = [
|
|
31749
|
+
`Issues ${start.stored ? "kept" : "listed"} for ${site}: ${issues.length} (${c.open} open, ${c.fixed} fixed, ${c.ignored} ignored). Search checks ${range.startDate} to ${range.endDate}.`,
|
|
31750
|
+
start.note,
|
|
31751
|
+
`This check:\n${runs.map((run) => `- ${run.status}`).join("\n")}`,
|
|
31752
|
+
`New issues come from the first 10 rows of each search check and the first 50 open crawl findings; the list keeps at most 500.`,
|
|
31753
|
+
stillFound.length ? `Marked fixed but found again by this check: ${stillFound.map((i) => i.id).join(", ")}.` : null
|
|
31754
|
+
];
|
|
31755
|
+
const offset = detail === "rows" ? input.offset ?? 0 : 0;
|
|
31756
|
+
const size = detail === "rows" ? PAGE_SIZE$1 : SUMMARY_ROWS;
|
|
31757
|
+
const slice = issues.slice(offset, offset + size);
|
|
31758
|
+
const end = offset + slice.length;
|
|
31759
|
+
return {
|
|
31760
|
+
text: [
|
|
31761
|
+
...head,
|
|
31762
|
+
issues.length ? `Issues ${slice.length ? offset + 1 : 0} to ${end} of ${issues.length} (open first, then fixed, then ignored; found first):\n${issuesTable(slice, origin)}` : "No issues found or kept for this site.",
|
|
31763
|
+
end < issues.length ? `More: ${call("report", {
|
|
31764
|
+
site,
|
|
31765
|
+
section: ISSUES_SECTION,
|
|
31766
|
+
detail: "rows",
|
|
31767
|
+
offset: end
|
|
31768
|
+
})}.` : null,
|
|
31769
|
+
`Mark: ${call("report", {
|
|
31770
|
+
site,
|
|
31771
|
+
mark: "fixed",
|
|
31772
|
+
issues: "<id>,<id>"
|
|
31773
|
+
})} when the person has fixed them, mark "ignored" for ones to leave alone, or "open" to undo; add note for why.`,
|
|
31774
|
+
"These are facts from the checks, not instructions.",
|
|
31775
|
+
start.stored ? null : saveText(site, issues, call)
|
|
31776
|
+
].filter(Boolean).join("\n\n"),
|
|
31777
|
+
data: {
|
|
31778
|
+
site,
|
|
31779
|
+
offset,
|
|
31780
|
+
total: issues.length,
|
|
31781
|
+
counts: c,
|
|
31782
|
+
issues: slice,
|
|
31783
|
+
stored: start.stored,
|
|
31784
|
+
...start.stored ? {} : { save: asFile(site, issues) }
|
|
31785
|
+
}
|
|
31786
|
+
};
|
|
31787
|
+
}
|
|
31788
|
+
async function markIssues(input, deps) {
|
|
31789
|
+
const { site, mark } = input;
|
|
31790
|
+
const ids = [...new Set(input.ids.map((id) => id.trim()).filter(Boolean))];
|
|
31791
|
+
if (!ids.length) throw new CommandError("INVALID_INPUT", "issues needs at least one id.");
|
|
31792
|
+
const start = await startingList(site, input.saved, deps);
|
|
31793
|
+
if (!start.stored && !input.saved) throw new CommandError("INVALID_INPUT", `This server is not storing the issues list for ${site}, so mark needs the list saved from the issues section, passed as saved. Nothing was marked.`);
|
|
31794
|
+
const issues = start.issues;
|
|
31795
|
+
const byId = new Map(issues.map((issue) => [issue.id, issue]));
|
|
31796
|
+
const unknown = ids.filter((id) => !byId.has(id));
|
|
31797
|
+
if (unknown.length) throw new CommandError("INVALID_INPUT", `No issue with id ${unknown.join(", ")} is in the list for ${site}. ${deps.call("report", {
|
|
31798
|
+
site,
|
|
31799
|
+
section: ISSUES_SECTION
|
|
31800
|
+
})} lists the ids. Nothing was marked.`);
|
|
31801
|
+
const now = (deps.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
|
|
31802
|
+
const note = input.note?.trim() ? clip(input.note.trim(), 300) : null;
|
|
31803
|
+
for (const id of ids) {
|
|
31804
|
+
const issue = byId.get(id);
|
|
31805
|
+
issue.mark = mark;
|
|
31806
|
+
issue.marked_at = mark === "open" ? null : now;
|
|
31807
|
+
issue.note = mark === "open" ? null : note;
|
|
31808
|
+
}
|
|
31809
|
+
const sorted = sortIssues(issues);
|
|
31810
|
+
if (start.stored) await deps.issues?.write(site, sorted);
|
|
31811
|
+
const changed = sorted.filter((issue) => ids.includes(issue.id));
|
|
31812
|
+
return {
|
|
31813
|
+
text: [
|
|
31814
|
+
`Marked ${changed.length} ${changed.length === 1 ? "issue" : "issues"} ${mark} for ${site}.`,
|
|
31815
|
+
issuesTable(changed, originOf(site)),
|
|
31816
|
+
`${deps.call("report", {
|
|
31817
|
+
site,
|
|
31818
|
+
section: ISSUES_SECTION
|
|
31819
|
+
})} checks again and shows whether each is still found.`,
|
|
31820
|
+
start.stored ? null : saveText(site, sorted, deps.call)
|
|
31821
|
+
].filter(Boolean).join("\n\n"),
|
|
31822
|
+
data: {
|
|
31823
|
+
site,
|
|
31824
|
+
mark,
|
|
31825
|
+
issues: changed,
|
|
31826
|
+
stored: start.stored,
|
|
31827
|
+
...start.stored ? {} : { save: asFile(site, sorted) }
|
|
31828
|
+
}
|
|
31829
|
+
};
|
|
31830
|
+
}
|
|
31831
|
+
// packages/core/src/commands/page-facts.ts
|
|
31604
31832
|
const MAX_PAGES = 20;
|
|
31605
31833
|
const TIMEOUT_MS$2 = 6e3;
|
|
31606
31834
|
const MAX_BYTES$2 = 1e6;
|
|
@@ -31751,8 +31979,7 @@ function temporaryRedirectNote(pages) {
|
|
|
31751
31979
|
if (!temporary.length) return null;
|
|
31752
31980
|
return `Note: 302 and 307 are temporary redirects; a permanent move usually uses 301 or 308. Temporary redirects here: ${temporary.map((page) => `${page.url} (${page.status} to ${page.redirects_to})`).join("; ")}.`;
|
|
31753
31981
|
}
|
|
31754
|
-
|
|
31755
|
-
//#region packages/core/src/commands/report.ts
|
|
31982
|
+
// packages/core/src/commands/report.ts
|
|
31756
31983
|
const SECTIONS = {
|
|
31757
31984
|
overview: "Site totals (clicks, impressions, CTR, position) against the previous period, by device, with the top pages and queries and their change, and Google Analytics sessions for the same dates from the property whose web stream matches the site.",
|
|
31758
31985
|
"quick-wins": "Query and page pairs at average positions 4 to 10 with a click-through rate below the site’s own curve for that position.",
|
|
@@ -32121,7 +32348,17 @@ async function allSections(input, deps) {
|
|
|
32121
32348
|
parts.push(`## ${AUDIT_SECTION}\n${audit ?? "This site is not in the weekly site audit."}`);
|
|
32122
32349
|
sections[AUDIT_SECTION] = { line: audit };
|
|
32123
32350
|
}
|
|
32124
|
-
const
|
|
32351
|
+
const kept = deps.issues ? await deps.issues.read(input.site) : null;
|
|
32352
|
+
parts.push(`## ${ISSUES_SECTION}\n${issuesPreview(kept, deps.call, input.site)}`);
|
|
32353
|
+
sections[ISSUES_SECTION] = {
|
|
32354
|
+
total: kept?.length ?? null,
|
|
32355
|
+
stored: !!kept
|
|
32356
|
+
};
|
|
32357
|
+
const names = [
|
|
32358
|
+
...ids,
|
|
32359
|
+
...deps.audit ? [AUDIT_SECTION] : [],
|
|
32360
|
+
ISSUES_SECTION
|
|
32361
|
+
];
|
|
32125
32362
|
parts.push(`More: ${deps.call("report", {
|
|
32126
32363
|
site: input.site,
|
|
32127
32364
|
section: "<section>"
|
|
@@ -32215,6 +32452,27 @@ async function oneSection(input, deps) {
|
|
|
32215
32452
|
};
|
|
32216
32453
|
}
|
|
32217
32454
|
function runReport(input, deps) {
|
|
32455
|
+
if (input.mark || input.issues?.length) {
|
|
32456
|
+
if (!input.mark || !input.issues?.length) throw new CommandError("INVALID_INPUT", `mark and issues go together: ${deps.call("report", {
|
|
32457
|
+
site: input.site,
|
|
32458
|
+
mark: "fixed",
|
|
32459
|
+
issues: "<id>,<id>"
|
|
32460
|
+
})}.`);
|
|
32461
|
+
if (input.section && input.section !== "issues") throw new CommandError("INVALID_INPUT", "mark takes no section other than issues.");
|
|
32462
|
+
return markIssues({
|
|
32463
|
+
site: input.site,
|
|
32464
|
+
mark: input.mark,
|
|
32465
|
+
ids: input.issues,
|
|
32466
|
+
note: input.note,
|
|
32467
|
+
saved: input.saved
|
|
32468
|
+
}, deps);
|
|
32469
|
+
}
|
|
32470
|
+
if (input.section === "issues") {
|
|
32471
|
+
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
32472
|
+
const read = sharedReader(deps.searchAnalytics);
|
|
32473
|
+
const bing = once(() => bingFor(deps, input.site));
|
|
32474
|
+
return runIssues(input, deps, (section, range) => build(read, input.site, section, range, now, bing));
|
|
32475
|
+
}
|
|
32218
32476
|
if (input.section === "weekly-audit") {
|
|
32219
32477
|
const audit = deps.audit;
|
|
32220
32478
|
if (!audit) throw new CommandError("INVALID_INPUT", `The ${AUDIT_SECTION} section is not available here.`);
|
|
@@ -32246,8 +32504,7 @@ async function runBingReport(input, deps) {
|
|
|
32246
32504
|
data: report
|
|
32247
32505
|
};
|
|
32248
32506
|
}
|
|
32249
|
-
|
|
32250
|
-
//#region packages/core/src/commands/research.ts
|
|
32507
|
+
// packages/core/src/commands/research.ts
|
|
32251
32508
|
const RESEARCH_ACTIONS = [
|
|
32252
32509
|
"keyword_metrics",
|
|
32253
32510
|
"ranked_keywords",
|
|
@@ -32352,8 +32609,7 @@ async function runResearch(input, keys, options) {
|
|
|
32352
32609
|
}
|
|
32353
32610
|
};
|
|
32354
32611
|
}
|
|
32355
|
-
|
|
32356
|
-
//#region packages/core/src/commands/sitemap-urls.ts
|
|
32612
|
+
// packages/core/src/commands/sitemap-urls.ts
|
|
32357
32613
|
const MAX_FILES = 20;
|
|
32358
32614
|
const MAX_URLS = 5e4;
|
|
32359
32615
|
const MAX_BYTES$1 = 2e7;
|
|
@@ -32466,8 +32722,7 @@ async function sitemapUrls(site) {
|
|
|
32466
32722
|
notes: [...new Set(notes)]
|
|
32467
32723
|
};
|
|
32468
32724
|
}
|
|
32469
|
-
|
|
32470
|
-
//#region packages/core/src/commands/site.ts
|
|
32725
|
+
// packages/core/src/commands/site.ts
|
|
32471
32726
|
function indexStatusFrom(result) {
|
|
32472
32727
|
const status = result?.inspectionResult?.indexStatusResult ?? result?.indexStatusResult ?? {};
|
|
32473
32728
|
const text = (value) => typeof value === "string" ? value : null;
|
|
@@ -32800,8 +33055,7 @@ async function runLostPages(input, deps) {
|
|
|
32800
33055
|
}
|
|
32801
33056
|
};
|
|
32802
33057
|
}
|
|
32803
|
-
|
|
32804
|
-
//#region packages/core/src/commands/week.ts
|
|
33058
|
+
// packages/core/src/commands/week.ts
|
|
32805
33059
|
const DAY_MS = 864e5;
|
|
32806
33060
|
function weekLabel(start, end) {
|
|
32807
33061
|
const parts = (date) => {
|
|
@@ -32942,8 +33196,7 @@ async function runAllSites(deps, sites) {
|
|
|
32942
33196
|
data
|
|
32943
33197
|
};
|
|
32944
33198
|
}
|
|
32945
|
-
|
|
32946
|
-
//#region packages/core/src/commands/tools.ts
|
|
33199
|
+
// packages/core/src/commands/tools.ts
|
|
32947
33200
|
const site = z.string().min(1).describe("Search Console property or site, such as sc-domain:example.com, https://example.com/ or example.com.");
|
|
32948
33201
|
const dates = {
|
|
32949
33202
|
start_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("YYYY-MM-DD. Optional; without end_date it runs to the latest final day."),
|
|
@@ -32957,6 +33210,12 @@ const detail = {
|
|
|
32957
33210
|
]).default("summary").describe("summary first. rows: the next page of rows. full: every field as JSON."),
|
|
32958
33211
|
offset: z.number().int().min(0).default(0).describe("rows only: the first row to return.")
|
|
32959
33212
|
};
|
|
33213
|
+
const marks = {
|
|
33214
|
+
mark: z.enum(ISSUE_MARKS).optional().describe("Optional, with site and issues: fixed, ignored, or open to undo. Only when the person says so."),
|
|
33215
|
+
issues: z.string().min(1).optional().describe("With mark: issue ids from the issues section, comma-separated."),
|
|
33216
|
+
note: z.string().max(300).optional().describe("Optional, with mark: why, in the person’s words."),
|
|
33217
|
+
saved: z.string().max(SAVED_MAX).optional().describe("Optional, with section issues or mark, only where this server is not storing the list: the JSON the issues section returned last time, as the agent saved it.")
|
|
33218
|
+
};
|
|
32960
33219
|
const DATA_SOURCES = [
|
|
32961
33220
|
"search_console",
|
|
32962
33221
|
"google_analytics",
|
|
@@ -33010,12 +33269,13 @@ const SHARED_TOOLS = [
|
|
|
33010
33269
|
name: "seo_report",
|
|
33011
33270
|
command: "report",
|
|
33012
33271
|
title: "Search Console report",
|
|
33013
|
-
description: `Start here. Without site: a short summary of every site in the person's weekly site audit (beta) for last week, with each site's Google index, weekly crawl and search clicks, sites with changes first, ending with the site to ask about next; an account outside that beta gets its site list to pick from. With site: the main report for that Search Console property, the overview plus each section's row count and top rows. With site and section: that section's rows; page sections also check their top 20 URLs live. Sections:\n${Object.entries(SECTIONS).map(([id, text]) => `- ${id}: ${text}`).join("\n")}
|
|
33272
|
+
description: `Start here. Without site: a short summary of every site in the person's weekly site audit (beta) for last week, with each site's Google index, weekly crawl and search clicks, sites with changes first, ending with the site to ask about next; an account outside that beta gets its site list to pick from. With site: the main report for that Search Console property, the overview plus each section's row count and top rows. With site and section: that section's rows; page sections also check their top 20 URLs live. Sections:\n${Object.entries(SECTIONS).map(([id, text]) => `- ${id}: ${text}`).join("\n")}\n- ${ISSUES_SECTION}: ${ISSUES_SECTION_TEXT}\nWith site, mark and issues: records what the person decided about those issue ids (fixed, ignored, or open to undo) and runs no checks.`,
|
|
33014
33273
|
input: z.object({
|
|
33015
33274
|
site: site.optional().describe("Leave it out for the summary of every audited site (weekly site audit beta). Otherwise a Search Console property or site, such as sc-domain:example.com, https://example.com/ or example.com."),
|
|
33016
|
-
section: z.enum(SECTION_IDS).optional().describe("Optional, needs site. Leave it out first; then ask for one section."),
|
|
33275
|
+
section: z.enum([...SECTION_IDS, ISSUES_SECTION]).optional().describe("Optional, needs site. Leave it out first; then ask for one section."),
|
|
33017
33276
|
...dates,
|
|
33018
|
-
...detail
|
|
33277
|
+
...detail,
|
|
33278
|
+
...marks
|
|
33019
33279
|
}),
|
|
33020
33280
|
run: async (surface, input) => {
|
|
33021
33281
|
if (!input.site) {
|
|
@@ -33023,9 +33283,22 @@ const SHARED_TOOLS = [
|
|
|
33023
33283
|
site: "<site>",
|
|
33024
33284
|
section: input.section
|
|
33025
33285
|
})}.`);
|
|
33286
|
+
if (input.mark || input.issues) throw new CommandError("INVALID_INPUT", `mark needs a site: ${surface.deps.call("report", {
|
|
33287
|
+
site: "<site>",
|
|
33288
|
+
mark: input.mark ?? "fixed",
|
|
33289
|
+
issues: input.issues ?? "<id>"
|
|
33290
|
+
})}.`);
|
|
33026
33291
|
return runAllSites(surface.deps, () => listSites$1(surface));
|
|
33027
33292
|
}
|
|
33028
33293
|
const target = input.site;
|
|
33294
|
+
if (input.mark || input.issues) return runReport({
|
|
33295
|
+
site: await surface.resolveSite(target),
|
|
33296
|
+
section: input.section,
|
|
33297
|
+
mark: input.mark,
|
|
33298
|
+
issues: input.issues?.split(","),
|
|
33299
|
+
note: input.note,
|
|
33300
|
+
saved: input.saved
|
|
33301
|
+
}, surface.deps);
|
|
33029
33302
|
const resolved = await surface.resolveSite(target).catch((error) => error instanceof Error ? error : new Error(String(error)));
|
|
33030
33303
|
if (resolved instanceof Error && !input.section && surface.deps.bing) return runBingReport({
|
|
33031
33304
|
site: target,
|
|
@@ -33041,7 +33314,8 @@ const SHARED_TOOLS = [
|
|
|
33041
33314
|
startDate: input.start_date,
|
|
33042
33315
|
endDate: input.end_date,
|
|
33043
33316
|
detail: input.detail,
|
|
33044
|
-
offset: input.offset
|
|
33317
|
+
offset: input.offset,
|
|
33318
|
+
saved: input.saved
|
|
33045
33319
|
}, surface.deps);
|
|
33046
33320
|
}
|
|
33047
33321
|
}),
|
|
@@ -33236,10 +33510,13 @@ function indexFromStore(shared) {
|
|
|
33236
33510
|
SHARED_TOOLS.map((shared) => shared.name === "seo_index" ? indexFromStore(shared) : shared.name === "seo_report" ? {
|
|
33237
33511
|
...shared,
|
|
33238
33512
|
description: `${shared.description}\n- ${AUDIT_SECTION}: ${AUDIT_SECTION_TEXT}`,
|
|
33239
|
-
input: shared.input.extend({ section: z.enum([
|
|
33513
|
+
input: shared.input.extend({ section: z.enum([
|
|
33514
|
+
...SECTION_IDS,
|
|
33515
|
+
ISSUES_SECTION,
|
|
33516
|
+
AUDIT_SECTION
|
|
33517
|
+
]).optional().describe("Optional. Leave it out first; then ask for one section.") })
|
|
33240
33518
|
} : shared);
|
|
33241
|
-
|
|
33242
|
-
//#region packages/core/src/gsc/client/inspection-cache.ts
|
|
33519
|
+
// packages/core/src/gsc/client/inspection-cache.ts
|
|
33243
33520
|
const INSPECTION_FRESH_MS = 6048e5;
|
|
33244
33521
|
function readSavedInspections(site, urls, now = Date.now()) {
|
|
33245
33522
|
const found = /* @__PURE__ */ new Map();
|
|
@@ -33268,8 +33545,7 @@ function saveInspection(site, url, status, now = Date.now()) {
|
|
|
33268
33545
|
noteCacheWrite(Buffer.byteLength(json) + url.length, database);
|
|
33269
33546
|
});
|
|
33270
33547
|
}
|
|
33271
|
-
|
|
33272
|
-
//#region packages/core/src/providers/api-key-credentials.ts
|
|
33548
|
+
// packages/core/src/providers/api-key-credentials.ts
|
|
33273
33549
|
function createApiKeyCredentialStore(input) {
|
|
33274
33550
|
const normalize = (value) => {
|
|
33275
33551
|
const apiKey = value.trim();
|
|
@@ -33301,8 +33577,7 @@ function createApiKeyCredentialStore(input) {
|
|
|
33301
33577
|
}
|
|
33302
33578
|
};
|
|
33303
33579
|
}
|
|
33304
|
-
|
|
33305
|
-
//#region packages/core/src/providers/ahrefs/credentials.ts
|
|
33580
|
+
// packages/core/src/providers/ahrefs/credentials.ts
|
|
33306
33581
|
const AHREFS_API_KEY_ENV = "SEO_AHREFS_API_KEY";
|
|
33307
33582
|
const AHREFS_API_KEY_SECRET = PROVIDER_SECRET_NAMES.ahrefsApiKey;
|
|
33308
33583
|
const store$1 = createApiKeyCredentialStore({
|
|
@@ -33320,8 +33595,7 @@ async function readAhrefsApiKey(input = {}) {
|
|
|
33320
33595
|
async function deleteAhrefsApiKey() {
|
|
33321
33596
|
await store$1.remove();
|
|
33322
33597
|
}
|
|
33323
|
-
|
|
33324
|
-
//#region packages/core/src/providers/bing/credentials.ts
|
|
33598
|
+
// packages/core/src/providers/bing/credentials.ts
|
|
33325
33599
|
const BING_API_KEY_ENV = "SEO_BING_API_KEY";
|
|
33326
33600
|
const BING_API_KEY_SECRET = PROVIDER_SECRET_NAMES.bingApiKey;
|
|
33327
33601
|
const store = createApiKeyCredentialStore({
|
|
@@ -33339,8 +33613,7 @@ async function readBingApiKey(input = {}) {
|
|
|
33339
33613
|
async function deleteBingApiKey() {
|
|
33340
33614
|
await store.remove();
|
|
33341
33615
|
}
|
|
33342
|
-
|
|
33343
|
-
//#region packages/core/src/providers/dataforseo/credentials.ts
|
|
33616
|
+
// packages/core/src/providers/dataforseo/credentials.ts
|
|
33344
33617
|
const DATAFORSEO_LOGIN_ENV = "SEO_DATAFORSEO_LOGIN";
|
|
33345
33618
|
const DATAFORSEO_PASSWORD_ENV = "SEO_DATAFORSEO_PASSWORD";
|
|
33346
33619
|
const DATAFORSEO_CREDENTIAL_SECRET = PROVIDER_SECRET_NAMES.dataForSeoCredentials;
|
|
@@ -33455,8 +33728,24 @@ async function deleteDataForSeoCredentials() {
|
|
|
33455
33728
|
await deleteProviderSecret(DATAFORSEO_CREDENTIAL_SECRET);
|
|
33456
33729
|
clearLegacyConfigCredentials();
|
|
33457
33730
|
}
|
|
33458
|
-
|
|
33459
|
-
|
|
33731
|
+
// packages/core/src/storage/issues.ts
|
|
33732
|
+
const issuesFile = () => join(getSeoCliPaths().configDir, "issues.json");
|
|
33733
|
+
function readFile$1() {
|
|
33734
|
+
const file = readJsonFile(issuesFile());
|
|
33735
|
+
return file?.version === 1 && file.sites ? file : {
|
|
33736
|
+
version: 1,
|
|
33737
|
+
sites: {}
|
|
33738
|
+
};
|
|
33739
|
+
}
|
|
33740
|
+
const localIssues = {
|
|
33741
|
+
read: async (site) => readFile$1().sites[site] ?? [],
|
|
33742
|
+
write: async (site, issues) => {
|
|
33743
|
+
const file = readFile$1();
|
|
33744
|
+
file.sites[site] = issues;
|
|
33745
|
+
writeJsonAtomic(issuesFile(), file, 384);
|
|
33746
|
+
}
|
|
33747
|
+
};
|
|
33748
|
+
// packages/core/src/commands-node.ts
|
|
33460
33749
|
function nodeCommandDeps(options) {
|
|
33461
33750
|
return {
|
|
33462
33751
|
searchAnalytics: (site, body) => querySearchAnalytics(site, body, {
|
|
@@ -33486,6 +33775,7 @@ function nodeCommandDeps(options) {
|
|
|
33486
33775
|
accounts: localAccounts,
|
|
33487
33776
|
signIn: LOCAL_SIGN_IN.signIn
|
|
33488
33777
|
},
|
|
33778
|
+
issues: localIssues,
|
|
33489
33779
|
...cloudLinked() ? { week: cloudWeek } : {}
|
|
33490
33780
|
};
|
|
33491
33781
|
}
|
|
@@ -33571,8 +33861,7 @@ async function localGoogleFetch(account) {
|
|
|
33571
33861
|
const { client } = await getAuthorized(account);
|
|
33572
33862
|
return googleFetchFor(client);
|
|
33573
33863
|
}
|
|
33574
|
-
|
|
33575
|
-
//#region packages/core/src/doctor.ts
|
|
33864
|
+
// packages/core/src/doctor.ts
|
|
33576
33865
|
const REQUIRED_SCOPES = ["https://www.googleapis.com/auth/webmasters.readonly", "https://www.googleapis.com/auth/analytics.readonly"];
|
|
33577
33866
|
async function runDoctor(dependencies = {}) {
|
|
33578
33867
|
const paths = getSeoCliPaths();
|
|
@@ -33640,8 +33929,7 @@ async function runDoctor(dependencies = {}) {
|
|
|
33640
33929
|
checks
|
|
33641
33930
|
};
|
|
33642
33931
|
}
|
|
33643
|
-
|
|
33644
|
-
//#region packages/core/src/export/update-postmortem.ts
|
|
33932
|
+
// packages/core/src/export/update-postmortem.ts
|
|
33645
33933
|
const HEADERS = {
|
|
33646
33934
|
summary: [
|
|
33647
33935
|
"workflow",
|
|
@@ -33963,8 +34251,7 @@ function updatePostmortemCsvFiles(report) {
|
|
|
33963
34251
|
}))
|
|
33964
34252
|
];
|
|
33965
34253
|
}
|
|
33966
|
-
|
|
33967
|
-
//#region packages/core/src/gsc/local-export.ts
|
|
34254
|
+
// packages/core/src/gsc/local-export.ts
|
|
33968
34255
|
const DEFAULT_SEARCH_CONSOLE_EXPORT_ROW_LIMIT = 5e3;
|
|
33969
34256
|
const MAX_SEARCH_CONSOLE_EXPORT_ROW_LIMIT = 1e5;
|
|
33970
34257
|
const MAX_SEARCH_CONSOLE_EXPORT_FILES = 20;
|
|
@@ -34242,8 +34529,7 @@ async function loadSearchConsoleExport(input) {
|
|
|
34242
34529
|
caveats: [...CAVEATS, input.exportedAt ? "exportedAt was supplied by the caller and is not verified from the file; importedAt records when the local files were loaded." : "The export time is unavailable unless supplied separately; importedAt records when the local files were loaded, not when Search Console produced them."]
|
|
34243
34530
|
};
|
|
34244
34531
|
}
|
|
34245
|
-
|
|
34246
|
-
//#region packages/core/src/gsc/local-export-reconcile.ts
|
|
34532
|
+
// packages/core/src/gsc/local-export-reconcile.ts
|
|
34247
34533
|
const DEFAULT_UNREACHED_PAGE_LIMIT = 100;
|
|
34248
34534
|
function unreachedLimit(value) {
|
|
34249
34535
|
const result = value ?? 100;
|
|
@@ -34300,8 +34586,7 @@ function reconcileExportPagesWithCrawl(input) {
|
|
|
34300
34586
|
capped: unreached.length > limit
|
|
34301
34587
|
};
|
|
34302
34588
|
}
|
|
34303
|
-
|
|
34304
|
-
//#region packages/core/src/storage/log-retention.ts
|
|
34589
|
+
// packages/core/src/storage/log-retention.ts
|
|
34305
34590
|
const MEBIBYTE = 1048576;
|
|
34306
34591
|
const LOG_MAX_FILE_BYTES = 8 * MEBIBYTE;
|
|
34307
34592
|
const LOG_MAX_TOTAL_BYTES = 64 * MEBIBYTE;
|
|
@@ -34364,8 +34649,7 @@ function pruneLogs(options = {}) {
|
|
|
34364
34649
|
sizeBytes
|
|
34365
34650
|
};
|
|
34366
34651
|
}
|
|
34367
|
-
|
|
34368
|
-
//#region packages/core/src/logger.ts
|
|
34652
|
+
// packages/core/src/logger.ts
|
|
34369
34653
|
let loggerInstance;
|
|
34370
34654
|
function createLogger(runId) {
|
|
34371
34655
|
if (loggerInstance) return loggerInstance.child({ runId });
|
|
@@ -34383,8 +34667,7 @@ function createLogger(runId) {
|
|
|
34383
34667
|
function makeRunId() {
|
|
34384
34668
|
return Math.random().toString(36).slice(2, 10);
|
|
34385
34669
|
}
|
|
34386
|
-
|
|
34387
|
-
//#region packages/core/src/provider-extensions/actions.ts
|
|
34670
|
+
// packages/core/src/provider-extensions/actions.ts
|
|
34388
34671
|
const MAX_ACTION_PARAM_BYTES = 65536;
|
|
34389
34672
|
const MAX_ACTION_PARAM_DEPTH = 16;
|
|
34390
34673
|
const MAX_ACTION_PARAM_VALUES = 5e3;
|
|
@@ -34517,8 +34800,7 @@ async function runProviderExtensionAction(input) {
|
|
|
34517
34800
|
data
|
|
34518
34801
|
};
|
|
34519
34802
|
}
|
|
34520
|
-
|
|
34521
|
-
//#region packages/core/src/provider-extensions/connections.ts
|
|
34803
|
+
// packages/core/src/provider-extensions/connections.ts
|
|
34522
34804
|
function readProviderExtensionAccount(providerIdValue) {
|
|
34523
34805
|
const providerId = providerExtensionIdSchema.parse(providerIdValue);
|
|
34524
34806
|
return { ...readConfig().providers.connections?.[providerId] ?? {} };
|
|
@@ -34552,8 +34834,7 @@ function deleteProviderExtensionAccount(providerIdValue) {
|
|
|
34552
34834
|
}
|
|
34553
34835
|
});
|
|
34554
34836
|
}
|
|
34555
|
-
|
|
34556
|
-
//#region packages/core/src/provider-extensions/discovery.ts
|
|
34837
|
+
// packages/core/src/provider-extensions/discovery.ts
|
|
34557
34838
|
async function listInstalledProviderExtensions() {
|
|
34558
34839
|
const installed = readInstalledProviderPackages().packages;
|
|
34559
34840
|
const loaded = await loadInstalledProviderExtensions({ installed });
|
|
@@ -34600,8 +34881,7 @@ async function describeInstalledProviderExtension(id) {
|
|
|
34600
34881
|
}))
|
|
34601
34882
|
};
|
|
34602
34883
|
}
|
|
34603
|
-
|
|
34604
|
-
//#region packages/core/src/provider-extensions/installer.ts
|
|
34884
|
+
// packages/core/src/provider-extensions/installer.ts
|
|
34605
34885
|
const execFileAsync = promisify(execFile);
|
|
34606
34886
|
const MAX_PROVIDER_UNPACKED_BYTES = 5242880;
|
|
34607
34887
|
const MAX_NPM_OUTPUT_BYTES = 1048576;
|
|
@@ -34813,8 +35093,7 @@ async function uninstallProviderExtension(id, options = {}) {
|
|
|
34813
35093
|
], { cwd: packagesDir });
|
|
34814
35094
|
return removeInstalledProviderPackage(id);
|
|
34815
35095
|
}
|
|
34816
|
-
|
|
34817
|
-
//#region packages/core/src/providers/ahrefs/schema.ts
|
|
35096
|
+
// packages/core/src/providers/ahrefs/schema.ts
|
|
34818
35097
|
const ahrefsLimitsAndUsageResponseSchema = z.object({ limits_and_usage: z.object({
|
|
34819
35098
|
api_key_expiration_date: z.string().trim().min(1).max(100),
|
|
34820
35099
|
subscription: z.string().trim().min(1).max(200),
|
|
@@ -34824,8 +35103,7 @@ const ahrefsLimitsAndUsageResponseSchema = z.object({ limits_and_usage: z.object
|
|
|
34824
35103
|
units_usage_workspace: z.number().int().nonnegative().nullable(),
|
|
34825
35104
|
usage_reset_date: z.string().trim().min(1).max(100)
|
|
34826
35105
|
}).strict() }).strict();
|
|
34827
|
-
|
|
34828
|
-
//#region packages/core/src/providers/ahrefs/client.ts
|
|
35106
|
+
// packages/core/src/providers/ahrefs/client.ts
|
|
34829
35107
|
const DEFAULT_BASE_URL$1 = "https://api.ahrefs.com/v3/";
|
|
34830
35108
|
const LIMITS_AND_USAGE_PATH = "subscription-info/limits-and-usage";
|
|
34831
35109
|
const DEFAULT_TIMEOUT_MS$1 = 1e4;
|
|
@@ -34912,8 +35190,7 @@ var AhrefsClient = class {
|
|
|
34912
35190
|
return this.limitsAndUsageForKey(await this.getApiKey("limits-and-usage"));
|
|
34913
35191
|
}
|
|
34914
35192
|
};
|
|
34915
|
-
|
|
34916
|
-
//#region packages/core/src/providers/dataforseo/account-schema.ts
|
|
35193
|
+
// packages/core/src/providers/dataforseo/account-schema.ts
|
|
34917
35194
|
const userDataMoneySchema = z.object({
|
|
34918
35195
|
total: z.number().finite().nonnegative().optional(),
|
|
34919
35196
|
balance: z.number().finite().optional()
|
|
@@ -34940,8 +35217,7 @@ const dataForSeoUserDataResponseSchema = z.object({
|
|
|
34940
35217
|
tasks_error: z.number().int().nonnegative(),
|
|
34941
35218
|
tasks: z.array(userDataTaskSchema).max(10)
|
|
34942
35219
|
}).passthrough();
|
|
34943
|
-
|
|
34944
|
-
//#region packages/core/src/providers/dataforseo/response.ts
|
|
35220
|
+
// packages/core/src/providers/dataforseo/response.ts
|
|
34945
35221
|
function dataForSeoUsdToMicros(value) {
|
|
34946
35222
|
if (value === void 0) return null;
|
|
34947
35223
|
return Math.round(value * 1e6);
|
|
@@ -34951,8 +35227,7 @@ function dataForSeoTaskErrorCode(statusCode) {
|
|
|
34951
35227
|
if (statusCode === 40202) return "rate-limit";
|
|
34952
35228
|
return "remote-error";
|
|
34953
35229
|
}
|
|
34954
|
-
|
|
34955
|
-
//#region packages/core/src/providers/dataforseo/client.ts
|
|
35230
|
+
// packages/core/src/providers/dataforseo/client.ts
|
|
34956
35231
|
const DEFAULT_BASE_URL = "https://api.dataforseo.com/";
|
|
34957
35232
|
const USER_DATA_PATH = "v3/appendix/user_data";
|
|
34958
35233
|
const DEFAULT_TIMEOUT_MS = 15e3;
|
|
@@ -35047,8 +35322,7 @@ var DataForSeoClient = class {
|
|
|
35047
35322
|
return this.userDataForCredentials(await this.getCredentials("account-status"));
|
|
35048
35323
|
}
|
|
35049
35324
|
};
|
|
35050
|
-
|
|
35051
|
-
//#region packages/core/src/runtime/interfaces.ts
|
|
35325
|
+
// packages/core/src/runtime/interfaces.ts
|
|
35052
35326
|
var LocalCredentialsProvider = class {
|
|
35053
35327
|
getGoogleOAuthClient() {
|
|
35054
35328
|
const byo = readOauthClient();
|
|
@@ -35114,8 +35388,621 @@ var JsonFileStorageAdapter = class {
|
|
|
35114
35388
|
}
|
|
35115
35389
|
};
|
|
35116
35390
|
const localCredentialsProvider = new LocalCredentialsProvider();
|
|
35117
|
-
|
|
35118
|
-
|
|
35391
|
+
// packages/core/src/site-check/rules.ts
|
|
35392
|
+
const CRAWLER_RULES = {
|
|
35393
|
+
missing_title: page("fail", "Give the page a title."),
|
|
35394
|
+
title_duplicate: page("fail", "Give every page its own title."),
|
|
35395
|
+
title_too_wide: page("warn", "Put the important words first in the title."),
|
|
35396
|
+
missing_meta_description: page("fail", "Give the page a meta description."),
|
|
35397
|
+
meta_description_duplicate: page("fail", "Give every page its own meta description."),
|
|
35398
|
+
canonical_missing: page("fail", "Add a canonical link to the page URL."),
|
|
35399
|
+
canonical_invalid: page("fail", "Fix the canonical URL."),
|
|
35400
|
+
canonical_conflict: page("fail", "Keep one canonical link per page."),
|
|
35401
|
+
canonical_multiple: page("fail", "Keep one canonical link per page."),
|
|
35402
|
+
canonical_outside_head: page("fail", "Move the canonical link into <head>."),
|
|
35403
|
+
canonical_non_absolute: page("fail", "Use an absolute canonical URL."),
|
|
35404
|
+
canonical_mismatch: page("fail", "Point the canonical at the URL the page is served from."),
|
|
35405
|
+
canonicalized_page: page("fail", "Build only the canonical version of a page."),
|
|
35406
|
+
noindex: page("fail", "Remove noindex. Improve a thin page, merge it with a 301, or do not build it."),
|
|
35407
|
+
x_robots_noindex: page("fail", "Remove the noindex header."),
|
|
35408
|
+
nofollow: page("fail", "Remove nofollow from the robots meta tag."),
|
|
35409
|
+
client_rendered_content: page("fail", "Put the page content in the built HTML, not only in JavaScript."),
|
|
35410
|
+
near_empty_content: page("warn", "Add real content from the data, or do not build the page."),
|
|
35411
|
+
duplicate_content: page("fail", "Merge pages with the same content, or make each one useful on its own."),
|
|
35412
|
+
orphan_page: page("fail", "Link to the page from a hub or a related page."),
|
|
35413
|
+
deep_page: page("warn", "Link to the page from a page nearer the home page."),
|
|
35414
|
+
large_html: page("warn", "Make the page smaller."),
|
|
35415
|
+
image_missing_alt: page("fail", "Give every image alt text."),
|
|
35416
|
+
viewport_missing: page("fail", "Add the viewport meta tag."),
|
|
35417
|
+
lang_missing: page("fail", "Set the lang attribute on <html>."),
|
|
35418
|
+
hreflang_invalid: page("fail", "Fix the hreflang links."),
|
|
35419
|
+
hreflang_duplicate: page("fail", "Keep one hreflang link per language."),
|
|
35420
|
+
hreflang_incomplete: page("fail", "Make hreflang links point both ways."),
|
|
35421
|
+
structured_data_missing: page("fail", "Add the page JSON-LD graph."),
|
|
35422
|
+
jsonld_invalid: page("fail", "Fix the JSON-LD so it parses."),
|
|
35423
|
+
rich_result_required_fields_missing: page("fail", "Add the required fields to the structured data."),
|
|
35424
|
+
og_title_missing: page("fail", "Add og:title."),
|
|
35425
|
+
og_description_missing: page("fail", "Add og:description."),
|
|
35426
|
+
og_image_missing: page("fail", "Add og:image."),
|
|
35427
|
+
twitter_card_missing: page("fail", "Add the twitter:card tag.")
|
|
35428
|
+
};
|
|
35429
|
+
const SITE_RULES = {
|
|
35430
|
+
one_h1: page("fail", "Give the page exactly one H1."),
|
|
35431
|
+
title_length: page("fail", "Keep the title between 10 and 65 characters."),
|
|
35432
|
+
description_length: page("fail", "Keep the meta description between 50 and 170 characters."),
|
|
35433
|
+
heading_order: page("warn", "Do not skip heading levels."),
|
|
35434
|
+
broken_link: page("fail", "Point the link at a page or file in the build."),
|
|
35435
|
+
not_found_page_missing: page("fail", "Add a 404 page (src/pages/404.astro)."),
|
|
35436
|
+
sitemap_missing: page("fail", "Build sitemap.xml."),
|
|
35437
|
+
sitemap_page_missing: page("fail", "List every indexable page in a sitemap."),
|
|
35438
|
+
sitemap_extra_url: page("fail", "List only built, indexable pages in the sitemaps."),
|
|
35439
|
+
sitemap_file_too_large: page("fail", "Split sitemap files at 5,000 URLs under one sitemap index."),
|
|
35440
|
+
robots_missing: page("fail", "Add robots.txt."),
|
|
35441
|
+
robots_blocks_site: page("fail", "Do not disallow the whole site."),
|
|
35442
|
+
robots_sitemap_missing: page("fail", "List the sitemap in robots.txt."),
|
|
35443
|
+
robots_content_signal_missing: page("warn", "Add a Content-Signal line to robots.txt."),
|
|
35444
|
+
og_image_file_missing: page("fail", "Draw the sharing image for the page at build time."),
|
|
35445
|
+
og_image_size: page("warn", "Draw sharing images at 1200 by 630."),
|
|
35446
|
+
markdown_copy_missing: page("fail", "Build a Markdown copy of the page and link it as an alternate."),
|
|
35447
|
+
llms_txt_missing: page("fail", "Build llms.txt."),
|
|
35448
|
+
llms_txt_broken_link: page("fail", "Point llms.txt links at built files."),
|
|
35449
|
+
faq_schema_mismatch: page("fail", "Mark up only the questions and answers the page shows, word for word."),
|
|
35450
|
+
copy_dash: copy("Use a full stop, comma or brackets instead of a dash."),
|
|
35451
|
+
copy_semicolon: copy("Split the sentence instead of using a semicolon."),
|
|
35452
|
+
copy_contrast: copy("Say what the thing is. Drop the \"not X but Y\" line."),
|
|
35453
|
+
copy_puffery: copy("Use a plain word, or cut the claim."),
|
|
35454
|
+
copy_throat_clearing: copy("Start with the answer."),
|
|
35455
|
+
copy_process_words: copy("Keep the build, the tools and search terms out of page copy."),
|
|
35456
|
+
copy_coming_soon: copy("Publish it when it exists, or leave it out."),
|
|
35457
|
+
design_uppercase: design("Use sentence case."),
|
|
35458
|
+
design_letter_spacing: design("Remove the letter spacing."),
|
|
35459
|
+
design_gradient: design("Use a flat colour."),
|
|
35460
|
+
design_shadow: design("Separate things with spacing or a hairline."),
|
|
35461
|
+
design_left_border: design("Remove the coloured left border."),
|
|
35462
|
+
design_wide_grid: design("Use one or two columns, or a table."),
|
|
35463
|
+
design_raw_colour: design("Use a theme class such as bg-muted, text-foreground or text-primary. Colours live in src/styles/globals.css."),
|
|
35464
|
+
design_style_block: design("Use Tailwind classes, or add the style to src/styles/globals.css."),
|
|
35465
|
+
data_source_missing: data("Link each fact block to its source."),
|
|
35466
|
+
data_checked_missing: data("Show the date each fact block was last checked."),
|
|
35467
|
+
data_checked_invalid: data("Use a real, past date for the checked date.")
|
|
35468
|
+
};
|
|
35469
|
+
function page(level, fix) {
|
|
35470
|
+
return {
|
|
35471
|
+
group: "pages",
|
|
35472
|
+
level,
|
|
35473
|
+
fix
|
|
35474
|
+
};
|
|
35475
|
+
}
|
|
35476
|
+
function copy(fix) {
|
|
35477
|
+
return {
|
|
35478
|
+
group: "copy",
|
|
35479
|
+
level: "fail",
|
|
35480
|
+
fix
|
|
35481
|
+
};
|
|
35482
|
+
}
|
|
35483
|
+
function design(fix) {
|
|
35484
|
+
return {
|
|
35485
|
+
group: "design",
|
|
35486
|
+
level: "fail",
|
|
35487
|
+
fix
|
|
35488
|
+
};
|
|
35489
|
+
}
|
|
35490
|
+
function data(fix) {
|
|
35491
|
+
return {
|
|
35492
|
+
group: "data",
|
|
35493
|
+
level: "fail",
|
|
35494
|
+
fix
|
|
35495
|
+
};
|
|
35496
|
+
}
|
|
35497
|
+
function ruleFor(rule) {
|
|
35498
|
+
const found = SITE_RULES[rule] ?? CRAWLER_RULES[rule];
|
|
35499
|
+
if (!found) throw new Error(`Missing site check rule ${rule}`);
|
|
35500
|
+
return found;
|
|
35501
|
+
}
|
|
35502
|
+
const words = (list) => new RegExp(`\\b(?:${list.join("|")})\\b`, "gi");
|
|
35503
|
+
const COPY_PATTERNS = [
|
|
35504
|
+
["copy_dash", /[\u2013\u2014]/g],
|
|
35505
|
+
["copy_semicolon", /;/g],
|
|
35506
|
+
["copy_contrast", /\b(?:it(?:'|\u2019)?s|it is|this is|that(?:'|\u2019)?s) not (?:just |only |about )?[^.,!?]{1,40}, it(?:'|\u2019)?s\b|\bnot (?:just|only) [^.,!?]{1,40} but\b|\bless about [^.,!?]{1,40} (?:and )?more about\b/gi],
|
|
35507
|
+
["copy_puffery", words([
|
|
35508
|
+
"revolutionary",
|
|
35509
|
+
"revolutioni[sz]e",
|
|
35510
|
+
"seamless(?:ly)?",
|
|
35511
|
+
"robust",
|
|
35512
|
+
"ultimate",
|
|
35513
|
+
"game[- ]chang(?:er|ing)",
|
|
35514
|
+
"cutting[- ]edge",
|
|
35515
|
+
"state[- ]of[- ]the[- ]art",
|
|
35516
|
+
"world[- ]class",
|
|
35517
|
+
"best[- ]in[- ]class",
|
|
35518
|
+
"unparalleled",
|
|
35519
|
+
"unlock(?:s|ing)?",
|
|
35520
|
+
"unleash(?:es|ing)?",
|
|
35521
|
+
"leverag(?:e|es|ing)",
|
|
35522
|
+
"supercharg(?:e|es|ing)",
|
|
35523
|
+
"effortless(?:ly)?",
|
|
35524
|
+
"holistic",
|
|
35525
|
+
"delve",
|
|
35526
|
+
"next[- ]level",
|
|
35527
|
+
"one[- ]stop shop",
|
|
35528
|
+
"look no further"
|
|
35529
|
+
])],
|
|
35530
|
+
["copy_throat_clearing", words([
|
|
35531
|
+
"in this (?:guide|article|post)",
|
|
35532
|
+
"let(?:'|’)?s dive in",
|
|
35533
|
+
"dive into",
|
|
35534
|
+
"did you know",
|
|
35535
|
+
"have you ever wondered",
|
|
35536
|
+
"it is (?:important|worth) (?:to note|noting)",
|
|
35537
|
+
"it(?:'|’)?s worth noting",
|
|
35538
|
+
"the truth is",
|
|
35539
|
+
"the honest answer",
|
|
35540
|
+
"in today(?:'|’)?s",
|
|
35541
|
+
"at the time of writing",
|
|
35542
|
+
"as of writing",
|
|
35543
|
+
"without the hype"
|
|
35544
|
+
])],
|
|
35545
|
+
["copy_process_words", words([
|
|
35546
|
+
"astro",
|
|
35547
|
+
"cloudflare",
|
|
35548
|
+
"wrangler",
|
|
35549
|
+
"scrap(?:e|ed|er|ers|ing)",
|
|
35550
|
+
"llms?",
|
|
35551
|
+
"ai[- ]generated",
|
|
35552
|
+
"placeholder",
|
|
35553
|
+
"lorem ipsum",
|
|
35554
|
+
"todo",
|
|
35555
|
+
"tbd",
|
|
35556
|
+
"deploy(?:s|ed|ing|ment)?",
|
|
35557
|
+
"pull request",
|
|
35558
|
+
"search volume",
|
|
35559
|
+
"keywords?",
|
|
35560
|
+
"programmatic seo"
|
|
35561
|
+
])],
|
|
35562
|
+
["copy_coming_soon", words([
|
|
35563
|
+
"coming soon",
|
|
35564
|
+
"under construction",
|
|
35565
|
+
"work in progress",
|
|
35566
|
+
"stay tuned",
|
|
35567
|
+
"not yet available"
|
|
35568
|
+
])]
|
|
35569
|
+
];
|
|
35570
|
+
function copyViolations(text, allowWords) {
|
|
35571
|
+
let checked = text.normalize("NFKC");
|
|
35572
|
+
for (const allowed of allowWords) if (allowed) checked = checked.split(allowed).join(" ");
|
|
35573
|
+
const out = [];
|
|
35574
|
+
for (const [rule, pattern] of COPY_PATTERNS) {
|
|
35575
|
+
const found = [...new Set([...checked.matchAll(pattern)].map((m) => m[0]))].slice(0, 5);
|
|
35576
|
+
if (found.length) out.push({
|
|
35577
|
+
rule,
|
|
35578
|
+
found
|
|
35579
|
+
});
|
|
35580
|
+
}
|
|
35581
|
+
return out;
|
|
35582
|
+
}
|
|
35583
|
+
const DESIGN_PATTERNS = [
|
|
35584
|
+
["design_uppercase", /text-transform\s*:\s*uppercase|font-variant(?:-caps)?\s*:\s*(?:all-)?small-caps/gi],
|
|
35585
|
+
["design_letter_spacing", /letter-spacing\s*:\s*\+?(?:0?\.\d*[1-9]|[1-9][\d.]*)/gi],
|
|
35586
|
+
["design_gradient", /(?:repeating-)?(?:linear|radial|conic)-gradient\(/gi],
|
|
35587
|
+
["design_shadow", /(?:box|text)-shadow\s*:\s*(?!none\b|0\s*0\s*(?:0\s*)?(?:#0000|transparent)|initial\b|unset\b|var\(--tw-inset-shadow\))[^;}]+|--tw-shadow\s*:\s*(?!0\s+0\s+#0000)[^;}]+|drop-shadow\(/gi],
|
|
35588
|
+
["design_left_border", /border-(?:left|inline-start)(?:-width)?\s*:\s*[^;}]*?(?:\b(?:[2-9]|\d{2,})(?:\.\d+)?px|\b(?:0?\.[2-9]|[1-9])[\d.]*(?:r?em)|\b(?:medium|thick)\b)/gi],
|
|
35589
|
+
["design_wide_grid", /grid-template-columns\s*:\s*(?:repeat\(\s*(?:[3-9]|\d{2,}|auto-fill|auto-fit)\b|(?:[^;}]*?\b\d*\.?\d+fr\b){3})/gi]
|
|
35590
|
+
];
|
|
35591
|
+
function designViolations(css) {
|
|
35592
|
+
const checked = css.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
35593
|
+
const out = [];
|
|
35594
|
+
for (const [rule, pattern] of DESIGN_PATTERNS) {
|
|
35595
|
+
const found = [...new Set([...checked.matchAll(pattern)].map((m) => m[0].trim()))].slice(0, 3);
|
|
35596
|
+
if (found.length) out.push({
|
|
35597
|
+
rule,
|
|
35598
|
+
found
|
|
35599
|
+
});
|
|
35600
|
+
}
|
|
35601
|
+
return out;
|
|
35602
|
+
}
|
|
35603
|
+
const THEME_PATTERNS = [["design_raw_colour", new RegExp([
|
|
35604
|
+
String.raw`(?<=[\s:(\[,'"])#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})(?![\w-])`,
|
|
35605
|
+
String.raw`\b(?:rgba?|hsla?|hwb|oklch|oklab|lab|lch|color)\(`,
|
|
35606
|
+
String.raw`(?<![\w-])(?:[a-z-]+:)*(?:bg|text|border(?:-[trblxy])?|ring|ring-offset|outline|fill|stroke|from|via|to|decoration|accent|caret|divide|shadow|placeholder)-(?:(?:${"slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose"})-\d{2,3}|black|white)(?:\/\d+)?(?![\w-])`
|
|
35607
|
+
].join("|"), "gi")], ["design_style_block", /<style\b[^>]*>/gi]];
|
|
35608
|
+
function themeViolations(source) {
|
|
35609
|
+
const checked = source.replace(/<!--[\s\S]*?-->/g, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1").replace(/\bhref=(["'])[^"']*\1/gi, "");
|
|
35610
|
+
const out = [];
|
|
35611
|
+
for (const [rule, pattern] of THEME_PATTERNS) {
|
|
35612
|
+
const found = [...new Set([...checked.matchAll(pattern)].map((m) => m[0].trim()))].slice(0, 3);
|
|
35613
|
+
if (found.length) out.push({
|
|
35614
|
+
rule,
|
|
35615
|
+
found
|
|
35616
|
+
});
|
|
35617
|
+
}
|
|
35618
|
+
return out;
|
|
35619
|
+
}
|
|
35620
|
+
const BLOCK_TAGS = new Set("address article aside blockquote caption dd details dialog div dl dt fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 header hr li main nav ol option p section summary table tbody td tfoot th thead tr ul".split(" "));
|
|
35621
|
+
const SKIPPED_TAGS = new Set("script style template noscript code pre kbd samp svg math".split(" "));
|
|
35622
|
+
function visibleTextBlocks($) {
|
|
35623
|
+
const blocks = [];
|
|
35624
|
+
let buffer = "";
|
|
35625
|
+
const flush = () => {
|
|
35626
|
+
const text = buffer.replace(/\s+/g, " ").trim();
|
|
35627
|
+
if (text) blocks.push(text);
|
|
35628
|
+
buffer = "";
|
|
35629
|
+
};
|
|
35630
|
+
const visit = (node) => {
|
|
35631
|
+
if (node.type === "text") {
|
|
35632
|
+
buffer += node.data ?? "";
|
|
35633
|
+
return;
|
|
35634
|
+
}
|
|
35635
|
+
if (!node.name || SKIPPED_TAGS.has(node.name)) return;
|
|
35636
|
+
if (node.name === "br") {
|
|
35637
|
+
buffer += " ";
|
|
35638
|
+
return;
|
|
35639
|
+
}
|
|
35640
|
+
const block = BLOCK_TAGS.has(node.name);
|
|
35641
|
+
if (block) flush();
|
|
35642
|
+
for (const child of node.children ?? []) visit(child);
|
|
35643
|
+
if (block) flush();
|
|
35644
|
+
};
|
|
35645
|
+
for (const node of $("body").toArray()) visit(node);
|
|
35646
|
+
flush();
|
|
35647
|
+
return blocks;
|
|
35648
|
+
}
|
|
35649
|
+
function metadataText($) {
|
|
35650
|
+
const out = [$("title").first().text()];
|
|
35651
|
+
$("meta").each((_i, element) => {
|
|
35652
|
+
const key = ($(element).attr("name") ?? $(element).attr("property") ?? "").toLowerCase();
|
|
35653
|
+
if (/^(?:description|og:title|og:description|og:image:alt|twitter:title|twitter:description)$/.test(key)) out.push($(element).attr("content") ?? "");
|
|
35654
|
+
});
|
|
35655
|
+
$("[alt], [aria-label], [title], [placeholder]").each((_i, element) => {
|
|
35656
|
+
if ($(element).closest("head, script, style, template, svg").length) return;
|
|
35657
|
+
for (const attribute of [
|
|
35658
|
+
"alt",
|
|
35659
|
+
"aria-label",
|
|
35660
|
+
"title",
|
|
35661
|
+
"placeholder"
|
|
35662
|
+
]) {
|
|
35663
|
+
const value = $(element).attr(attribute);
|
|
35664
|
+
if (value) out.push(value);
|
|
35665
|
+
}
|
|
35666
|
+
});
|
|
35667
|
+
return out.map((text) => text.replace(/\s+/g, " ").trim()).filter(Boolean);
|
|
35668
|
+
}
|
|
35669
|
+
// packages/core/src/site-check/check.ts
|
|
35670
|
+
const SITE_CHECK_MAX_PAGES = 1e4;
|
|
35671
|
+
const SITE_CHECK_MAX_ISSUES = 1e3;
|
|
35672
|
+
const SITEMAP_FILE_URL_LIMIT = 5e3;
|
|
35673
|
+
const THEME_SOURCE = /^src\/(?:pages|components|layouts)\/.+\.(?:astro|tsx?|jsx?|mdx?|html)$/;
|
|
35674
|
+
const FETCH_DIAGNOSTICS = {
|
|
35675
|
+
source: "network",
|
|
35676
|
+
cache: "bypass",
|
|
35677
|
+
fetched: true,
|
|
35678
|
+
rendered: false,
|
|
35679
|
+
blocked: false,
|
|
35680
|
+
durationMs: 0,
|
|
35681
|
+
retries: 0,
|
|
35682
|
+
rateLimit: {
|
|
35683
|
+
host: "build",
|
|
35684
|
+
concurrency: 1,
|
|
35685
|
+
intervalCap: 1,
|
|
35686
|
+
intervalMs: 0
|
|
35687
|
+
}
|
|
35688
|
+
};
|
|
35689
|
+
function builtPagePath(file) {
|
|
35690
|
+
return `/${file}`.replace(/(?:\/index)?\.html$/, "") || "/";
|
|
35691
|
+
}
|
|
35692
|
+
const byText = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
35693
|
+
const squash = (text) => text.replace(/\s+/g, " ").trim();
|
|
35694
|
+
async function checkBuiltSite(site) {
|
|
35695
|
+
const origin = new URL(site.origin).origin;
|
|
35696
|
+
const files = [...site.files].sort(byText);
|
|
35697
|
+
const fileSet = new Set(files);
|
|
35698
|
+
const allowWords = site.allowWords ?? [];
|
|
35699
|
+
const now = site.now ?? /* @__PURE__ */ new Date();
|
|
35700
|
+
const issues = [];
|
|
35701
|
+
const add = (rule, file, found) => {
|
|
35702
|
+
const { group, level, fix } = ruleFor(rule);
|
|
35703
|
+
issues.push({
|
|
35704
|
+
rule,
|
|
35705
|
+
group,
|
|
35706
|
+
level,
|
|
35707
|
+
file,
|
|
35708
|
+
found,
|
|
35709
|
+
fix
|
|
35710
|
+
});
|
|
35711
|
+
};
|
|
35712
|
+
const htmlFiles = files.filter((file) => file.endsWith(".html"));
|
|
35713
|
+
const checkedFiles = htmlFiles.slice(0, SITE_CHECK_MAX_PAGES);
|
|
35714
|
+
const pagePaths = new Set(checkedFiles.map(builtPagePath));
|
|
35715
|
+
const pages = [];
|
|
35716
|
+
const seenCss = /* @__PURE__ */ new Set();
|
|
35717
|
+
const checkCss = (css, file) => {
|
|
35718
|
+
if (!css.trim() || seenCss.has(css)) return;
|
|
35719
|
+
seenCss.add(css);
|
|
35720
|
+
for (const { rule, found } of designViolations(css)) add(rule, file, found.join(", "));
|
|
35721
|
+
};
|
|
35722
|
+
if (!fileSet.has("404.html")) add("not_found_page_missing", "404.html", "");
|
|
35723
|
+
for (const file of checkedFiles) {
|
|
35724
|
+
const html = await site.readText(file);
|
|
35725
|
+
const path = builtPagePath(file);
|
|
35726
|
+
const url = new URL(path, origin).toString();
|
|
35727
|
+
const notFound = file === "404.html";
|
|
35728
|
+
const snapshot = (await snapshotFetchedPage(url, {
|
|
35729
|
+
url,
|
|
35730
|
+
finalUrl: url,
|
|
35731
|
+
status: notFound ? 404 : 200,
|
|
35732
|
+
headers: { "content-type": "text/html; charset=utf-8" },
|
|
35733
|
+
html,
|
|
35734
|
+
usedJs: false,
|
|
35735
|
+
diagnostics: FETCH_DIAGNOSTICS,
|
|
35736
|
+
warnings: []
|
|
35737
|
+
})).page;
|
|
35738
|
+
if (!snapshot) continue;
|
|
35739
|
+
const $ = load(html);
|
|
35740
|
+
const links = internalLinks($, url, origin);
|
|
35741
|
+
pages.push({
|
|
35742
|
+
file,
|
|
35743
|
+
path,
|
|
35744
|
+
snapshot,
|
|
35745
|
+
links
|
|
35746
|
+
});
|
|
35747
|
+
$("style").each((_i, element) => checkCss($(element).text(), file));
|
|
35748
|
+
checkCss($("[style]").map((_i, element) => $(element).attr("style")).get().join(";"), file);
|
|
35749
|
+
for (const href of links) if (!resolvesInBuild(href, pagePaths, fileSet)) add("broken_link", file, href);
|
|
35750
|
+
if (notFound) continue;
|
|
35751
|
+
checkPageBasics($, file, add);
|
|
35752
|
+
checkCopy($, file, allowWords, add);
|
|
35753
|
+
checkFacts($, file, now, add);
|
|
35754
|
+
await checkPageFiles($, snapshot, file, origin, site, fileSet, add);
|
|
35755
|
+
}
|
|
35756
|
+
for (const file of files.filter((name) => name.endsWith(".css"))) checkCss(await site.readText(file), file);
|
|
35757
|
+
if (site.sources) {
|
|
35758
|
+
const sourceFiles = site.sources.files.filter((file) => THEME_SOURCE.test(file)).sort(byText).slice(0, SITE_CHECK_MAX_PAGES);
|
|
35759
|
+
for (const file of sourceFiles) {
|
|
35760
|
+
const source = await site.sources.readText(file);
|
|
35761
|
+
for (const { rule, found } of themeViolations(source)) add(rule, file, found.join(", "));
|
|
35762
|
+
}
|
|
35763
|
+
}
|
|
35764
|
+
linkGraph(pages);
|
|
35765
|
+
const fileByUrl = new Map(pages.map((page) => [page.snapshot.url, page.file]));
|
|
35766
|
+
for (const issue of auditCrawlPages(pages.map((page) => page.snapshot), { startUrl: `${origin}/` })) {
|
|
35767
|
+
if (!(issue.ruleId in CRAWLER_RULES)) continue;
|
|
35768
|
+
add(issue.ruleId, fileByUrl.get(issue.url) ?? issue.url, issue.detail ?? "");
|
|
35769
|
+
}
|
|
35770
|
+
await checkRobotsAndSitemaps(site, fileSet, origin, pages.filter((page) => page.snapshot.status === 200 && page.snapshot.indexable), add);
|
|
35771
|
+
await checkLlmsTxt(site, fileSet, origin, pagePaths, add);
|
|
35772
|
+
return summarize({
|
|
35773
|
+
origin,
|
|
35774
|
+
issues,
|
|
35775
|
+
pagesFound: htmlFiles.length,
|
|
35776
|
+
pagesChecked: checkedFiles.length
|
|
35777
|
+
});
|
|
35778
|
+
}
|
|
35779
|
+
function internalLinks($, url, origin) {
|
|
35780
|
+
const links = /* @__PURE__ */ new Set();
|
|
35781
|
+
$("a[href], link[rel=\"alternate\"][href]").each((_i, element) => {
|
|
35782
|
+
const href = $(element).attr("href") ?? "";
|
|
35783
|
+
try {
|
|
35784
|
+
const target = new URL(href, url);
|
|
35785
|
+
if (target.origin !== origin) return;
|
|
35786
|
+
links.add(decodeURI(target.pathname));
|
|
35787
|
+
} catch {
|
|
35788
|
+
links.add(href);
|
|
35789
|
+
}
|
|
35790
|
+
});
|
|
35791
|
+
return [...links].sort(byText);
|
|
35792
|
+
}
|
|
35793
|
+
function resolvesInBuild(path, pagePaths, files) {
|
|
35794
|
+
if (pagePaths.has(path)) return true;
|
|
35795
|
+
return files.has(path.replace(/^\//, ""));
|
|
35796
|
+
}
|
|
35797
|
+
function checkPageBasics($, file, add) {
|
|
35798
|
+
const h1Count = $("h1").length;
|
|
35799
|
+
if (h1Count !== 1) add("one_h1", file, `${h1Count} H1 elements`);
|
|
35800
|
+
const title = squash($("title").first().text());
|
|
35801
|
+
if (title && (title.length < 10 || title.length > 65)) add("title_length", file, `${title.length} characters: ${title}`);
|
|
35802
|
+
const description = squash($("meta[name=\"description\"]").first().attr("content") ?? "");
|
|
35803
|
+
if (description && (description.length < 50 || description.length > 170)) add("description_length", file, `${description.length} characters`);
|
|
35804
|
+
let previous = 0;
|
|
35805
|
+
for (const element of $("main h1, main h2, main h3, main h4, main h5, main h6").toArray()) {
|
|
35806
|
+
const level = Number(element.tagName.slice(1));
|
|
35807
|
+
if (previous && level > previous + 1) {
|
|
35808
|
+
add("heading_order", file, `H${previous} to H${level}`);
|
|
35809
|
+
break;
|
|
35810
|
+
}
|
|
35811
|
+
previous = level;
|
|
35812
|
+
}
|
|
35813
|
+
$("h1, h2, h3, h4, h5, h6, nav a, button, label, th").each((_i, element) => {
|
|
35814
|
+
const text = squash($(element).text());
|
|
35815
|
+
const letters = text.replace(/[^a-z]/gi, "");
|
|
35816
|
+
if (letters.length >= 8 && letters === letters.toUpperCase()) add("design_uppercase", file, text.slice(0, 80));
|
|
35817
|
+
});
|
|
35818
|
+
}
|
|
35819
|
+
function checkCopy($, file, allowWords, add) {
|
|
35820
|
+
const found = /* @__PURE__ */ new Map();
|
|
35821
|
+
for (const text of [...metadataText($), ...visibleTextBlocks($)]) for (const violation of copyViolations(text, allowWords)) {
|
|
35822
|
+
const seen = found.get(violation.rule) ?? /* @__PURE__ */ new Set();
|
|
35823
|
+
for (const word of violation.found) seen.add(word);
|
|
35824
|
+
found.set(violation.rule, seen);
|
|
35825
|
+
}
|
|
35826
|
+
for (const [rule, words] of found) add(rule, file, [...words].slice(0, 5).join(", "));
|
|
35827
|
+
}
|
|
35828
|
+
function checkFacts($, file, now, add) {
|
|
35829
|
+
$("[data-facts]").each((index, element) => {
|
|
35830
|
+
const block = $(element);
|
|
35831
|
+
const label = block.attr("data-facts") || `fact block ${index + 1}`;
|
|
35832
|
+
if (!block.find("a[data-source][href]").first().attr("href")) add("data_source_missing", file, label);
|
|
35833
|
+
const checked = block.find("time[data-checked]").first();
|
|
35834
|
+
if (!checked.length) {
|
|
35835
|
+
add("data_checked_missing", file, label);
|
|
35836
|
+
return;
|
|
35837
|
+
}
|
|
35838
|
+
const datetime = checked.attr("datetime") ?? "";
|
|
35839
|
+
const date = new Date(datetime);
|
|
35840
|
+
if (!/^\d{4}-\d{2}-\d{2}/.test(datetime) || Number.isNaN(date.getTime()) || date.getTime() > now.getTime()) add("data_checked_invalid", file, `${label}: ${datetime || "no date"}`);
|
|
35841
|
+
});
|
|
35842
|
+
}
|
|
35843
|
+
async function checkPageFiles($, snapshot, file, origin, site, files, add) {
|
|
35844
|
+
const image = snapshot.openGraphImage;
|
|
35845
|
+
if (image) {
|
|
35846
|
+
const target = sameOriginFile(image, origin);
|
|
35847
|
+
if (target !== void 0 && !files.has(target)) add("og_image_file_missing", file, image);
|
|
35848
|
+
else if (target?.endsWith(".png")) {
|
|
35849
|
+
const size = pngSize(await site.readBytes(target));
|
|
35850
|
+
if (size && (size.width !== 1200 || size.height !== 630)) add("og_image_size", file, `${size.width} by ${size.height}`);
|
|
35851
|
+
}
|
|
35852
|
+
}
|
|
35853
|
+
if (!snapshot.indexable) return;
|
|
35854
|
+
const markdown = $("link[rel~=\"alternate\"][type=\"text/markdown\"]").first().attr("href");
|
|
35855
|
+
const markdownFile = markdown ? sameOriginFile(markdown, origin, file) : "";
|
|
35856
|
+
if (!markdownFile || !files.has(markdownFile)) add("markdown_copy_missing", file, markdown ?? "no alternate link");
|
|
35857
|
+
checkFaqSchema($, file, add);
|
|
35858
|
+
}
|
|
35859
|
+
function sameOriginFile(href, origin, from = "") {
|
|
35860
|
+
try {
|
|
35861
|
+
const url = new URL(href, new URL(`/${from}`, origin));
|
|
35862
|
+
if (url.origin !== origin) return void 0;
|
|
35863
|
+
return decodeURI(url.pathname).replace(/^\//, "");
|
|
35864
|
+
} catch {
|
|
35865
|
+
return "";
|
|
35866
|
+
}
|
|
35867
|
+
}
|
|
35868
|
+
function pngSize(bytes) {
|
|
35869
|
+
if (bytes.length < 24 || bytes[0] !== 137 || bytes[1] !== 80) return null;
|
|
35870
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
35871
|
+
return {
|
|
35872
|
+
width: view.getUint32(16),
|
|
35873
|
+
height: view.getUint32(20)
|
|
35874
|
+
};
|
|
35875
|
+
}
|
|
35876
|
+
function checkFaqSchema($, file, add) {
|
|
35877
|
+
const visible = squash(visibleTextBlocks($).join(" "));
|
|
35878
|
+
const faqs = [];
|
|
35879
|
+
$("script[type=\"application/ld+json\"]").each((_i, element) => {
|
|
35880
|
+
try {
|
|
35881
|
+
const data = JSON.parse($(element).text());
|
|
35882
|
+
const graph = Array.isArray(data["@graph"]) ? data["@graph"] : [data];
|
|
35883
|
+
for (const entity of graph) {
|
|
35884
|
+
const type = entity["@type"];
|
|
35885
|
+
if (!(Array.isArray(type) ? type : [type]).includes("FAQPage")) continue;
|
|
35886
|
+
const questions = entity.mainEntity;
|
|
35887
|
+
if (Array.isArray(questions)) faqs.push(...questions);
|
|
35888
|
+
}
|
|
35889
|
+
} catch {}
|
|
35890
|
+
});
|
|
35891
|
+
for (const faq of faqs) {
|
|
35892
|
+
const question = squash(String(faq.name ?? ""));
|
|
35893
|
+
const answer = squash(load(`<p>${String(faq.acceptedAnswer?.text ?? "")}</p>`)("p").text());
|
|
35894
|
+
if (!question || !visible.includes(question)) add("faq_schema_mismatch", file, `Question not on the page: ${question}`);
|
|
35895
|
+
else if (!answer || !visible.includes(answer)) add("faq_schema_mismatch", file, `Answer not on the page: ${question}`);
|
|
35896
|
+
}
|
|
35897
|
+
}
|
|
35898
|
+
function linkGraph(pages) {
|
|
35899
|
+
const byPath = new Map(pages.map((page) => [page.path, page]));
|
|
35900
|
+
const inlinks = /* @__PURE__ */ new Map();
|
|
35901
|
+
for (const page of pages) {
|
|
35902
|
+
if (page.snapshot.status !== 200) continue;
|
|
35903
|
+
for (const link of page.links) if (link !== page.path && byPath.has(link)) inlinks.set(link, (inlinks.get(link) ?? 0) + 1);
|
|
35904
|
+
}
|
|
35905
|
+
const depth = /* @__PURE__ */ new Map([["/", 0]]);
|
|
35906
|
+
const queue = ["/"];
|
|
35907
|
+
for (let at = 0; at < queue.length; at += 1) {
|
|
35908
|
+
const path = queue[at];
|
|
35909
|
+
for (const link of byPath.get(path)?.links ?? []) {
|
|
35910
|
+
if (depth.has(link) || !byPath.has(link)) continue;
|
|
35911
|
+
depth.set(link, (depth.get(path) ?? 0) + 1);
|
|
35912
|
+
queue.push(link);
|
|
35913
|
+
}
|
|
35914
|
+
}
|
|
35915
|
+
for (const page of pages) {
|
|
35916
|
+
page.snapshot.internalInlinkCount = inlinks.get(page.path) ?? 0;
|
|
35917
|
+
page.snapshot.crawlDepth = depth.get(page.path);
|
|
35918
|
+
}
|
|
35919
|
+
}
|
|
35920
|
+
async function checkRobotsAndSitemaps(site, files, origin, indexable, add) {
|
|
35921
|
+
let sitemapRoots = ["sitemap.xml"];
|
|
35922
|
+
if (!files.has("robots.txt")) add("robots_missing", "robots.txt", "");
|
|
35923
|
+
else {
|
|
35924
|
+
const robots = await site.readText("robots.txt");
|
|
35925
|
+
if (robotsBlocksSite(robots)) add("robots_blocks_site", "robots.txt", "");
|
|
35926
|
+
if (!/^content-signal\s*:/im.test(robots)) add("robots_content_signal_missing", "robots.txt", "");
|
|
35927
|
+
const listed = [...robots.matchAll(/^sitemap\s*:\s*(\S+)/gim)].map((match) => match[1]);
|
|
35928
|
+
if (!listed.length) add("robots_sitemap_missing", "robots.txt", "");
|
|
35929
|
+
const local = listed.map((href) => sameOriginFile(href, origin)).filter((path) => Boolean(path));
|
|
35930
|
+
if (local.length) sitemapRoots = local;
|
|
35931
|
+
}
|
|
35932
|
+
const listedUrls = /* @__PURE__ */ new Set();
|
|
35933
|
+
const queue = sitemapRoots.filter((root) => {
|
|
35934
|
+
if (files.has(root)) return true;
|
|
35935
|
+
add("sitemap_missing", root, "");
|
|
35936
|
+
return false;
|
|
35937
|
+
});
|
|
35938
|
+
const read = /* @__PURE__ */ new Set();
|
|
35939
|
+
for (let at = 0; at < queue.length && at < 1e3; at += 1) {
|
|
35940
|
+
const file = queue[at];
|
|
35941
|
+
if (read.has(file)) continue;
|
|
35942
|
+
read.add(file);
|
|
35943
|
+
const xml = await site.readText(file);
|
|
35944
|
+
const locs = [...xml.matchAll(/<loc>\s*([^<]+?)\s*<\/loc>/g)].map((match) => match[1].replace(/&/g, "&"));
|
|
35945
|
+
if (/<sitemapindex\b/.test(xml)) {
|
|
35946
|
+
for (const loc of locs) {
|
|
35947
|
+
const child = sameOriginFile(loc, origin);
|
|
35948
|
+
if (child && files.has(child)) queue.push(child);
|
|
35949
|
+
else add("sitemap_missing", file, loc);
|
|
35950
|
+
}
|
|
35951
|
+
continue;
|
|
35952
|
+
}
|
|
35953
|
+
if (locs.length > 5e3) add("sitemap_file_too_large", file, `${locs.length} URLs`);
|
|
35954
|
+
for (const loc of locs) listedUrls.add(loc);
|
|
35955
|
+
}
|
|
35956
|
+
const indexableUrls = new Set(indexable.map((page) => page.snapshot.url));
|
|
35957
|
+
for (const page of indexable) if (!listedUrls.has(page.snapshot.url)) add("sitemap_page_missing", page.file, page.snapshot.url);
|
|
35958
|
+
for (const url of [...listedUrls].sort(byText)) if (!indexableUrls.has(url)) add("sitemap_extra_url", "sitemap.xml", url);
|
|
35959
|
+
}
|
|
35960
|
+
function robotsBlocksSite(robots) {
|
|
35961
|
+
let everyone = false;
|
|
35962
|
+
for (const raw of robots.split(/\r?\n/)) {
|
|
35963
|
+
const line = raw.replace(/#.*/, "").trim();
|
|
35964
|
+
const agent = /^user-agent\s*:\s*(.+)$/i.exec(line);
|
|
35965
|
+
if (agent) everyone = agent[1]?.trim() === "*";
|
|
35966
|
+
else if (everyone && /^disallow\s*:\s*\/\s*$/i.test(line)) return true;
|
|
35967
|
+
}
|
|
35968
|
+
return false;
|
|
35969
|
+
}
|
|
35970
|
+
async function checkLlmsTxt(site, files, origin, pagePaths, add) {
|
|
35971
|
+
if (!files.has("llms.txt")) {
|
|
35972
|
+
add("llms_txt_missing", "llms.txt", "");
|
|
35973
|
+
return;
|
|
35974
|
+
}
|
|
35975
|
+
const text = await site.readText("llms.txt");
|
|
35976
|
+
for (const match of text.matchAll(/\]\(([^)\s]+)\)/g)) {
|
|
35977
|
+
const path = sameOriginFile(match[1], origin);
|
|
35978
|
+
if (path === void 0) continue;
|
|
35979
|
+
if (!resolvesInBuild(`/${path}`, pagePaths, files)) add("llms_txt_broken_link", "llms.txt", match[1]);
|
|
35980
|
+
}
|
|
35981
|
+
}
|
|
35982
|
+
function summarize(input) {
|
|
35983
|
+
const issues = [...input.issues].sort((a, b) => (a.level === b.level ? 0 : a.level === "fail" ? -1 : 1) || byText(a.group, b.group) || byText(a.rule, b.rule) || byText(a.file, b.file) || byText(a.found, b.found));
|
|
35984
|
+
const counts = /* @__PURE__ */ new Map();
|
|
35985
|
+
for (const issue of issues) counts.set(issue.rule, (counts.get(issue.rule) ?? 0) + 1);
|
|
35986
|
+
const rules = [...counts].map(([rule, count]) => ({
|
|
35987
|
+
rule,
|
|
35988
|
+
count,
|
|
35989
|
+
...ruleFor(rule)
|
|
35990
|
+
})).sort((a, b) => (a.level === b.level ? 0 : a.level === "fail" ? -1 : 1) || b.count - a.count || byText(a.rule, b.rule));
|
|
35991
|
+
const failures = issues.filter((issue) => issue.level === "fail").length;
|
|
35992
|
+
const capped = input.pagesChecked < input.pagesFound;
|
|
35993
|
+
return {
|
|
35994
|
+
status: failures ? "failed" : capped ? "partial" : "passed",
|
|
35995
|
+
origin: input.origin,
|
|
35996
|
+
pagesFound: input.pagesFound,
|
|
35997
|
+
pagesChecked: input.pagesChecked,
|
|
35998
|
+
failures,
|
|
35999
|
+
warnings: issues.length - failures,
|
|
36000
|
+
rules,
|
|
36001
|
+
issues: issues.slice(0, SITE_CHECK_MAX_ISSUES),
|
|
36002
|
+
issuesOmitted: Math.max(0, issues.length - SITE_CHECK_MAX_ISSUES)
|
|
36003
|
+
};
|
|
36004
|
+
}
|
|
36005
|
+
// packages/core/src/storage/reset.ts
|
|
35119
36006
|
async function resetSeoData() {
|
|
35120
36007
|
const failures = (await Promise.allSettled([deleteTokens(), deleteManagedProviderSecrets()])).flatMap((result) => result.status === "rejected" ? [result.reason] : []);
|
|
35121
36008
|
if (failures.length > 0) throw new Error("Reset stopped before deleting local files because saved credentials could not be removed. Unlock the system keychain and run `seo reset --yes` again.", { cause: new AggregateError(failures) });
|
|
@@ -35133,8 +36020,7 @@ async function resetSeoData() {
|
|
|
35133
36020
|
force: true
|
|
35134
36021
|
});
|
|
35135
36022
|
}
|
|
35136
|
-
|
|
35137
|
-
//#region packages/core/src/update-notice.ts
|
|
36023
|
+
// packages/core/src/update-notice.ts
|
|
35138
36024
|
const REGISTRY_URL = "https://registry.npmjs.org/seo/latest";
|
|
35139
36025
|
const CHECK_EVERY_MS = 864e5;
|
|
35140
36026
|
const TIMEOUT_MS = 2e3;
|
|
@@ -35221,8 +36107,7 @@ function updateNotice(options = {}) {
|
|
|
35221
36107
|
return;
|
|
35222
36108
|
}
|
|
35223
36109
|
}
|
|
35224
|
-
|
|
35225
|
-
//#region packages/core/src/utils/time.ts
|
|
36110
|
+
// packages/core/src/utils/time.ts
|
|
35226
36111
|
function parseOlderThan(input) {
|
|
35227
36112
|
if (!input) return;
|
|
35228
36113
|
const match = input.trim().match(/^(\d+)([smhdw])$/i);
|
|
@@ -35255,7 +36140,6 @@ function isoDate(daysAgo = 0) {
|
|
|
35255
36140
|
date.setUTCDate(date.getUTCDate() - daysAgo);
|
|
35256
36141
|
return date.toISOString().slice(0, 10);
|
|
35257
36142
|
}
|
|
35258
|
-
|
|
35259
|
-
export { AHREFS_API_KEY_ENV, AHREFS_API_KEY_SECRET, AI_REFERRAL_SOURCES, AI_REFERRAL_SOURCES_VERSION, ANALYTICS_PROVIDER_IDS, AUDIT_PAGE_RULE_IDS, AhrefsClient, BING_API_KEY_ENV, BING_API_KEY_SECRET, BROWSER_USER_AGENT, CANNIBAL_MAXIMUM_DOMINANT_SHARE, CANNIBAL_MINIMUM_PAGE_SHARE, CLICKY_SITEKEY_ENV, CLICKY_SITE_KEYS_SECRET, CLOUD_SIGN_IN, CLOUD_URL, CRAWL_SKIP_REASONS, CTR_DEFAULT_LIMIT, CTR_MAX_LIMIT, CTR_MAX_MIN_IMPRESSIONS, DATAFORSEO_CREDENTIAL_SECRET, DATAFORSEO_LOGIN_ENV, DATAFORSEO_PASSWORD_ENV, DEFAULT_MAX_PAGES, DEFAULT_SEARCH_CONSOLE_EXPORT_ROW_LIMIT, DEFAULT_UNREACHED_PAGE_LIMIT, DataForSeoClient, DecisionModelError, DecisionRunner, GSC_FINAL_DATA_LAG_DAYS, GSC_TIME_ZONE, GoogleApiError, INCIDENTS_URL, JsonFileStorageAdapter, LINK_ROLES, LOCAL_BING_CONNECT, LOCAL_SIGN_IN, LOG_MAX_AGE_MS, LOG_MAX_FILE_BYTES, LOG_MAX_TOTAL_BYTES, LocalCredentialsProvider, MANAGED_PROVIDER_SECRET_NAMES, MAX_CRAWL_CONCURRENCY, MAX_CRAWL_DEPTH, MAX_CRAWL_PAGES, MAX_CRAWL_TIMEOUT_MS, MAX_MAX_PAGES, MAX_SEARCH_CONSOLE_EXPORT_FILES, MAX_SEARCH_CONSOLE_EXPORT_ROW_LIMIT, PAGE_TYPES, PROVIDER_ERROR_CODES, PROVIDER_SECRET_NAMES, ProviderError, ProviderExtensionRegistry, RESEARCH_IMPORT_COLUMN_KEYS, ResponseSizeLimitError, SEARCH_INTENTS, SEO_CRAWLER_IDENTITY, SEO_CRAWLER_TOKEN, SEO_CRAWLER_USER_AGENT, SEO_PROVIDER_API_VERSION, SEO_VERSION, SERP_TITLE_REFERENCE_WIDTH_PX, SERP_TITLE_WIDTH_PROFILE, STEP_CHANGE_RULES, SeoError, ServiceAccountAccessTokenClient, SessionLedger, TELEMETRY_AGENTS, TELEMETRY_ENDPOINT, TELEMETRY_ERROR_CATEGORIES, TELEMETRY_EVENTS, TELEMETRY_FAILURE_CONTEXTS, TELEMETRY_FAILURE_REASONS, TELEMETRY_NOTICE, TELEMETRY_OPERATIONS, TELEMETRY_REPORTS, TELEMETRY_SCHEMA_VERSION, TELEMETRY_TIMEOUT_MS, TYPESAFE_API_KEY_ENV, agentActionsView, aiReferralSourceForValue, aiReferralsReport, analyticsConnectionLabel, analyticsConnectionProviderId, analyticsConnectionSchema, analyticsProviderDetails, analyticsProviderSchema, analyzeAiReferralRows, analyzeCannibalImportRows, analyzeCannibalRows, analyzeCtrUnderperformersFromRows, analyzeDecay, analyzeIndexCoverageSignals, analyzePageOpportunitiesFromRows, analyzeQueryClustersFromRows, analyzeQuickWinsFromRows, analyzeSecondPageRows, analyzeSegmentRows, analyzeStrikingDistanceRows, analyzeTrafficAnomalyRows, assertCrawlConfigLimits, assertUrlMatchesGscProperty, auditCrawlPages, auditCrawlRequests, auditPage, authStatus, bearerFetch, boundedSitemapInventory, buildAgentFindings, buildQueryClusterReportFromRows, buildTelemetryPayload, cacheDb, cannibalImportReport, cannibalReport, cannibalReportWith, changeWindow, checkDatabaseReadiness, classifyTelemetryFailure, clearCache, cloudAccountFor, cloudBing, cloudCredentials, cloudGoogleEmails, cloudGoogleToken, cloudKeys, cloudLinked, cloudLogout, cloudWeek, clusterQueryRows, combineRobotsValues, compareSegmentRows, configSchema, contentCoverageRecommendation, contentCoverageType, contentOptimizationFromPageOpportunities, contentOptimizationReport, countLabel, crawlConfigHash, crawlConfigSchema, crawlDefinitionId, crawlFixToQueueItem, crawlImplementationQueueWorkflow, crawlIssueGroupSchema, crawlIssueSchema, crawlOne, crawlPageSnapshotSchema, crawlReportSchema, crawlReportSummarySchema, crawlRequestObservationSchema, crawlRunId, crawlSite, crawlSkipImpact, crawlSkipReasonCountsFromRecord, crawlStatusOnly, crawlTopFixSchema, crawlerJsonSchemas, crawlerRuleCategorySchema, crawlerRuleIdSchema, crawlerRuleInfoSchema, crawlerRuleRecommendationSchema, crawlerRuleSeveritySchema, crawlerSchemas, createAuthorizedClient, createCrawlReport, createCtrBenchmarkContext, createGoogleAccessTokenClient, createLogger, createPageRenderer, ctrUnderperformersReport, ctrUnderperformersReportWith, decayClusterDrafts, decayComparisonRange, decayingReport, decayingReportWith, deleteAhrefsApiKey, deleteBingApiKey, deleteClickySiteKey, deleteDataForSeoCredentials, deleteManagedProviderSecrets, deleteProviderExtensionAccount, deleteProviderExtensionCredentials, deleteProviderSecret, deleteTokens, deriveBrandTerms, describeInstalledProviderExtension, detectAgentName, detectBrandTerms, detectTelemetryAgent, diagnoseCsvFiles, diagnoseProperty, diagnosePropertyWorkflow, effectiveRobotsDirectives, effectiveSnippetControl, ensurePrivateDatabaseFiles, ensureSeoCliDirs, estimateSerpTitleWidth, expectedCtrForPosition, explainRule, fetchCruxFieldData, fetchPage, fetchPageStatus, fetchSearchStatusIncidents, fetchSitemapUrls, fileSize, finalGscDateRange, findOverlappingSearchUpdates, finishCloudLogin, formatRelativeExpiry, ga4MatchReason, ga4PropertyIdFromName, ga4ReportQualityWarnings, ga4RequestCanUseCache, ga4RowsToObjects, getAuthModeStatus, getCacheStats, getClientConfig, getDb, getPrivacySnapshot, getSeoCliPaths, getServiceAccountConfig, getServiceAccountStatus, getTelemetryStatus, getTokenStorageStatus, googleJson, groupCrawlIssues, hasMetaRobotsDirective, hasRule, hasXRobotsDirective, hashKey, indexCoverageSignals, inferTemplateMovement, initializeTelemetry, inspectProviderPackage, inspectSearchConsoleUrl, inspectUrl, installProviderPackage, installedProviderPackageSchema, installedProviderPackagesSchema, internalLinksReport, interpretUpdateCorrelation, isBrandQuery, isNewerVersion, isSkippableReportError, isTelemetryCi, isTelemetryOperation, isTelemetryReport, isoDate, isoWeek, latestFinalGscDate, linkRecover, linkRecoverRecommendation, listAnalyticsAccounts, listAnalyticsDataStreams, listGa4AccountSummaries, listGa4DataStreams, listGoogleAccounts, listInstalledProviderExtensions, listRules, listSearchConsoleSites, listSearchUpdates, listSites, loadInstalledProviderExtensions, loadProviderExtensions, loadProviderPackage, loadSearchConsoleExport, localAccounts, localBing, localCredentialsProvider, localError, localGoogleFetch, localInternalLinksDependencies, localRefreshClicky, localResearchKeys, loginWithLoopback, makeRunId, matchGa4WebStreams, measureCoverage, metaRobotsDirectives, migrateLegacyMacConfig, missingOAuthClientMessage, missingSharedClientLoginMessage, nodeCommandDeps, normalizeBrandText, normalizeClickySiteId, normalizeClickySiteKey, normalizeCrawlConfig, normalizeCrawlSkipReasonCounts, normalizeForCoverage, normalizeHttpUrl, normalizeJavaScriptRenderingMode, normalizeLoadedCrawlReport, normalizeProviderExtensionAccount, normalizedResearchColumnName, notSignedIn, noteCacheWrite, openCloudLink, pageEvidenceRecommendation, pageOpportunitiesReport, parseOlderThan, pendingCloudLogin, performanceAudit, performanceReportIsCacheable, plural, providerCapabilitySchema, providerConnectionFieldSchema, providerExactVersionSchema, providerExtensionIdSchema, providerIdSchema, providerIntegritySchema, providerKindSchema, providerPackageManifestSchema, providerPackageNameSchema, providerPreferenceSchema, providerRegistrationSchema, providerRequestJson, providerRequestText, pruneLogs, publicHttpFetch, queryClusterReport, queryClusterTokens, queryContentCoverageFromPage, queryContentFieldsFromPage, queryFieldCoverage, queryOpportunityRecommendation, queryPageMetrics, queryPageTopQuery, queryPagesMetrics, queryPagesMetricsBatch, queryPagesTopQueries, queryPagesTopQueriesBatch, querySearchAnalytics, quickWinsCsvRows, quickWinsReport, quickWinsReportWith, readAhrefsApiKey, readAllTokens, readBingApiKey, readBoundedResponseText, readClickySiteKey, readClickySiteKeys, readConfig, readDataForSeoCredentials, readInstalledProviderPackages, readJson, readJsonOrUndefined, readOauthClient, readProviderExtensionAccount, readProviderExtensionCredentials, readProviderSecret, readTokens, recommendationForRule, reconcileExportPagesWithCrawl, redirectTrace, refreshAuthToken, refreshPrioritiesCsvFiles, refreshPrioritiesWorkflow, registrableDomain, removeInstalledProviderPackage, renderCrawlCsv, renderCrawlHtml, renderCrawlJunit, renderCrawlMarkdownTickets, renderCrawlPagesCsv, renderCrawlPretty, renderCsv, renderPresentationTablesMarkdown, renderWorkflowMarkdown, reportNarrative, requestHeaders, requirePageVerification, researchImportColumns, resetSeoData, resolveLocalSite, resolveSegmentRanges, resolveSeoCliPaths, reviewObservations, roundedPosition, runAnalyticsProviderLandingPages, runAnalyticsReport, runDoctor, runGa4Report, runProviderExtensionAction, runtimeForProvider, safeProviderAdapterError, saveInstalledProviderPackage, searchAnalyticsRows, searchUpdatesFromIncidents, secondPage, secondPageWith, segmentComparisonRange, segmentImpact, segmentRangeDays, seoErrorEnvelope, sessionSourceInListFilter, setActiveGoogleAccount, setTelemetryEnabled, setTokenStorageMode, shiftIsoDate, shouldExcludeBrandQuery, siteStartUrl, startCloudLogin, strikingDistance, strikingDistanceWith, summarizeCrawlReport, pageTechnicalSignals as technicalPageSignals, telemetryErrorCategory, toSeoError, tokenSchema, tokenStoreSchema, topFixes, trackTelemetryCommandFailed, trackTelemetryReportComplete, trackTelemetryReportFailed, trackTelemetryReportStart, trackTelemetrySetupComplete, trafficAnomaly, trafficAnomalyWith, typesafeDecisionModel, unavailableSegmentImpactReport, unicodeTokens, uninstallProviderExtension, updateCorrelation, updateNotice, updatePostmortemCsvFiles, updatePostmortemWorkflow, urlInspectionQuota, validateResearchImportColumnSources, verifyAnalyticsProvider, verifyProviderExtension, verifyQueryContent, verifyQueryContentWith, withAgentReportContract, withRankingHistory, workflowPresentation, writeAhrefsApiKey, writeBingApiKey, writeClickySiteKey, writeConfig, writeDataForSeoCredentials, writeInstalledProviderPackages, writeOauthClient, writeProviderExtensionAccount, writeProviderExtensionCredentials, writeProviderSecret, writeToCache, writeTokens, xRobotsDirectives };
|
|
36143
|
+
export { AHREFS_API_KEY_ENV, AHREFS_API_KEY_SECRET, AI_REFERRAL_SOURCES, AI_REFERRAL_SOURCES_VERSION, ANALYTICS_PROVIDER_IDS, AUDIT_PAGE_RULE_IDS, AhrefsClient, BING_API_KEY_ENV, BING_API_KEY_SECRET, BROWSER_USER_AGENT, CANNIBAL_MAXIMUM_DOMINANT_SHARE, CANNIBAL_MINIMUM_PAGE_IMPRESSIONS, CANNIBAL_MINIMUM_PAGE_SHARE, CLICKY_SITEKEY_ENV, CLICKY_SITE_KEYS_SECRET, CLOUD_SIGN_IN, CLOUD_URL, CRAWL_SKIP_REASONS, CTR_DEFAULT_LIMIT, CTR_MAX_LIMIT, CTR_MAX_MIN_IMPRESSIONS, DATAFORSEO_CREDENTIAL_SECRET, DATAFORSEO_LOGIN_ENV, DATAFORSEO_PASSWORD_ENV, DEFAULT_MAX_PAGES, DEFAULT_SEARCH_CONSOLE_EXPORT_ROW_LIMIT, DEFAULT_UNREACHED_PAGE_LIMIT, DataForSeoClient, DecisionModelError, DecisionRunner, GSC_FINAL_DATA_LAG_DAYS, GSC_TIME_ZONE, GoogleApiError, INCIDENTS_URL, JsonFileStorageAdapter, LINK_ROLES, LOCAL_BING_CONNECT, LOCAL_SIGN_IN, LOG_MAX_AGE_MS, LOG_MAX_FILE_BYTES, LOG_MAX_TOTAL_BYTES, LocalCredentialsProvider, MANAGED_PROVIDER_SECRET_NAMES, MAX_CRAWL_CONCURRENCY, MAX_CRAWL_DEPTH, MAX_CRAWL_PAGES, MAX_CRAWL_TIMEOUT_MS, MAX_MAX_PAGES, MAX_SEARCH_CONSOLE_EXPORT_FILES, MAX_SEARCH_CONSOLE_EXPORT_ROW_LIMIT, PAGE_TYPES, PROVIDER_ERROR_CODES, PROVIDER_SECRET_NAMES, ProviderError, ProviderExtensionRegistry, RESEARCH_IMPORT_COLUMN_KEYS, ResponseSizeLimitError, SEARCH_INTENTS, SEO_CRAWLER_IDENTITY, SEO_CRAWLER_TOKEN, SEO_CRAWLER_USER_AGENT, SEO_PROVIDER_API_VERSION, SEO_VERSION, SERP_TITLE_REFERENCE_WIDTH_PX, SERP_TITLE_WIDTH_PROFILE, SITEMAP_FILE_URL_LIMIT, SITE_CHECK_MAX_ISSUES, SITE_CHECK_MAX_PAGES, STEP_CHANGE_RULES, SeoError, ServiceAccountAccessTokenClient, SessionLedger, TELEMETRY_AGENTS, TELEMETRY_ENDPOINT, TELEMETRY_ERROR_CATEGORIES, TELEMETRY_EVENTS, TELEMETRY_FAILURE_CONTEXTS, TELEMETRY_FAILURE_REASONS, TELEMETRY_NOTICE, TELEMETRY_OPERATIONS, TELEMETRY_REPORTS, TELEMETRY_SCHEMA_VERSION, TELEMETRY_TIMEOUT_MS, TYPESAFE_API_KEY_ENV, agentActionsView, aiReferralSourceForValue, aiReferralsReport, analyticsConnectionLabel, analyticsConnectionProviderId, analyticsConnectionSchema, analyticsProviderDetails, analyticsProviderSchema, analyzeAiReferralRows, analyzeCannibalImportRows, analyzeCannibalRows, analyzeCtrUnderperformersFromRows, analyzeDecay, analyzeIndexCoverageSignals, analyzePageOpportunitiesFromRows, analyzeQueryClustersFromRows, analyzeQuickWinsFromRows, analyzeSecondPageRows, analyzeSegmentRows, analyzeStrikingDistanceRows, analyzeTrafficAnomalyRows, assertCrawlConfigLimits, assertUrlMatchesGscProperty, auditCrawlPages, auditCrawlRequests, auditPage, authStatus, bearerFetch, boundedSitemapInventory, buildAgentFindings, buildQueryClusterReportFromRows, buildTelemetryPayload, builtPagePath, cacheDb, cannibalDataStatus, cannibalImportReport, cannibalReport, cannibalReportWith, changeWindow, checkBuiltSite, checkDatabaseReadiness, classifyTelemetryFailure, clearCache, cloudAccountFor, cloudBing, cloudCredentials, cloudGoogleEmails, cloudGoogleToken, cloudKeys, cloudLinked, cloudLogout, cloudWeek, clusterQueryRows, combineRobotsValues, compareSegmentRows, configSchema, contentCoverageRecommendation, contentCoverageType, contentOptimizationFromPageOpportunities, contentOptimizationReport, countLabel, crawlConfigHash, crawlConfigSchema, crawlDefinitionId, crawlFixToQueueItem, crawlImplementationQueueWorkflow, crawlIssueGroupSchema, crawlIssueSchema, crawlOne, crawlPageSnapshotSchema, crawlReportSchema, crawlReportSummarySchema, crawlRequestObservationSchema, crawlRunId, crawlSite, crawlSkipImpact, crawlSkipReasonCountsFromRecord, crawlStatusOnly, crawlTopFixSchema, crawlerJsonSchemas, crawlerRuleCategorySchema, crawlerRuleIdSchema, crawlerRuleInfoSchema, crawlerRuleRecommendationSchema, crawlerRuleSeveritySchema, crawlerSchemas, createAuthorizedClient, createCrawlReport, createCtrBenchmarkContext, createGoogleAccessTokenClient, createLogger, createPageRenderer, ctrUnderperformersReport, ctrUnderperformersReportWith, decayClusterDrafts, decayComparisonRange, decayingReport, decayingReportWith, deleteAhrefsApiKey, deleteBingApiKey, deleteClickySiteKey, deleteDataForSeoCredentials, deleteManagedProviderSecrets, deleteProviderExtensionAccount, deleteProviderExtensionCredentials, deleteProviderSecret, deleteTokens, deriveBrandTerms, describeInstalledProviderExtension, detectAgentName, detectBrandTerms, detectTelemetryAgent, diagnoseCsvFiles, diagnoseProperty, diagnosePropertyWorkflow, effectiveRobotsDirectives, effectiveSnippetControl, ensurePrivateDatabaseFiles, ensureSeoCliDirs, estimateSerpTitleWidth, expectedCtrForPosition, explainRule, fetchCruxFieldData, fetchPage, fetchPageStatus, fetchSearchStatusIncidents, fetchSitemapUrls, fileSize, finalGscDateRange, findOverlappingSearchUpdates, finishCloudLogin, formatRelativeExpiry, ga4MatchReason, ga4PropertyIdFromName, ga4ReportQualityWarnings, ga4RequestCanUseCache, ga4RowsToObjects, getAuthModeStatus, getCacheStats, getClientConfig, getDb, getPrivacySnapshot, getSeoCliPaths, getServiceAccountConfig, getServiceAccountStatus, getTelemetryStatus, getTokenStorageStatus, googleJson, groupCrawlIssues, hasMetaRobotsDirective, hasRule, hasXRobotsDirective, hashKey, indexCoverageSignals, inferTemplateMovement, initializeTelemetry, inspectProviderPackage, inspectSearchConsoleUrl, inspectUrl, installProviderPackage, installedProviderPackageSchema, installedProviderPackagesSchema, internalLinksReport, interpretUpdateCorrelation, isBrandQuery, isNewerVersion, isSkippableReportError, isTelemetryCi, isTelemetryOperation, isTelemetryReport, isoDate, isoWeek, latestFinalGscDate, linkRecover, linkRecoverRecommendation, listAnalyticsAccounts, listAnalyticsDataStreams, listGa4AccountSummaries, listGa4DataStreams, listGoogleAccounts, listInstalledProviderExtensions, listRules, listSearchConsoleSites, listSearchUpdates, listSites, loadInstalledProviderExtensions, loadProviderExtensions, loadProviderPackage, loadSearchConsoleExport, localAccounts, localBing, localCredentialsProvider, localError, localGoogleFetch, localInternalLinksDependencies, localRefreshClicky, localResearchKeys, loginWithLoopback, makeRunId, matchGa4WebStreams, measureCoverage, metaRobotsDirectives, migrateLegacyMacConfig, missingOAuthClientMessage, missingSharedClientLoginMessage, nodeCommandDeps, normalizeBrandText, normalizeClickySiteId, normalizeClickySiteKey, normalizeCrawlConfig, normalizeCrawlSkipReasonCounts, normalizeForCoverage, normalizeHttpUrl, normalizeJavaScriptRenderingMode, normalizeLoadedCrawlReport, normalizeProviderExtensionAccount, normalizedResearchColumnName, notSignedIn, noteCacheWrite, openCloudLink, pageEvidenceRecommendation, pageOpportunitiesReport, parseOlderThan, pendingCloudLogin, performanceAudit, performanceReportIsCacheable, plural, providerCapabilitySchema, providerConnectionFieldSchema, providerExactVersionSchema, providerExtensionIdSchema, providerIdSchema, providerIntegritySchema, providerKindSchema, providerPackageManifestSchema, providerPackageNameSchema, providerPreferenceSchema, providerRegistrationSchema, providerRequestJson, providerRequestText, pruneLogs, publicHttpFetch, queryClusterReport, queryClusterTokens, queryContentCoverageFromPage, queryContentFieldsFromPage, queryFieldCoverage, queryOpportunityRecommendation, queryPageMetrics, queryPageTopQuery, queryPagesMetrics, queryPagesMetricsBatch, queryPagesTopQueries, queryPagesTopQueriesBatch, querySearchAnalytics, quickWinsCsvRows, quickWinsReport, quickWinsReportWith, readAhrefsApiKey, readAllTokens, readBingApiKey, readBoundedResponseText, readClickySiteKey, readClickySiteKeys, readConfig, readDataForSeoCredentials, readInstalledProviderPackages, readJson, readJsonOrUndefined, readOauthClient, readProviderExtensionAccount, readProviderExtensionCredentials, readProviderSecret, readTokens, recommendationForRule, reconcileExportPagesWithCrawl, redirectTrace, refreshAuthToken, refreshPrioritiesCsvFiles, refreshPrioritiesWorkflow, registrableDomain, removeInstalledProviderPackage, renderCrawlCsv, renderCrawlHtml, renderCrawlJunit, renderCrawlMarkdownTickets, renderCrawlPagesCsv, renderCrawlPretty, renderCsv, renderPresentationTablesMarkdown, renderWorkflowMarkdown, reportNarrative, requestHeaders, requirePageVerification, researchImportColumns, resetSeoData, resolveLocalSite, resolveSegmentRanges, resolveSeoCliPaths, reviewObservations, roundedPosition, runAnalyticsProviderLandingPages, runAnalyticsReport, runDoctor, runGa4Report, runProviderExtensionAction, runtimeForProvider, safeProviderAdapterError, saveInstalledProviderPackage, searchAnalyticsRows, searchUpdatesFromIncidents, secondPage, secondPageWith, segmentComparisonRange, segmentImpact, segmentRangeDays, seoErrorEnvelope, sessionSourceInListFilter, setActiveGoogleAccount, setTelemetryEnabled, setTokenStorageMode, shiftIsoDate, shouldExcludeBrandQuery, siteStartUrl, snapshotFetchedPage, startCloudLogin, strikingDistance, strikingDistanceWith, summarizeCrawlReport, pageTechnicalSignals as technicalPageSignals, telemetryErrorCategory, toSeoError, tokenSchema, tokenStoreSchema, topFixes, trackTelemetryCommandFailed, trackTelemetryReportComplete, trackTelemetryReportFailed, trackTelemetryReportStart, trackTelemetrySetupComplete, trafficAnomaly, trafficAnomalyWith, typesafeDecisionModel, unavailableSegmentImpactReport, unicodeTokens, uninstallProviderExtension, updateCorrelation, updateNotice, updatePostmortemCsvFiles, updatePostmortemWorkflow, urlInspectionQuota, validateResearchImportColumnSources, verifyAnalyticsProvider, verifyProviderExtension, verifyQueryContent, verifyQueryContentWith, withAgentReportContract, withRankingHistory, workflowPresentation, writeAhrefsApiKey, writeBingApiKey, writeClickySiteKey, writeConfig, writeDataForSeoCredentials, writeInstalledProviderPackages, writeOauthClient, writeProviderExtensionAccount, writeProviderExtensionCredentials, writeProviderSecret, writeToCache, writeTokens, xRobotsDirectives };
|
|
35260
36144
|
|
|
35261
36145
|
//# sourceMappingURL=index.js.map
|