seo 0.13.2 → 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 +37 -8
- package/dist/cli.js +84 -80
- package/dist/cli.js.map +1 -1
- package/dist/{dist-BBszRLUG.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-Dmtr8qrP.js → esm-BmuxgZyN.js} +95 -188
- package/dist/{esm-Dmtr8qrP.js.map → esm-BmuxgZyN.js.map} +1 -1
- package/dist/index.d.ts +92 -406
- package/dist/index.js +1558 -646
- package/dist/index.js.map +1 -1
- package/dist/mcp.d.ts +0 -14
- package/dist/mcp.js +1130 -889
- 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-7TxFytBp.js +0 -420
- package/dist/dist-7TxFytBp.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) {
|
|
@@ -1638,6 +1632,13 @@ async function failure(response, label, subject, options) {
|
|
|
1638
1632
|
const detail = (await response.text().catch(() => "")).slice(0, 300);
|
|
1639
1633
|
const status = response.status;
|
|
1640
1634
|
const what = subject ? `${label} for ${subject}` : label;
|
|
1635
|
+
if (status === 400) {
|
|
1636
|
+
let reason = detail;
|
|
1637
|
+
try {
|
|
1638
|
+
reason = JSON.parse(detail).error?.message ?? detail;
|
|
1639
|
+
} catch {}
|
|
1640
|
+
return new GoogleApiError("INVALID_INPUT", `${label} rejected the request: ${reason}`.trim(), status);
|
|
1641
|
+
}
|
|
1641
1642
|
if (status === 401) return new GoogleApiError("AUTH_EXPIRED", `Google rejected the sign-in. ${options.signIn}`, status);
|
|
1642
1643
|
if (status === 403) return new GoogleApiError("ACCESS_DENIED", `Google denied access to ${what}. Check the Google account, its access to this property, and the granted scopes. ${detail}`.trim(), status);
|
|
1643
1644
|
if (status === 404) return new GoogleApiError("PROPERTY_NOT_FOUND", `Google could not find ${what}. Check the property and account access.`, status);
|
|
@@ -1738,8 +1739,7 @@ async function runAnalyticsReport(gfetch, propertyId, body, options) {
|
|
|
1738
1739
|
body
|
|
1739
1740
|
});
|
|
1740
1741
|
}
|
|
1741
|
-
|
|
1742
|
-
//#region packages/core/src/bing/api.ts
|
|
1742
|
+
// packages/core/src/bing/api.ts
|
|
1743
1743
|
const BASE = "https://ssl.bing.com/webmaster/api.svc/json";
|
|
1744
1744
|
const MAX_BYTES$4 = 8e6;
|
|
1745
1745
|
function bingCall(auth, options) {
|
|
@@ -1769,7 +1769,7 @@ function bingCall(auth, options) {
|
|
|
1769
1769
|
return (await readJson(response, "Bing Webmaster", { maxBytes: MAX_BYTES$4 })).d;
|
|
1770
1770
|
};
|
|
1771
1771
|
}
|
|
1772
|
-
function day$
|
|
1772
|
+
function day$3(value) {
|
|
1773
1773
|
if (typeof value !== "string") return null;
|
|
1774
1774
|
const legacy = /^\/Date\((-?\d+)(?:[+-]\d{4})?\)\/$/.exec(value);
|
|
1775
1775
|
const date = legacy ? new Date(Number(legacy[1])) : new Date(value);
|
|
@@ -1790,7 +1790,7 @@ async function bingTraffic(call, site) {
|
|
|
1790
1790
|
const rows = [];
|
|
1791
1791
|
let invalidRows = 0;
|
|
1792
1792
|
for (const row of list$2(await call("GetRankAndTrafficStats", { siteUrl: site }), "traffic list")) {
|
|
1793
|
-
const date = day$
|
|
1793
|
+
const date = day$3(row.Date);
|
|
1794
1794
|
const clicks = count$3(row.Clicks);
|
|
1795
1795
|
const impressions = count$3(row.Impressions);
|
|
1796
1796
|
if (!date || clicks === null || impressions === null) {
|
|
@@ -1813,7 +1813,7 @@ async function keyed(call, method, params) {
|
|
|
1813
1813
|
const rows = [];
|
|
1814
1814
|
let invalidRows = 0;
|
|
1815
1815
|
for (const row of list$2(await call(method, params), "row list")) {
|
|
1816
|
-
const date = day$
|
|
1816
|
+
const date = day$3(row.Date);
|
|
1817
1817
|
const key = typeof row.Query === "string" ? row.Query.trim() : "";
|
|
1818
1818
|
const clicks = count$3(row.Clicks);
|
|
1819
1819
|
const impressions = count$3(row.Impressions);
|
|
@@ -1850,8 +1850,7 @@ function bingSiteFor(sites, target) {
|
|
|
1850
1850
|
if (!wanted) return null;
|
|
1851
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;
|
|
1852
1852
|
}
|
|
1853
|
-
|
|
1854
|
-
//#region packages/core/src/gsc/dates.ts
|
|
1853
|
+
// packages/core/src/gsc/dates.ts
|
|
1855
1854
|
const GSC_TIME_ZONE = "America/Los_Angeles";
|
|
1856
1855
|
const GSC_FINAL_DATA_LAG_DAYS = 4;
|
|
1857
1856
|
function dateInTimeZone(now, timeZone) {
|
|
@@ -1880,8 +1879,7 @@ function finalGscDateRange(days = 28, now = /* @__PURE__ */ new Date()) {
|
|
|
1880
1879
|
endDate
|
|
1881
1880
|
};
|
|
1882
1881
|
}
|
|
1883
|
-
|
|
1884
|
-
//#region packages/core/src/analyze/site-diagnostics/quick-wins-report-input.ts
|
|
1882
|
+
// packages/core/src/analyze/site-diagnostics/quick-wins-report-input.ts
|
|
1885
1883
|
function integerOption$1(input) {
|
|
1886
1884
|
if (input.value === void 0) return input.fallback;
|
|
1887
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}.`);
|
|
@@ -1907,8 +1905,7 @@ function explicitDateRange(input, maximumDays) {
|
|
|
1907
1905
|
days
|
|
1908
1906
|
};
|
|
1909
1907
|
}
|
|
1910
|
-
|
|
1911
|
-
//#region packages/core/src/analyze/site-diagnostics/decay-report-input.ts
|
|
1908
|
+
// packages/core/src/analyze/site-diagnostics/decay-report-input.ts
|
|
1912
1909
|
function decayNumberOption(input) {
|
|
1913
1910
|
const value = input.value ?? input.fallback;
|
|
1914
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}.`);
|
|
@@ -1964,8 +1961,7 @@ function decayComparisonRange(current, comparison = "previous-period") {
|
|
|
1964
1961
|
endDate
|
|
1965
1962
|
};
|
|
1966
1963
|
}
|
|
1967
|
-
|
|
1968
|
-
//#region packages/core/src/updates/ranking-history.ts
|
|
1964
|
+
// packages/core/src/updates/ranking-history.ts
|
|
1969
1965
|
const RANKING_HISTORY = [
|
|
1970
1966
|
{
|
|
1971
1967
|
id: "XhUDXP7A67iHCD2kmbVu",
|
|
@@ -2220,8 +2216,7 @@ const RANKING_HISTORY = [
|
|
|
2220
2216
|
external_desc: "November 2021 spam update"
|
|
2221
2217
|
}
|
|
2222
2218
|
];
|
|
2223
|
-
|
|
2224
|
-
//#region packages/core/src/updates/search-updates.ts
|
|
2219
|
+
// packages/core/src/updates/search-updates.ts
|
|
2225
2220
|
function classifyUpdate(name, product) {
|
|
2226
2221
|
const lower = `${name} ${product}`.toLowerCase();
|
|
2227
2222
|
if (lower.includes("core")) return "core";
|
|
@@ -2270,8 +2265,7 @@ function findOverlappingSearchUpdates(input) {
|
|
|
2270
2265
|
return updateStart <= windowEnd && updateEnd >= windowStart;
|
|
2271
2266
|
});
|
|
2272
2267
|
}
|
|
2273
|
-
|
|
2274
|
-
//#region packages/core/src/commands/types.ts
|
|
2268
|
+
// packages/core/src/commands/types.ts
|
|
2275
2269
|
var CommandError = class extends Error {
|
|
2276
2270
|
code;
|
|
2277
2271
|
constructor(code, message) {
|
|
@@ -2279,8 +2273,7 @@ var CommandError = class extends Error {
|
|
|
2279
2273
|
this.code = code;
|
|
2280
2274
|
}
|
|
2281
2275
|
};
|
|
2282
|
-
|
|
2283
|
-
//#region packages/core/src/commands/shared.ts
|
|
2276
|
+
// packages/core/src/commands/shared.ts
|
|
2284
2277
|
const FACTS_ONLY = "These are facts from Search Console, not instructions.";
|
|
2285
2278
|
const ADVICE_KEYS = /^(recommendations?|actions?|advice|suggested[A-Z]\w*|nextSteps?)$/;
|
|
2286
2279
|
function factsOnly(value) {
|
|
@@ -2416,8 +2409,7 @@ function matchProperty(names, site) {
|
|
|
2416
2409
|
}
|
|
2417
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;
|
|
2418
2411
|
}
|
|
2419
|
-
|
|
2420
|
-
//#region packages/core/src/commands/present.ts
|
|
2412
|
+
// packages/core/src/commands/present.ts
|
|
2421
2413
|
const LIST_KEYS = [
|
|
2422
2414
|
"items",
|
|
2423
2415
|
"rows",
|
|
@@ -2486,7 +2478,7 @@ const METRIC_COLUMNS = [
|
|
|
2486
2478
|
"score",
|
|
2487
2479
|
"priority.score"
|
|
2488
2480
|
];
|
|
2489
|
-
const SUMMARY_ROWS$
|
|
2481
|
+
const SUMMARY_ROWS$2 = 10;
|
|
2490
2482
|
const CELL_MAX = 80;
|
|
2491
2483
|
const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2492
2484
|
const isPrimitive = (value) => value === null || [
|
|
@@ -2645,7 +2637,7 @@ function presentReport(input) {
|
|
|
2645
2637
|
}
|
|
2646
2638
|
};
|
|
2647
2639
|
}
|
|
2648
|
-
const shown = flat.slice(0, Math.min(input.limit, SUMMARY_ROWS$
|
|
2640
|
+
const shown = flat.slice(0, Math.min(input.limit, SUMMARY_ROWS$2));
|
|
2649
2641
|
const columns = columnsFor(flat, 8);
|
|
2650
2642
|
return {
|
|
2651
2643
|
text: [
|
|
@@ -2680,8 +2672,7 @@ function preview(report, origin, rows) {
|
|
|
2680
2672
|
text: table$1(flat.slice(0, rows), columnsFor(flat, 6), origin)
|
|
2681
2673
|
};
|
|
2682
2674
|
}
|
|
2683
|
-
|
|
2684
|
-
//#region packages/core/src/commands/sites.ts
|
|
2675
|
+
// packages/core/src/commands/sites.ts
|
|
2685
2676
|
async function readableSites(google, signIn) {
|
|
2686
2677
|
return (await listSearchConsoleSites(google, { signIn })).filter((entry) => entry.permissionLevel !== "siteUnverifiedUser").map((entry) => ({
|
|
2687
2678
|
siteUrl: entry.siteUrl,
|
|
@@ -2755,8 +2746,7 @@ async function runSites(input) {
|
|
|
2755
2746
|
}
|
|
2756
2747
|
};
|
|
2757
2748
|
}
|
|
2758
|
-
|
|
2759
|
-
//#region packages/core/src/commands/linked.ts
|
|
2749
|
+
// packages/core/src/commands/linked.ts
|
|
2760
2750
|
const MAX_ROWS$2 = 1e5;
|
|
2761
2751
|
const PERMISSION_RANK = {
|
|
2762
2752
|
siteOwner: 0,
|
|
@@ -2859,8 +2849,7 @@ var LinkedAccounts = class {
|
|
|
2859
2849
|
});
|
|
2860
2850
|
}
|
|
2861
2851
|
};
|
|
2862
|
-
|
|
2863
|
-
//#region packages/core/src/paths.ts
|
|
2852
|
+
// packages/core/src/paths.ts
|
|
2864
2853
|
let memoizedPaths;
|
|
2865
2854
|
function defaultConfigDir(input) {
|
|
2866
2855
|
if (input.platform === "win32") return join(input.env.APPDATA ?? join(input.home, "AppData", "Roaming"), "seo", "Config");
|
|
@@ -2955,8 +2944,7 @@ function fileSize(path) {
|
|
|
2955
2944
|
return 0;
|
|
2956
2945
|
}
|
|
2957
2946
|
}
|
|
2958
|
-
|
|
2959
|
-
//#region packages/core/src/storage/files.ts
|
|
2947
|
+
// packages/core/src/storage/files.ts
|
|
2960
2948
|
function ensureParentDir(path, mode = 448) {
|
|
2961
2949
|
mkdirSync(dirname(path), {
|
|
2962
2950
|
recursive: true,
|
|
@@ -2994,8 +2982,7 @@ function safeRemove(path) {
|
|
|
2994
2982
|
force: true
|
|
2995
2983
|
});
|
|
2996
2984
|
}
|
|
2997
|
-
|
|
2998
|
-
//#region packages/core/src/telemetry-failures.ts
|
|
2985
|
+
// packages/core/src/telemetry-failures.ts
|
|
2999
2986
|
const TELEMETRY_ERROR_CATEGORIES = [
|
|
3000
2987
|
"auth",
|
|
3001
2988
|
"crawl_timeout",
|
|
@@ -3270,8 +3257,7 @@ function classifyTelemetryFailure(error) {
|
|
|
3270
3257
|
function isTelemetryOperation(value) {
|
|
3271
3258
|
return typeof value === "string" && TELEMETRY_OPERATIONS.includes(value);
|
|
3272
3259
|
}
|
|
3273
|
-
|
|
3274
|
-
//#region packages/core/src/telemetry-shared.ts
|
|
3260
|
+
// packages/core/src/telemetry-shared.ts
|
|
3275
3261
|
const TELEMETRY_ENDPOINT = "https://seoskill.dev/api/t";
|
|
3276
3262
|
const TELEMETRY_SCHEMA_VERSION = 2;
|
|
3277
3263
|
function isoWeek(date) {
|
|
@@ -3290,11 +3276,9 @@ function agentFromClientName(name) {
|
|
|
3290
3276
|
if (value.includes("codex") || value.includes("openai")) return "codex";
|
|
3291
3277
|
return "unknown";
|
|
3292
3278
|
}
|
|
3293
|
-
|
|
3294
|
-
//#region packages/core/src/version.ts
|
|
3279
|
+
// packages/core/src/version.ts
|
|
3295
3280
|
const SEO_VERSION = version;
|
|
3296
|
-
|
|
3297
|
-
//#region packages/core/src/telemetry.ts
|
|
3281
|
+
// packages/core/src/telemetry.ts
|
|
3298
3282
|
const TELEMETRY_TIMEOUT_MS = 2e3;
|
|
3299
3283
|
const TELEMETRY_EVENTS = [
|
|
3300
3284
|
"first_run",
|
|
@@ -3658,8 +3642,7 @@ function telemetryFailureInput(error) {
|
|
|
3658
3642
|
};
|
|
3659
3643
|
return classifyTelemetryFailure(error);
|
|
3660
3644
|
}
|
|
3661
|
-
|
|
3662
|
-
//#region packages/core/src/cloud.ts
|
|
3645
|
+
// packages/core/src/cloud.ts
|
|
3663
3646
|
const CLOUD_URL = "https://seoskill.dev";
|
|
3664
3647
|
const PRIVATE = 384;
|
|
3665
3648
|
const EARLY = 6e4;
|
|
@@ -3815,8 +3798,7 @@ async function cloudLogout() {
|
|
|
3815
3798
|
async function openCloudLink(url) {
|
|
3816
3799
|
await open(url).catch(() => void 0);
|
|
3817
3800
|
}
|
|
3818
|
-
|
|
3819
|
-
//#region packages/core/src/types/auth.ts
|
|
3801
|
+
// packages/core/src/types/auth.ts
|
|
3820
3802
|
const tokenSchema = z.object({
|
|
3821
3803
|
provider: z.literal("google"),
|
|
3822
3804
|
account_email: z.string().email().max(320),
|
|
@@ -3845,8 +3827,7 @@ const tokenStoreSchema = z.object({
|
|
|
3845
3827
|
message: "The active Google account must exist in accounts."
|
|
3846
3828
|
});
|
|
3847
3829
|
});
|
|
3848
|
-
|
|
3849
|
-
//#region packages/core/src/providers/contracts.ts
|
|
3830
|
+
// packages/core/src/providers/contracts.ts
|
|
3850
3831
|
const providerIdSchema = z.string().min(1).max(64).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/u);
|
|
3851
3832
|
const providerCapabilitySchema = z.enum([
|
|
3852
3833
|
"keyword-metrics",
|
|
@@ -3864,8 +3845,7 @@ const providerCapabilitySchema = z.enum([
|
|
|
3864
3845
|
"ai-mentions",
|
|
3865
3846
|
"ai-prompt-observation"
|
|
3866
3847
|
]);
|
|
3867
|
-
|
|
3868
|
-
//#region packages/core/src/types/config.ts
|
|
3848
|
+
// packages/core/src/types/config.ts
|
|
3869
3849
|
const providerPreferenceSchema = z.enum(["cheap", "authoritative"]);
|
|
3870
3850
|
const analyticsProviderSchema = z.enum(["google", "clicky"]);
|
|
3871
3851
|
const analyticsExtensionIdSchema = z.string().min(1).max(64).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/u);
|
|
@@ -3918,8 +3898,7 @@ async function setKeyringPassword(service, account, password) {
|
|
|
3918
3898
|
async function deleteKeyringPassword(service, account) {
|
|
3919
3899
|
return (await resolveKeyring()).deletePassword(service, account);
|
|
3920
3900
|
}
|
|
3921
|
-
|
|
3922
|
-
//#region packages/core/src/storage/config.ts
|
|
3901
|
+
// packages/core/src/storage/config.ts
|
|
3923
3902
|
const KEYRING_SERVICE$1 = "seo";
|
|
3924
3903
|
const PRIVATE_FILE_MODE$2 = 384;
|
|
3925
3904
|
function tightenConfigPermissions(path) {
|
|
@@ -4201,8 +4180,7 @@ function getPrivacySnapshot() {
|
|
|
4201
4180
|
}
|
|
4202
4181
|
];
|
|
4203
4182
|
}
|
|
4204
|
-
|
|
4205
|
-
//#region packages/core/src/storage/lock.ts
|
|
4183
|
+
// packages/core/src/storage/lock.ts
|
|
4206
4184
|
async function withFileLock(path, task) {
|
|
4207
4185
|
ensureParentDir(path);
|
|
4208
4186
|
const fastLocks = process.env.SEO_LOCK_FAST === "1";
|
|
@@ -4231,14 +4209,12 @@ async function withFileLock(path, task) {
|
|
|
4231
4209
|
await release();
|
|
4232
4210
|
}
|
|
4233
4211
|
}
|
|
4234
|
-
|
|
4235
|
-
//#region packages/core/src/gsc/shared-client.generated.ts
|
|
4212
|
+
// packages/core/src/gsc/shared-client.generated.ts
|
|
4236
4213
|
const SHARED_OAUTH_CLIENT = {
|
|
4237
4214
|
clientId: "548418788053-q7p964rqvjiv8ooamciprku6bpu34u9d.apps.googleusercontent.com",
|
|
4238
4215
|
clientSecret: "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"
|
|
4239
4216
|
};
|
|
4240
|
-
|
|
4241
|
-
//#region packages/core/src/gsc/auth/client-config.ts
|
|
4217
|
+
// packages/core/src/gsc/auth/client-config.ts
|
|
4242
4218
|
function getAuthModeStatus() {
|
|
4243
4219
|
const byo = readOauthClient();
|
|
4244
4220
|
return {
|
|
@@ -4270,8 +4246,7 @@ function getClientConfig(source) {
|
|
|
4270
4246
|
function missingOAuthClientMessage(source) {
|
|
4271
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.";
|
|
4272
4248
|
}
|
|
4273
|
-
|
|
4274
|
-
//#region packages/core/src/gsc/auth/types.ts
|
|
4249
|
+
// packages/core/src/gsc/auth/types.ts
|
|
4275
4250
|
const GOOGLE_READONLY_SCOPES = ["https://www.googleapis.com/auth/webmasters.readonly", "https://www.googleapis.com/auth/analytics.readonly"];
|
|
4276
4251
|
const GOOGLE_READONLY_SCOPE_LABELS = {
|
|
4277
4252
|
"https://www.googleapis.com/auth/webmasters.readonly": "Search Console",
|
|
@@ -4286,8 +4261,7 @@ const GOOGLE_SCOPE = [
|
|
|
4286
4261
|
"openid",
|
|
4287
4262
|
"email"
|
|
4288
4263
|
].join(" ");
|
|
4289
|
-
|
|
4290
|
-
//#region packages/core/src/gsc/auth/service-account.ts
|
|
4264
|
+
// packages/core/src/gsc/auth/service-account.ts
|
|
4291
4265
|
const GOOGLE_TOKEN_ENDPOINT$1 = "https://oauth2.googleapis.com/token";
|
|
4292
4266
|
const TOKEN_BUFFER_MS = 6e4;
|
|
4293
4267
|
function authConfigError(message) {
|
|
@@ -4433,8 +4407,7 @@ var ServiceAccountAccessTokenClient = class {
|
|
|
4433
4407
|
return this.token.value;
|
|
4434
4408
|
}
|
|
4435
4409
|
};
|
|
4436
|
-
|
|
4437
|
-
//#region packages/core/src/gsc/auth/token-endpoint.ts
|
|
4410
|
+
// packages/core/src/gsc/auth/token-endpoint.ts
|
|
4438
4411
|
const GOOGLE_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token";
|
|
4439
4412
|
var GoogleTokenEndpointError = class extends Error {
|
|
4440
4413
|
status;
|
|
@@ -4488,8 +4461,7 @@ async function exchangeGoogleCode(input, fetchImpl = fetch) {
|
|
|
4488
4461
|
}
|
|
4489
4462
|
return readJson(response, "Google sign-in");
|
|
4490
4463
|
}
|
|
4491
|
-
|
|
4492
|
-
//#region packages/core/src/gsc/auth/authorized-client.ts
|
|
4464
|
+
// packages/core/src/gsc/auth/authorized-client.ts
|
|
4493
4465
|
const REFRESH_BUFFER_MS = 6e4;
|
|
4494
4466
|
function authRequired(accountEmail) {
|
|
4495
4467
|
return new SeoError("AUTH_REQUIRED", accountEmail ? `Google account ${accountEmail} is not connected. Run \`seo auth login\` first.` : notSignedIn());
|
|
@@ -4641,8 +4613,7 @@ async function authStatus() {
|
|
|
4641
4613
|
serviceAccount
|
|
4642
4614
|
};
|
|
4643
4615
|
}
|
|
4644
|
-
|
|
4645
|
-
//#region packages/core/src/gsc/auth/callback-page.ts
|
|
4616
|
+
// packages/core/src/gsc/auth/callback-page.ts
|
|
4646
4617
|
function callbackCopy(options) {
|
|
4647
4618
|
if (options.status === "connected") return {
|
|
4648
4619
|
title: "Google account connected",
|
|
@@ -4681,8 +4652,7 @@ function oauthCallbackPage(options = { status: "connected" }) {
|
|
|
4681
4652
|
</body>
|
|
4682
4653
|
</html>`;
|
|
4683
4654
|
}
|
|
4684
|
-
|
|
4685
|
-
//#region packages/core/src/gsc/auth/loopback-callback.ts
|
|
4655
|
+
// packages/core/src/gsc/auth/loopback-callback.ts
|
|
4686
4656
|
const OAUTH_CALLBACK_TIMEOUT_MS = 3e5;
|
|
4687
4657
|
function timeoutError() {
|
|
4688
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.");
|
|
@@ -4738,8 +4708,7 @@ function waitForCode(input) {
|
|
|
4738
4708
|
});
|
|
4739
4709
|
});
|
|
4740
4710
|
}
|
|
4741
|
-
|
|
4742
|
-
//#region packages/core/src/gsc/auth/user-email.ts
|
|
4711
|
+
// packages/core/src/gsc/auth/user-email.ts
|
|
4743
4712
|
function decodeJwtEmail(idToken) {
|
|
4744
4713
|
if (!idToken) return;
|
|
4745
4714
|
const [, payload] = idToken.split(".");
|
|
@@ -4755,8 +4724,7 @@ async function fetchUserEmail(accessToken) {
|
|
|
4755
4724
|
if (!response.ok) return;
|
|
4756
4725
|
return (await readJson(response, "Google user info")).email;
|
|
4757
4726
|
}
|
|
4758
|
-
|
|
4759
|
-
//#region packages/core/src/gsc/auth/loopback.ts
|
|
4727
|
+
// packages/core/src/gsc/auth/loopback.ts
|
|
4760
4728
|
function missingSharedClientLoginMessage() {
|
|
4761
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.";
|
|
4762
4730
|
}
|
|
@@ -4831,8 +4799,7 @@ async function loginWithLoopback(clientConfig = getClientConfig()) {
|
|
|
4831
4799
|
server.close();
|
|
4832
4800
|
}
|
|
4833
4801
|
}
|
|
4834
|
-
|
|
4835
|
-
//#region packages/core/src/gsc/client/fetch.ts
|
|
4802
|
+
// packages/core/src/gsc/client/fetch.ts
|
|
4836
4803
|
async function authedFetch(client, url, init) {
|
|
4837
4804
|
const accessToken = await client.getAccessToken();
|
|
4838
4805
|
if (!accessToken) throw new SeoError("AUTH_EXPIRED", "Could not obtain a Google access token. Run `seo auth login` again.");
|
|
@@ -4853,8 +4820,7 @@ function googleFetchFor(client) {
|
|
|
4853
4820
|
return (url, init) => authedFetch(client, url, init);
|
|
4854
4821
|
}
|
|
4855
4822
|
const LOCAL_SIGN_IN = { signIn: "Run `seo auth login` again." };
|
|
4856
|
-
|
|
4857
|
-
//#region packages/core/src/storage/cache-maintenance.ts
|
|
4823
|
+
// packages/core/src/storage/cache-maintenance.ts
|
|
4858
4824
|
const MEBIBYTE$2 = 1048576;
|
|
4859
4825
|
const CACHE_MAX_BYTES = 256 * MEBIBYTE$2;
|
|
4860
4826
|
const CACHE_MAX_AGE_MS = 2592e6;
|
|
@@ -5031,8 +4997,7 @@ function runCacheMaintenance(database, options = {}) {
|
|
|
5031
4997
|
compaction
|
|
5032
4998
|
};
|
|
5033
4999
|
}
|
|
5034
|
-
|
|
5035
|
-
//#region packages/core/src/storage/sqlite.ts
|
|
5000
|
+
// packages/core/src/storage/sqlite.ts
|
|
5036
5001
|
function withoutDriverMetadata(row) {
|
|
5037
5002
|
if (!row || typeof row !== "object" || Array.isArray(row)) return row;
|
|
5038
5003
|
const { _metadata: _driverMetadata, ...data } = row;
|
|
@@ -5077,8 +5042,7 @@ var SqliteDatabase = class {
|
|
|
5077
5042
|
return this;
|
|
5078
5043
|
}
|
|
5079
5044
|
};
|
|
5080
|
-
|
|
5081
|
-
//#region packages/core/src/storage/database.ts
|
|
5045
|
+
// packages/core/src/storage/database.ts
|
|
5082
5046
|
const PRIVATE_DATABASE_FILE_MODE = 384;
|
|
5083
5047
|
const DATABASE_FILE_SUFFIXES = [
|
|
5084
5048
|
"",
|
|
@@ -5361,8 +5325,7 @@ function clearCache(provider, olderThanMs) {
|
|
|
5361
5325
|
compactCacheDatabase(database, { allowFullVacuum: true });
|
|
5362
5326
|
return removed;
|
|
5363
5327
|
}
|
|
5364
|
-
|
|
5365
|
-
//#region packages/core/src/ga4/client.ts
|
|
5328
|
+
// packages/core/src/ga4/client.ts
|
|
5366
5329
|
function ga4RequestCanUseCache(body) {
|
|
5367
5330
|
return body.dateRanges.every((range) => /^\d{4}-\d{2}-\d{2}$/.test(range.startDate) && /^\d{4}-\d{2}-\d{2}$/.test(range.endDate));
|
|
5368
5331
|
}
|
|
@@ -5443,8 +5406,7 @@ function ga4ReportQualityWarnings(result, label = "Google Analytics report") {
|
|
|
5443
5406
|
})) warnings.push(`${label} was sampled by Google Analytics.`);
|
|
5444
5407
|
return warnings;
|
|
5445
5408
|
}
|
|
5446
|
-
|
|
5447
|
-
//#region packages/core/src/analyze/ai-referrals-sources.ts
|
|
5409
|
+
// packages/core/src/analyze/ai-referrals-sources.ts
|
|
5448
5410
|
const AI_REFERRAL_SOURCES_VERSION = "ai-referral-sources@1";
|
|
5449
5411
|
const AI_REFERRAL_SOURCES = [
|
|
5450
5412
|
{
|
|
@@ -5530,8 +5492,7 @@ function aiReferralSourceForValue(value) {
|
|
|
5530
5492
|
if (!hostname) return void 0;
|
|
5531
5493
|
return AI_REFERRAL_SOURCES.find((source) => source.domains.some((domain) => matchesDomain(hostname, domain)));
|
|
5532
5494
|
}
|
|
5533
|
-
|
|
5534
|
-
//#region packages/core/src/analyze/ai-referrals-analysis.ts
|
|
5495
|
+
// packages/core/src/analyze/ai-referrals-analysis.ts
|
|
5535
5496
|
function compareText$19(left, right) {
|
|
5536
5497
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
5537
5498
|
}
|
|
@@ -5703,13 +5664,12 @@ function analyzeAiReferralRows(input) {
|
|
|
5703
5664
|
]
|
|
5704
5665
|
};
|
|
5705
5666
|
}
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
const PAGE_SIZE$3 = 25e3;
|
|
5667
|
+
// packages/core/src/analyze/ai-referrals-query.ts
|
|
5668
|
+
const PAGE_SIZE$4 = 25e3;
|
|
5709
5669
|
async function fetchAiReferralRows(input) {
|
|
5710
|
-
const pageSize = input.pageSize ?? PAGE_SIZE$
|
|
5670
|
+
const pageSize = input.pageSize ?? PAGE_SIZE$4;
|
|
5711
5671
|
if (!Number.isInteger(input.maxRows) || input.maxRows < 1) throw new Error("maxRows must be a positive whole number.");
|
|
5712
|
-
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}.`);
|
|
5713
5673
|
const rows = [];
|
|
5714
5674
|
const warnings = /* @__PURE__ */ new Set();
|
|
5715
5675
|
const retainedKeys = /* @__PURE__ */ new Set();
|
|
@@ -5779,8 +5739,7 @@ function sessionSourceInListFilter(values) {
|
|
|
5779
5739
|
}
|
|
5780
5740
|
} };
|
|
5781
5741
|
}
|
|
5782
|
-
|
|
5783
|
-
//#region packages/core/src/analyze/ai-referrals.ts
|
|
5742
|
+
// packages/core/src/analyze/ai-referrals.ts
|
|
5784
5743
|
const DEFAULT_MAX_ROWS$1 = 1e5;
|
|
5785
5744
|
const MAX_ROWS$1 = 1e5;
|
|
5786
5745
|
const DEFAULT_RESULT_LIMIT = 25;
|
|
@@ -5957,8 +5916,7 @@ async function aiReferralsReport(input, dependencies = {}) {
|
|
|
5957
5916
|
totalUsersEvidence
|
|
5958
5917
|
});
|
|
5959
5918
|
}
|
|
5960
|
-
|
|
5961
|
-
//#region packages/core/src/robots-directives.ts
|
|
5919
|
+
// packages/core/src/robots-directives.ts
|
|
5962
5920
|
function normalizedDirective(value) {
|
|
5963
5921
|
const directive = value.trim().toLowerCase().split(/\s+/)[0];
|
|
5964
5922
|
if (directive === "none") return ["noindex", "nofollow"];
|
|
@@ -6217,8 +6175,7 @@ function resolveStreamedSegments($) {
|
|
|
6217
6175
|
function hasNextAppRouterPayload($) {
|
|
6218
6176
|
return $("script:not([src])").toArray().some((script) => $(script).text().includes("self.__next_f"));
|
|
6219
6177
|
}
|
|
6220
|
-
|
|
6221
|
-
//#region packages/core/src/extract/canonical.ts
|
|
6178
|
+
// packages/core/src/extract/canonical.ts
|
|
6222
6179
|
function headerValue$4(headers, name) {
|
|
6223
6180
|
const lower = name.toLowerCase();
|
|
6224
6181
|
return Object.entries(headers).find(([key]) => key.toLowerCase() === lower)?.[1];
|
|
@@ -6367,11 +6324,10 @@ function extractHttpCanonicalEvidence(headers, baseUrl) {
|
|
|
6367
6324
|
function extractCanonicalEvidence($, headers, baseUrl) {
|
|
6368
6325
|
return evidenceFromCandidates([...htmlCandidates($, baseUrl), ...headerCandidates(headers, baseUrl)]);
|
|
6369
6326
|
}
|
|
6370
|
-
|
|
6371
|
-
//#region packages/core/src/extract/content-sketch.ts
|
|
6327
|
+
// packages/core/src/extract/content-sketch.ts
|
|
6372
6328
|
const SHINGLE_SIZE = 5;
|
|
6373
6329
|
const MAX_HASHES = 32;
|
|
6374
|
-
function words$
|
|
6330
|
+
function words$3(value) {
|
|
6375
6331
|
return value.normalize("NFKC").toLocaleLowerCase("en").match(/[\p{L}\p{N}]+/gu) ?? [];
|
|
6376
6332
|
}
|
|
6377
6333
|
function sampleIndexes(total, limit) {
|
|
@@ -6382,7 +6338,7 @@ function digest(value) {
|
|
|
6382
6338
|
return createHash("sha256").update(value).digest("hex").slice(0, 16);
|
|
6383
6339
|
}
|
|
6384
6340
|
function contentSketch(value) {
|
|
6385
|
-
const tokens = words$
|
|
6341
|
+
const tokens = words$3(value);
|
|
6386
6342
|
const hashes = sampleIndexes(Math.max(0, tokens.length - SHINGLE_SIZE + 1), MAX_HASHES).map((index) => digest(tokens.slice(index, index + SHINGLE_SIZE).join(" ")));
|
|
6387
6343
|
return {
|
|
6388
6344
|
version: 1,
|
|
@@ -6392,8 +6348,7 @@ function contentSketch(value) {
|
|
|
6392
6348
|
hashes
|
|
6393
6349
|
};
|
|
6394
6350
|
}
|
|
6395
|
-
|
|
6396
|
-
//#region packages/core/src/extract/main-content.ts
|
|
6351
|
+
// packages/core/src/extract/main-content.ts
|
|
6397
6352
|
function parseDocument$1(html) {
|
|
6398
6353
|
const { document } = parseHTML(html);
|
|
6399
6354
|
if (document.documentElement?.localName === "html") return document;
|
|
@@ -6519,8 +6474,7 @@ async function extractMainMarkdown(fetchResult) {
|
|
|
6519
6474
|
truncated: true
|
|
6520
6475
|
};
|
|
6521
6476
|
}
|
|
6522
|
-
|
|
6523
|
-
//#region packages/core/src/extract/google-rich-results.ts
|
|
6477
|
+
// packages/core/src/extract/google-rich-results.ts
|
|
6524
6478
|
const SUPPORTED_TYPES = /* @__PURE__ */ new Set([
|
|
6525
6479
|
"Article",
|
|
6526
6480
|
"BlogPosting",
|
|
@@ -6797,8 +6751,7 @@ function selectGoogleRichResultAssessments(assessments, limit = 50) {
|
|
|
6797
6751
|
}
|
|
6798
6752
|
};
|
|
6799
6753
|
}
|
|
6800
|
-
|
|
6801
|
-
//#region packages/core/src/extract/structured-data.ts
|
|
6754
|
+
// packages/core/src/extract/structured-data.ts
|
|
6802
6755
|
function schemaTypeFromIri(value) {
|
|
6803
6756
|
try {
|
|
6804
6757
|
const iri = new URL(value);
|
|
@@ -7035,8 +6988,7 @@ function extractStructuredData($, baseUrl, options = {}) {
|
|
|
7035
6988
|
hasDate: parsed.hasDate || $("[itemprop~=\"datePublished\"], [itemprop~=\"dateModified\"], [property~=\"datePublished\"], [property~=\"dateModified\"]").toArray().some((element) => isValidStructuredDate($(element).attr("content") ?? $(element).attr("datetime") ?? $(element).text()))
|
|
7036
6989
|
};
|
|
7037
6990
|
}
|
|
7038
|
-
|
|
7039
|
-
//#region packages/core/src/extract/page-extractor.ts
|
|
6991
|
+
// packages/core/src/extract/page-extractor.ts
|
|
7040
6992
|
function metaRefresh($, finalUrl) {
|
|
7041
6993
|
for (const element of $("meta[http-equiv]").toArray()) {
|
|
7042
6994
|
if ($(element).attr("http-equiv")?.trim().toLowerCase() !== "refresh") continue;
|
|
@@ -7454,8 +7406,7 @@ async function extractPage(fetchResult, extractor = "defuddle", dependencies = {
|
|
|
7454
7406
|
]
|
|
7455
7407
|
};
|
|
7456
7408
|
}
|
|
7457
|
-
|
|
7458
|
-
//#region packages/core/src/fetch/crawler-identity.ts
|
|
7409
|
+
// packages/core/src/fetch/crawler-identity.ts
|
|
7459
7410
|
const SEO_CRAWLER_TOKEN = "SEO-Skill";
|
|
7460
7411
|
const SEO_CRAWLER_USER_AGENT = `${SEO_CRAWLER_TOKEN}/${SEO_VERSION} (+https://seoskill.dev)`;
|
|
7461
7412
|
const SEO_CRAWLER_IDENTITY = {
|
|
@@ -7465,8 +7416,7 @@ const SEO_CRAWLER_IDENTITY = {
|
|
|
7465
7416
|
userAgent: SEO_CRAWLER_USER_AGENT,
|
|
7466
7417
|
documentationUrl: "https://seoskill.dev/docs/crawler/"
|
|
7467
7418
|
};
|
|
7468
|
-
|
|
7469
|
-
//#region packages/core/src/fetch/access-block.ts
|
|
7419
|
+
// packages/core/src/fetch/access-block.ts
|
|
7470
7420
|
const CLOUDFLARE_SKIP_DOCS = "https://developers.cloudflare.com/waf/custom-rules/skip/";
|
|
7471
7421
|
function isHeadersLike(headers) {
|
|
7472
7422
|
return "get" in headers && typeof headers.get === "function";
|
|
@@ -7536,8 +7486,7 @@ function detectAccessBlock(input) {
|
|
|
7536
7486
|
}
|
|
7537
7487
|
};
|
|
7538
7488
|
}
|
|
7539
|
-
|
|
7540
|
-
//#region packages/core/src/fetch/http-client.ts
|
|
7489
|
+
// packages/core/src/fetch/http-client.ts
|
|
7541
7490
|
var ResponseSizeLimitError = class extends Error {
|
|
7542
7491
|
maxBytes;
|
|
7543
7492
|
constructor(maxBytes, label = "Response") {
|
|
@@ -7604,8 +7553,7 @@ async function readBoundedResponseText(response, maxBytes, label = "Response") {
|
|
|
7604
7553
|
}
|
|
7605
7554
|
return Buffer.concat(chunks, total).toString("utf8");
|
|
7606
7555
|
}
|
|
7607
|
-
|
|
7608
|
-
//#region packages/core/src/fetch/page-fetcher/rate-controls.ts
|
|
7556
|
+
// packages/core/src/fetch/page-fetcher/rate-controls.ts
|
|
7609
7557
|
const HOST_QUEUES = /* @__PURE__ */ new Map();
|
|
7610
7558
|
const HOST_HEALTH = /* @__PURE__ */ new Map();
|
|
7611
7559
|
var OriginBackpressureError = class extends Error {
|
|
@@ -7750,8 +7698,7 @@ function recordHostFetch(input) {
|
|
|
7750
7698
|
}
|
|
7751
7699
|
return snapshotForHost(input.host);
|
|
7752
7700
|
}
|
|
7753
|
-
|
|
7754
|
-
//#region packages/core/src/fetch/page-fetcher/redirects.ts
|
|
7701
|
+
// packages/core/src/fetch/page-fetcher/redirects.ts
|
|
7755
7702
|
async function fetchWithRedirectChain(url, signal) {
|
|
7756
7703
|
const redirectChain = [];
|
|
7757
7704
|
let currentUrl = url;
|
|
@@ -7776,8 +7723,7 @@ async function fetchWithRedirectChain(url, signal) {
|
|
|
7776
7723
|
}
|
|
7777
7724
|
throw new Error(`Too many redirects for ${url}`);
|
|
7778
7725
|
}
|
|
7779
|
-
|
|
7780
|
-
//#region packages/core/src/fetch/page-fetcher/robots.ts
|
|
7726
|
+
// packages/core/src/fetch/page-fetcher/robots.ts
|
|
7781
7727
|
const MAX_ROBOTS_RESPONSE_BYTES = 1048576;
|
|
7782
7728
|
var RobotsAccessError = class extends Error {
|
|
7783
7729
|
reason;
|
|
@@ -7928,8 +7874,7 @@ function createRobotsSession(input = {}) {
|
|
|
7928
7874
|
}
|
|
7929
7875
|
};
|
|
7930
7876
|
}
|
|
7931
|
-
|
|
7932
|
-
//#region packages/core/src/fetch/page-fetcher/robots-evidence.ts
|
|
7877
|
+
// packages/core/src/fetch/page-fetcher/robots-evidence.ts
|
|
7933
7878
|
function pageRobotsEvidence(robots) {
|
|
7934
7879
|
return {
|
|
7935
7880
|
url: robots.url,
|
|
@@ -7950,8 +7895,7 @@ function diagnosticRobotsEvidence(robots) {
|
|
|
7950
7895
|
error: robots.error
|
|
7951
7896
|
};
|
|
7952
7897
|
}
|
|
7953
|
-
|
|
7954
|
-
//#region packages/core/src/fetch/page-fetcher/plain.ts
|
|
7898
|
+
// packages/core/src/fetch/page-fetcher/plain.ts
|
|
7955
7899
|
const MAX_PAGE_RESPONSE_BYTES = 5242880;
|
|
7956
7900
|
function encodePageFetchCacheEvidence(result) {
|
|
7957
7901
|
return JSON.stringify({
|
|
@@ -8093,8 +8037,7 @@ async function fetchPlain(url, refresh = false, timeoutMs = 2e4, rate, signal, r
|
|
|
8093
8037
|
});
|
|
8094
8038
|
return result;
|
|
8095
8039
|
}
|
|
8096
|
-
|
|
8097
|
-
//#region packages/core/src/fetch/page-fetcher/browser-path.ts
|
|
8040
|
+
// packages/core/src/fetch/page-fetcher/browser-path.ts
|
|
8098
8041
|
const PLAYWRIGHT_CORE_VERSION = "1.61.1";
|
|
8099
8042
|
function systemBrowserCandidates(input) {
|
|
8100
8043
|
const macApplications = ["/Applications", join(input.homeDirectory, "Applications")];
|
|
@@ -8192,8 +8135,7 @@ function browserUnavailableMessage(resolution) {
|
|
|
8192
8135
|
"Set SEO_BROWSER_EXECUTABLE_PATH to use a managed browser executable."
|
|
8193
8136
|
].join(" ");
|
|
8194
8137
|
}
|
|
8195
|
-
|
|
8196
|
-
//#region packages/core/src/fetch/page-fetcher/rendered.ts
|
|
8138
|
+
// packages/core/src/fetch/page-fetcher/rendered.ts
|
|
8197
8139
|
const DEFAULT_RENDER_TIMEOUT_MS = 2e4;
|
|
8198
8140
|
const NETWORK_IDLE_TIMEOUT_MS = 3e3;
|
|
8199
8141
|
const MAX_RENDER_CONTEXTS = 4;
|
|
@@ -8423,8 +8365,7 @@ function looksLikeSpa(html) {
|
|
|
8423
8365
|
const lower = html.toLowerCase();
|
|
8424
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);
|
|
8425
8367
|
}
|
|
8426
|
-
|
|
8427
|
-
//#region packages/core/src/fetch/page-fetcher/rendering-difference.ts
|
|
8368
|
+
// packages/core/src/fetch/page-fetcher/rendering-difference.ts
|
|
8428
8369
|
const MAX_HEADINGS = 50;
|
|
8429
8370
|
const MAX_TEXT_VALUE_LENGTH = 500;
|
|
8430
8371
|
function safeText(value) {
|
|
@@ -8529,8 +8470,7 @@ function renderingDocumentDifference(raw, rendered) {
|
|
|
8529
8470
|
].filter((field) => JSON.stringify(rawDocument[field]) !== JSON.stringify(renderedDocument[field]))
|
|
8530
8471
|
};
|
|
8531
8472
|
}
|
|
8532
|
-
|
|
8533
|
-
//#region packages/core/src/fetch/page-fetcher/status-probe.ts
|
|
8473
|
+
// packages/core/src/fetch/page-fetcher/status-probe.ts
|
|
8534
8474
|
async function statusProbe(url, opts) {
|
|
8535
8475
|
const startedAt = Date.now();
|
|
8536
8476
|
const host = new URL(url).host;
|
|
@@ -8608,8 +8548,7 @@ async function fetchPageStatus(url, opts = {}) {
|
|
|
8608
8548
|
const rate = normalizeRateControls(opts.rate);
|
|
8609
8549
|
return await queueForHost(new URL(url).host, rate).add(() => statusProbe(url, opts));
|
|
8610
8550
|
}
|
|
8611
|
-
|
|
8612
|
-
//#region packages/core/src/fetch/page-fetcher.ts
|
|
8551
|
+
// packages/core/src/fetch/page-fetcher.ts
|
|
8613
8552
|
function normalizeJavaScriptRenderingMode(input) {
|
|
8614
8553
|
if (input === true) return "on";
|
|
8615
8554
|
if (input === false) return "off";
|
|
@@ -8686,8 +8625,7 @@ async function fetchPage(url, opts = {}, dependencies = {}) {
|
|
|
8686
8625
|
}
|
|
8687
8626
|
});
|
|
8688
8627
|
}
|
|
8689
|
-
|
|
8690
|
-
//#region packages/core/src/gsc/property-url.ts
|
|
8628
|
+
// packages/core/src/gsc/property-url.ts
|
|
8691
8629
|
function invalid(message) {
|
|
8692
8630
|
throw new SeoError("INVALID_INPUT", message);
|
|
8693
8631
|
}
|
|
@@ -8880,8 +8818,7 @@ function finalizeUrlInspectionQuota(input) {
|
|
|
8880
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);
|
|
8881
8819
|
}).immediate();
|
|
8882
8820
|
}
|
|
8883
|
-
|
|
8884
|
-
//#region packages/core/src/gsc/client/inspection.ts
|
|
8821
|
+
// packages/core/src/gsc/client/inspection.ts
|
|
8885
8822
|
function credentialKey(input) {
|
|
8886
8823
|
return createHash("sha256").update(`${input.clientId}\n${input.accountEmail.toLowerCase()}`).digest("base64url");
|
|
8887
8824
|
}
|
|
@@ -8966,8 +8903,7 @@ async function inspectUrl(input) {
|
|
|
8966
8903
|
if (!response.ok) throw inspectionResponseError(response, input);
|
|
8967
8904
|
return await readJson(response, "URL Inspection");
|
|
8968
8905
|
}
|
|
8969
|
-
|
|
8970
|
-
//#region packages/core/src/gsc/client/search-analytics.ts
|
|
8906
|
+
// packages/core/src/gsc/client/search-analytics.ts
|
|
8971
8907
|
function searchDateRange(days) {
|
|
8972
8908
|
return finalGscDateRange(days);
|
|
8973
8909
|
}
|
|
@@ -9133,8 +9069,7 @@ async function queryPagesTopQueriesBatch(site, pageUrls, range = 28) {
|
|
|
9133
9069
|
retainedRowLimitReached: result.rowsFetched >= retainedRowLimit
|
|
9134
9070
|
};
|
|
9135
9071
|
}
|
|
9136
|
-
|
|
9137
|
-
//#region packages/core/src/gsc/client/sites.ts
|
|
9072
|
+
// packages/core/src/gsc/client/sites.ts
|
|
9138
9073
|
async function listSites(_refresh = false, accountEmail) {
|
|
9139
9074
|
const entries = !accountEmail && cloudLinked() ? await allCloudSites() : await listSearchConsoleSites(googleFetchFor((await getAuthorized(accountEmail)).client), LOCAL_SIGN_IN);
|
|
9140
9075
|
writeToCache((database) => {
|
|
@@ -9153,8 +9088,7 @@ async function allCloudSites() {
|
|
|
9153
9088
|
}
|
|
9154
9089
|
return [...best.values()];
|
|
9155
9090
|
}
|
|
9156
|
-
|
|
9157
|
-
//#region packages/core/src/analyze/page-technical-signals.ts
|
|
9091
|
+
// packages/core/src/analyze/page-technical-signals.ts
|
|
9158
9092
|
function normalizePageUrl(value) {
|
|
9159
9093
|
try {
|
|
9160
9094
|
const parsed = new URL(value);
|
|
@@ -9187,8 +9121,7 @@ function pageTechnicalSignals(input) {
|
|
|
9187
9121
|
if (input.page?.canonical && !samePageUrl(input.page.finalUrl, absoluteUrl(input.page.canonical, input.page.finalUrl))) signals.push("canonical-mismatch");
|
|
9188
9122
|
return signals;
|
|
9189
9123
|
}
|
|
9190
|
-
|
|
9191
|
-
//#region packages/core/src/analyze/title-width.ts
|
|
9124
|
+
// packages/core/src/analyze/title-width.ts
|
|
9192
9125
|
const SERP_TITLE_REFERENCE_WIDTH_PX = 580;
|
|
9193
9126
|
const SERP_TITLE_WIDTH_PROFILE = {
|
|
9194
9127
|
id: "arial-20-v1",
|
|
@@ -9319,8 +9252,7 @@ function estimateSerpTitleWidth(title) {
|
|
|
9319
9252
|
fallbackGraphemes: fallbackGraphemes.slice(0, 10)
|
|
9320
9253
|
};
|
|
9321
9254
|
}
|
|
9322
|
-
|
|
9323
|
-
//#region packages/core/src/analyze/audit-page.ts
|
|
9255
|
+
// packages/core/src/analyze/audit-page.ts
|
|
9324
9256
|
const AUDIT_PAGE_RULE_IDS = [
|
|
9325
9257
|
"missing_title",
|
|
9326
9258
|
"title_too_wide",
|
|
@@ -9480,8 +9412,7 @@ async function auditPage(input, dependencies = defaultDependencies$12) {
|
|
|
9480
9412
|
report.recommendations = buildRecommendations(report);
|
|
9481
9413
|
return report;
|
|
9482
9414
|
}
|
|
9483
|
-
|
|
9484
|
-
//#region packages/core/src/analyze/content-coverage-analysis.ts
|
|
9415
|
+
// packages/core/src/analyze/content-coverage-analysis.ts
|
|
9485
9416
|
const STOPWORDS = /* @__PURE__ */ new Set([
|
|
9486
9417
|
"a",
|
|
9487
9418
|
"an",
|
|
@@ -9707,8 +9638,7 @@ async function verifyQueryContentWith(input) {
|
|
|
9707
9638
|
};
|
|
9708
9639
|
}
|
|
9709
9640
|
}
|
|
9710
|
-
|
|
9711
|
-
//#region packages/core/src/analyze/content-coverage.ts
|
|
9641
|
+
// packages/core/src/analyze/content-coverage.ts
|
|
9712
9642
|
function verifyQueryContent(input) {
|
|
9713
9643
|
return verifyQueryContentWith({
|
|
9714
9644
|
...input,
|
|
@@ -9716,8 +9646,7 @@ function verifyQueryContent(input) {
|
|
|
9716
9646
|
extract: input.extract ?? extractPage
|
|
9717
9647
|
});
|
|
9718
9648
|
}
|
|
9719
|
-
|
|
9720
|
-
//#region packages/core/src/brand.ts
|
|
9649
|
+
// packages/core/src/brand.ts
|
|
9721
9650
|
const COUNTRY_CODE_SECOND_LEVELS = /* @__PURE__ */ new Set([
|
|
9722
9651
|
"ac",
|
|
9723
9652
|
"co",
|
|
@@ -9825,8 +9754,7 @@ function shouldExcludeBrandQuery(input) {
|
|
|
9825
9754
|
const terms = input.brandTerms?.length ? input.brandTerms : deriveBrandTerms({ siteUrl: input.siteUrl });
|
|
9826
9755
|
return isBrandQuery(input.query, terms);
|
|
9827
9756
|
}
|
|
9828
|
-
|
|
9829
|
-
//#region packages/core/src/analyze/shared.ts
|
|
9757
|
+
// packages/core/src/analyze/shared.ts
|
|
9830
9758
|
const CTR_BASELINE = {
|
|
9831
9759
|
1: .27,
|
|
9832
9760
|
2: .15,
|
|
@@ -9861,8 +9789,7 @@ function jaccard(a, b) {
|
|
|
9861
9789
|
function defaultDateRange$1(days = 28, now = /* @__PURE__ */ new Date()) {
|
|
9862
9790
|
return finalGscDateRange(days, now);
|
|
9863
9791
|
}
|
|
9864
|
-
|
|
9865
|
-
//#region packages/core/src/analyze/opportunity-primitives.ts
|
|
9792
|
+
// packages/core/src/analyze/opportunity-primitives.ts
|
|
9866
9793
|
const MIN_BENCHMARK_URL_IMPRESSIONS = 30;
|
|
9867
9794
|
const MIN_BENCHMARK_IMPRESSIONS = 1e3;
|
|
9868
9795
|
const MIN_BENCHMARK_URL_SAMPLES = 5;
|
|
@@ -10275,8 +10202,7 @@ function queryOpportunityRecommendation(input) {
|
|
|
10275
10202
|
expectedImpact: `About ${Math.round(input.estimatedClickLift)} extra clicks in the selected GSC window if CTR reaches the benchmark.`
|
|
10276
10203
|
};
|
|
10277
10204
|
}
|
|
10278
|
-
|
|
10279
|
-
//#region packages/core/src/analyze/page-opportunities-evidence.ts
|
|
10205
|
+
// packages/core/src/analyze/page-opportunities-evidence.ts
|
|
10280
10206
|
function verificationFor(input) {
|
|
10281
10207
|
const signals = pageTechnicalSignals(input);
|
|
10282
10208
|
if (signals.length > 0) return {
|
|
@@ -10341,8 +10267,7 @@ function benchmarkDetails$2(benchmark, excludedTargetRows) {
|
|
|
10341
10267
|
excludedTargetRows
|
|
10342
10268
|
};
|
|
10343
10269
|
}
|
|
10344
|
-
|
|
10345
|
-
//#region packages/core/src/analyze/query-quality.ts
|
|
10270
|
+
// packages/core/src/analyze/query-quality.ts
|
|
10346
10271
|
function hasLetter(value) {
|
|
10347
10272
|
return /\p{L}/u.test(value);
|
|
10348
10273
|
}
|
|
@@ -10613,8 +10538,7 @@ function countQueryFlags(items) {
|
|
|
10613
10538
|
flaggedRows
|
|
10614
10539
|
};
|
|
10615
10540
|
}
|
|
10616
|
-
|
|
10617
|
-
//#region packages/core/src/analyze/page-opportunities-selection.ts
|
|
10541
|
+
// packages/core/src/analyze/page-opportunities-selection.ts
|
|
10618
10542
|
const DEFAULT_MIN_IMPRESSIONS$1 = 10;
|
|
10619
10543
|
const DEFAULT_LIMIT$3 = 25;
|
|
10620
10544
|
const MAX_LIMIT$3 = 100;
|
|
@@ -10707,8 +10631,7 @@ function selectBenchmarkRows$1(input) {
|
|
|
10707
10631
|
});
|
|
10708
10632
|
});
|
|
10709
10633
|
}
|
|
10710
|
-
|
|
10711
|
-
//#region packages/core/src/analyze/page-opportunities-analysis.ts
|
|
10634
|
+
// packages/core/src/analyze/page-opportunities-analysis.ts
|
|
10712
10635
|
function compareText$18(left, right) {
|
|
10713
10636
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
10714
10637
|
}
|
|
@@ -10807,8 +10730,7 @@ function analyzePageOpportunitiesFromRows(input) {
|
|
|
10807
10730
|
}
|
|
10808
10731
|
};
|
|
10809
10732
|
}
|
|
10810
|
-
|
|
10811
|
-
//#region packages/core/src/analyze/page-opportunities-presentation.ts
|
|
10733
|
+
// packages/core/src/analyze/page-opportunities-presentation.ts
|
|
10812
10734
|
const actionPriority = new Map([
|
|
10813
10735
|
"technical-check",
|
|
10814
10736
|
"content-gap",
|
|
@@ -10872,8 +10794,7 @@ function pageOpportunityRecommendations(analysis) {
|
|
|
10872
10794
|
if (gaps >= 3) recommendations.push(`Group the ${gaps} verified content gaps into useful sections rather than adding one thin paragraph per query.`);
|
|
10873
10795
|
return recommendations;
|
|
10874
10796
|
}
|
|
10875
|
-
|
|
10876
|
-
//#region packages/core/src/analyze/page-opportunities.ts
|
|
10797
|
+
// packages/core/src/analyze/page-opportunities.ts
|
|
10877
10798
|
const MAX_BENCHMARK_ROWS = 1e5;
|
|
10878
10799
|
const defaultDependencies$11 = {
|
|
10879
10800
|
searchAnalytics: querySearchAnalytics,
|
|
@@ -11074,8 +10995,7 @@ async function pageOpportunitiesReport(input, dependencies = defaultDependencies
|
|
|
11074
10995
|
recommendations: pageOpportunityRecommendations(analysis)
|
|
11075
10996
|
};
|
|
11076
10997
|
}
|
|
11077
|
-
|
|
11078
|
-
//#region packages/core/src/analyze/content-optimization.ts
|
|
10998
|
+
// packages/core/src/analyze/content-optimization.ts
|
|
11079
10999
|
function intentFor(query) {
|
|
11080
11000
|
const value = query.toLowerCase();
|
|
11081
11001
|
if (/\b(vs|versus|alternative|alternatives|compare|comparison|best)\b/.test(value)) return "comparison";
|
|
@@ -11223,8 +11143,7 @@ function contentOptimizationFromPageOpportunities(sourceReport) {
|
|
|
11223
11143
|
async function contentOptimizationReport(input) {
|
|
11224
11144
|
return contentOptimizationFromPageOpportunities(await pageOpportunitiesReport(input));
|
|
11225
11145
|
}
|
|
11226
|
-
|
|
11227
|
-
//#region packages/core/src/analyze/crawler/page-eligibility.ts
|
|
11146
|
+
// packages/core/src/analyze/crawler/page-eligibility.ts
|
|
11228
11147
|
function comparableUrl(value) {
|
|
11229
11148
|
if (!value) return void 0;
|
|
11230
11149
|
try {
|
|
@@ -11251,8 +11170,7 @@ function isAuditableHtmlPage(page) {
|
|
|
11251
11170
|
const contentAuditAllowed = page.contentAuditAllowed ?? (!page.blocked && page.robotsTxt?.allowed !== false);
|
|
11252
11171
|
return isSuccessfulResponse(page) && isHtmlPage(page) && !isRedirectedPage(page) && contentAuditAllowed;
|
|
11253
11172
|
}
|
|
11254
|
-
|
|
11255
|
-
//#region packages/core/src/analyze/crawler/audit.ts
|
|
11173
|
+
// packages/core/src/analyze/crawler/audit.ts
|
|
11256
11174
|
function sameUrl(a, b) {
|
|
11257
11175
|
if (!a || !b) return false;
|
|
11258
11176
|
try {
|
|
@@ -11726,8 +11644,7 @@ function auditCrawlPages(pages, opts = {}) {
|
|
|
11726
11644
|
}
|
|
11727
11645
|
return issues;
|
|
11728
11646
|
}
|
|
11729
|
-
|
|
11730
|
-
//#region packages/core/src/analyze/crawler/crawl-skip-reasons.ts
|
|
11647
|
+
// packages/core/src/analyze/crawler/crawl-skip-reasons.ts
|
|
11731
11648
|
const CRAWL_SKIP_REASONS = [
|
|
11732
11649
|
"asset-url",
|
|
11733
11650
|
"configured-exclusion",
|
|
@@ -11802,8 +11719,7 @@ function crawlSkipReasonCountsFromRecord(input = {}) {
|
|
|
11802
11719
|
count
|
|
11803
11720
|
})) }).skipReasons;
|
|
11804
11721
|
}
|
|
11805
|
-
|
|
11806
|
-
//#region packages/core/src/export/diagnosis-segment-csv.ts
|
|
11722
|
+
// packages/core/src/export/diagnosis-segment-csv.ts
|
|
11807
11723
|
const dimensions = [
|
|
11808
11724
|
"page",
|
|
11809
11725
|
"query",
|
|
@@ -11904,8 +11820,7 @@ function diagnosisSegmentCsvFiles(report) {
|
|
|
11904
11820
|
}))
|
|
11905
11821
|
];
|
|
11906
11822
|
}
|
|
11907
|
-
|
|
11908
|
-
//#region packages/core/src/export/csv.ts
|
|
11823
|
+
// packages/core/src/export/csv.ts
|
|
11909
11824
|
const CSV_SCHEMAS = {
|
|
11910
11825
|
"priorities.csv": [
|
|
11911
11826
|
"rank",
|
|
@@ -12370,8 +12285,7 @@ function refreshPrioritiesCsvFiles(report) {
|
|
|
12370
12285
|
...prefixCsvFiles("diagnosis", diagnoseCsvFiles(report.output.diagnosis))
|
|
12371
12286
|
]);
|
|
12372
12287
|
}
|
|
12373
|
-
|
|
12374
|
-
//#region packages/core/src/analyze/crawler/top-fixes.ts
|
|
12288
|
+
// packages/core/src/analyze/crawler/top-fixes.ts
|
|
12375
12289
|
const SEVERITY_SCORE = {
|
|
12376
12290
|
high: 5e3,
|
|
12377
12291
|
medium: 1200,
|
|
@@ -12522,8 +12436,7 @@ function reviewObservations(report, filters = {}) {
|
|
|
12522
12436
|
recommendation: "review"
|
|
12523
12437
|
});
|
|
12524
12438
|
}
|
|
12525
|
-
|
|
12526
|
-
//#region packages/core/src/analyze/crawler/export.ts
|
|
12439
|
+
// packages/core/src/analyze/crawler/export.ts
|
|
12527
12440
|
const issueHeaders = [
|
|
12528
12441
|
"rule_id",
|
|
12529
12442
|
"title",
|
|
@@ -12835,8 +12748,7 @@ function escapeHtml(value) {
|
|
|
12835
12748
|
function escapeXml(value) {
|
|
12836
12749
|
return escapeHtml(value);
|
|
12837
12750
|
}
|
|
12838
|
-
|
|
12839
|
-
//#region packages/core/src/analyze/crawler/report.ts
|
|
12751
|
+
// packages/core/src/analyze/crawler/report.ts
|
|
12840
12752
|
const MAX_CRAWL_PAGES = 1e4;
|
|
12841
12753
|
const MAX_CRAWL_DEPTH = 64;
|
|
12842
12754
|
const MAX_CRAWL_CONCURRENCY = 16;
|
|
@@ -13275,8 +13187,7 @@ function normalizeLoadedCrawlReport(report) {
|
|
|
13275
13187
|
caveats: sanitizeMessages(report.caveats).sort(compareText$17)
|
|
13276
13188
|
};
|
|
13277
13189
|
}
|
|
13278
|
-
|
|
13279
|
-
//#region packages/core/src/analyze/crawler/site-checks-schema.ts
|
|
13190
|
+
// packages/core/src/analyze/crawler/site-checks-schema.ts
|
|
13280
13191
|
const crawlSiteChecksSchema = z.object({ soft404: z.object({
|
|
13281
13192
|
status: z.enum([
|
|
13282
13193
|
"pass",
|
|
@@ -13294,8 +13205,7 @@ const crawlSiteChecksSchema = z.object({ soft404: z.object({
|
|
|
13294
13205
|
probeLimit: z.number().int().positive(),
|
|
13295
13206
|
complete: z.boolean()
|
|
13296
13207
|
}) });
|
|
13297
|
-
|
|
13298
|
-
//#region packages/core/src/analyze/crawler/site-crawl-ai-schema.ts
|
|
13208
|
+
// packages/core/src/analyze/crawler/site-crawl-ai-schema.ts
|
|
13299
13209
|
const crawlAiAvailabilitySchema = z.enum([
|
|
13300
13210
|
"available",
|
|
13301
13211
|
"absent",
|
|
@@ -13380,8 +13290,7 @@ const crawlAiSignalsSchema = z.object({
|
|
|
13380
13290
|
contentSignal: z.string().optional()
|
|
13381
13291
|
}).optional()
|
|
13382
13292
|
});
|
|
13383
|
-
|
|
13384
|
-
//#region packages/core/src/analyze/crawler/schemas.ts
|
|
13293
|
+
// packages/core/src/analyze/crawler/schemas.ts
|
|
13385
13294
|
const crawlerRuleSeveritySchema = z.enum([
|
|
13386
13295
|
"low",
|
|
13387
13296
|
"medium",
|
|
@@ -14339,8 +14248,7 @@ const crawlerJsonSchemas = {
|
|
|
14339
14248
|
requestObservation: z.toJSONSchema(crawlRequestObservationSchema),
|
|
14340
14249
|
pageSnapshot: z.toJSONSchema(crawlPageSnapshotSchema)
|
|
14341
14250
|
};
|
|
14342
|
-
|
|
14343
|
-
//#region packages/core/src/analyze/crawler/agent-ready.ts
|
|
14251
|
+
// packages/core/src/analyze/crawler/agent-ready.ts
|
|
14344
14252
|
function needsSignIn(page) {
|
|
14345
14253
|
return page.status === 401 || page.status === 403 || Boolean(page.softAuthenticationGate);
|
|
14346
14254
|
}
|
|
@@ -14391,8 +14299,7 @@ function agentReadyIssues(input) {
|
|
|
14391
14299
|
}
|
|
14392
14300
|
return issues;
|
|
14393
14301
|
}
|
|
14394
|
-
|
|
14395
|
-
//#region packages/core/src/analyze/crawler/crawl-caveats.ts
|
|
14302
|
+
// packages/core/src/analyze/crawler/crawl-caveats.ts
|
|
14396
14303
|
function crawlCaveats(input) {
|
|
14397
14304
|
const caveats = input.cancelled ? ["Crawl cancelled before all queued URLs finished."] : input.pageLimitReached ? [`Stopped after reaching maxPages (${input.maxPages}).`] : [];
|
|
14398
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.`);
|
|
@@ -14400,8 +14307,7 @@ function crawlCaveats(input) {
|
|
|
14400
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.`);
|
|
14401
14308
|
return caveats;
|
|
14402
14309
|
}
|
|
14403
|
-
|
|
14404
|
-
//#region packages/core/src/analyze/crawler/crawl-control.ts
|
|
14310
|
+
// packages/core/src/analyze/crawler/crawl-control.ts
|
|
14405
14311
|
const CRAWL_CANCELLED = Symbol("crawl-cancelled");
|
|
14406
14312
|
function abortController(input) {
|
|
14407
14313
|
const controller = new AbortController();
|
|
@@ -14423,8 +14329,7 @@ function cancellationRace(signal) {
|
|
|
14423
14329
|
signal.addEventListener("abort", () => resolve(CRAWL_CANCELLED), { once: true });
|
|
14424
14330
|
});
|
|
14425
14331
|
}
|
|
14426
|
-
|
|
14427
|
-
//#region packages/core/src/analyze/crawler/crawl-memory.ts
|
|
14332
|
+
// packages/core/src/analyze/crawler/crawl-memory.ts
|
|
14428
14333
|
const MEBIBYTE$1 = 1048576;
|
|
14429
14334
|
const MIN_CRAWL_MEMORY_LIMIT = 384 * MEBIBYTE$1;
|
|
14430
14335
|
const MAX_CRAWL_MEMORY_LIMIT = 640 * MEBIBYTE$1;
|
|
@@ -14443,8 +14348,7 @@ function crawlRssLimitBytes(totalMemoryBytes) {
|
|
|
14443
14348
|
function crawlMemoryPressure(input) {
|
|
14444
14349
|
return input.memoryUsage.heapUsed + input.memoryUsage.external >= crawlMemoryLimitBytes(input.totalMemoryBytes) || input.memoryUsage.rss >= crawlRssLimitBytes(input.totalMemoryBytes);
|
|
14445
14350
|
}
|
|
14446
|
-
|
|
14447
|
-
//#region packages/core/src/analyze/crawler/crawl-url-queue.ts
|
|
14351
|
+
// packages/core/src/analyze/crawler/crawl-url-queue.ts
|
|
14448
14352
|
function before(left, right) {
|
|
14449
14353
|
return left.depth < right.depth || left.depth === right.depth && left.url < right.url;
|
|
14450
14354
|
}
|
|
@@ -14523,8 +14427,7 @@ var CrawlUrlQueue = class {
|
|
|
14523
14427
|
}
|
|
14524
14428
|
}
|
|
14525
14429
|
};
|
|
14526
|
-
|
|
14527
|
-
//#region packages/core/src/analyze/crawler/external-link-checks.ts
|
|
14430
|
+
// packages/core/src/analyze/crawler/external-link-checks.ts
|
|
14528
14431
|
const EXTERNAL_LINK_SELECTION_LIMIT = 200;
|
|
14529
14432
|
const EXTERNAL_LINK_STATES = [
|
|
14530
14433
|
"available",
|
|
@@ -14712,8 +14615,7 @@ async function verifyExternalLinks(input) {
|
|
|
14712
14615
|
warnings
|
|
14713
14616
|
};
|
|
14714
14617
|
}
|
|
14715
|
-
|
|
14716
|
-
//#region packages/core/src/analyze/crawler/link-graph.ts
|
|
14618
|
+
// packages/core/src/analyze/crawler/link-graph.ts
|
|
14717
14619
|
function resolveFetchedAlias(url, aliases) {
|
|
14718
14620
|
const visited = /* @__PURE__ */ new Set();
|
|
14719
14621
|
let current = url;
|
|
@@ -14733,8 +14635,7 @@ function resolveLinkCountAliases(counts, aliases) {
|
|
|
14733
14635
|
}
|
|
14734
14636
|
return resolved;
|
|
14735
14637
|
}
|
|
14736
|
-
|
|
14737
|
-
//#region packages/core/src/analyze/crawler/request-evidence.ts
|
|
14638
|
+
// packages/core/src/analyze/crawler/request-evidence.ts
|
|
14738
14639
|
function observationFromPage(requestedUrl, page) {
|
|
14739
14640
|
const observation = {
|
|
14740
14641
|
requestedUrl,
|
|
@@ -14757,8 +14658,7 @@ function observationFromPage(requestedUrl, page) {
|
|
|
14757
14658
|
extraction: page.extractionStatus ?? "complete"
|
|
14758
14659
|
};
|
|
14759
14660
|
}
|
|
14760
|
-
|
|
14761
|
-
//#region packages/core/src/analyze/crawler/site-checks.ts
|
|
14661
|
+
// packages/core/src/analyze/crawler/site-checks.ts
|
|
14762
14662
|
const PROBE_COUNT = 2;
|
|
14763
14663
|
function probeUrls(startUrl) {
|
|
14764
14664
|
const origin = new URL(startUrl).origin;
|
|
@@ -14826,8 +14726,7 @@ async function collectCrawlSiteChecks(input) {
|
|
|
14826
14726
|
}]
|
|
14827
14727
|
};
|
|
14828
14728
|
}
|
|
14829
|
-
|
|
14830
|
-
//#region packages/core/src/analyze/crawler/site-crawl-ai-signals.ts
|
|
14729
|
+
// packages/core/src/analyze/crawler/site-crawl-ai-signals.ts
|
|
14831
14730
|
const MAX_AUXILIARY_RESPONSE_BYTES = 2097152;
|
|
14832
14731
|
const LLMS_LINK_LIMIT = 20;
|
|
14833
14732
|
const AI_BOTS = [
|
|
@@ -14929,7 +14828,7 @@ function llmsLinks(text, base) {
|
|
|
14929
14828
|
} catch {}
|
|
14930
14829
|
return links;
|
|
14931
14830
|
}
|
|
14932
|
-
async function checkLlmsTxt(input) {
|
|
14831
|
+
async function checkLlmsTxt$1(input) {
|
|
14933
14832
|
const result = await get$1(input, new URL("/llms.txt", input.url).toString());
|
|
14934
14833
|
const availability = result.availability === "available" && isHtml(result.contentType) ? "absent" : result.availability;
|
|
14935
14834
|
const exists = availability === "available";
|
|
@@ -15057,7 +14956,7 @@ async function collectCrawlAiSignals(input) {
|
|
|
15057
14956
|
const allows = input.respectRobots ? robots.allows : () => true;
|
|
15058
14957
|
const startAllowed = allows(input.url);
|
|
15059
14958
|
const [llmsTxt, agentResources, markdownNegotiation] = await Promise.all([
|
|
15060
|
-
checkLlmsTxt(input),
|
|
14959
|
+
checkLlmsTxt$1(input),
|
|
15061
14960
|
checkAgentResources(input),
|
|
15062
14961
|
startAllowed ? checkMarkdownNegotiation(input) : Promise.resolve({
|
|
15063
14962
|
url: input.url,
|
|
@@ -15121,8 +15020,7 @@ function linkRelations(header) {
|
|
|
15121
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);
|
|
15122
15021
|
return [...relations].sort();
|
|
15123
15022
|
}
|
|
15124
|
-
|
|
15125
|
-
//#region packages/core/src/analyze/crawler/site-crawl-providers.ts
|
|
15023
|
+
// packages/core/src/analyze/crawler/site-crawl-providers.ts
|
|
15126
15024
|
function crawlMetricsWindow(now) {
|
|
15127
15025
|
return {
|
|
15128
15026
|
...defaultDateRange$2(28, now),
|
|
@@ -15318,8 +15216,7 @@ async function joinSearchMetrics(input) {
|
|
|
15318
15216
|
...warning ? { warning } : {}
|
|
15319
15217
|
};
|
|
15320
15218
|
}
|
|
15321
|
-
|
|
15322
|
-
//#region packages/core/src/analyze/crawler/site-crawl-data-sources.ts
|
|
15219
|
+
// packages/core/src/analyze/crawler/site-crawl-data-sources.ts
|
|
15323
15220
|
function crawlProviderLimits(input) {
|
|
15324
15221
|
const normalize = (value, fallback, label) => {
|
|
15325
15222
|
const limit = value ?? fallback;
|
|
@@ -15389,8 +15286,7 @@ async function crawlDataSources(input) {
|
|
|
15389
15286
|
});
|
|
15390
15287
|
return dataSources;
|
|
15391
15288
|
}
|
|
15392
|
-
|
|
15393
|
-
//#region packages/core/src/analyze/monitoring/crawl-page.ts
|
|
15289
|
+
// packages/core/src/analyze/monitoring/crawl-page.ts
|
|
15394
15290
|
function resolvedUrl(href, base) {
|
|
15395
15291
|
if (!href) return void 0;
|
|
15396
15292
|
try {
|
|
@@ -15584,6 +15480,9 @@ async function crawlOne(url, opts = {}) {
|
|
|
15584
15480
|
warning: `${url}: ${message}`
|
|
15585
15481
|
};
|
|
15586
15482
|
}
|
|
15483
|
+
return snapshotFetchedPage(url, fetched);
|
|
15484
|
+
}
|
|
15485
|
+
async function snapshotFetchedPage(url, fetched) {
|
|
15587
15486
|
const contentType = headerValue(fetched.headers, "content-type");
|
|
15588
15487
|
if (fetched.diagnostics.accessBlock) {
|
|
15589
15488
|
const page = unextractedPage(fetched, contentType);
|
|
@@ -15786,8 +15685,7 @@ async function crawlStatusOnly(url, opts = {}) {
|
|
|
15786
15685
|
};
|
|
15787
15686
|
}
|
|
15788
15687
|
}
|
|
15789
|
-
|
|
15790
|
-
//#region packages/core/src/analyze/monitoring/sitemaps.ts
|
|
15688
|
+
// packages/core/src/analyze/monitoring/sitemaps.ts
|
|
15791
15689
|
const INVALID_LOC_SAMPLE_LIMIT = 10;
|
|
15792
15690
|
const LASTMOD_SAMPLE_LIMIT = 10;
|
|
15793
15691
|
const MAX_SITEMAP_BYTES = 52428800;
|
|
@@ -16198,8 +16096,7 @@ async function fetchSitemapUrls(input) {
|
|
|
16198
16096
|
warnings
|
|
16199
16097
|
};
|
|
16200
16098
|
}
|
|
16201
|
-
|
|
16202
|
-
//#region packages/core/src/providers/cache.ts
|
|
16099
|
+
// packages/core/src/providers/cache.ts
|
|
16203
16100
|
function stableValue(value) {
|
|
16204
16101
|
if (Array.isArray(value)) return value.map(stableValue);
|
|
16205
16102
|
if (!value || typeof value !== "object") return value;
|
|
@@ -16285,8 +16182,7 @@ function writeProviderCache(key, input, options = {}) {
|
|
|
16285
16182
|
taskIds: normalizedTaskIds
|
|
16286
16183
|
};
|
|
16287
16184
|
}
|
|
16288
|
-
|
|
16289
|
-
//#region packages/core/src/storage/provider-secrets.ts
|
|
16185
|
+
// packages/core/src/storage/provider-secrets.ts
|
|
16290
16186
|
const KEYRING_SERVICE = "seo";
|
|
16291
16187
|
const PRIVATE_FILE_MODE$1 = 384;
|
|
16292
16188
|
const PROVIDER_SECRET_NAMES = {
|
|
@@ -16316,7 +16212,7 @@ function tightenFilePermissions(path) {
|
|
|
16316
16212
|
if (error.code !== "ENOENT") throw error;
|
|
16317
16213
|
}
|
|
16318
16214
|
}
|
|
16319
|
-
function readFile$
|
|
16215
|
+
function readFile$2() {
|
|
16320
16216
|
const path = getSeoCliPaths().providerSecretsFile;
|
|
16321
16217
|
tightenFilePermissions(path);
|
|
16322
16218
|
const raw = readJsonFile(path);
|
|
@@ -16337,7 +16233,7 @@ function writeFile$1(file) {
|
|
|
16337
16233
|
writeJsonAtomic(path, file, PRIVATE_FILE_MODE$1);
|
|
16338
16234
|
}
|
|
16339
16235
|
function deleteFileSecret(name) {
|
|
16340
|
-
const file = readFile$
|
|
16236
|
+
const file = readFile$2();
|
|
16341
16237
|
if (!(name in file.secrets)) return;
|
|
16342
16238
|
delete file.secrets[name];
|
|
16343
16239
|
writeFile$1(file);
|
|
@@ -16356,7 +16252,7 @@ async function readProviderSecret(input) {
|
|
|
16356
16252
|
source: "keychain"
|
|
16357
16253
|
};
|
|
16358
16254
|
} catch {}
|
|
16359
|
-
const value = readFile$
|
|
16255
|
+
const value = readFile$2().secrets[input.name];
|
|
16360
16256
|
return value ? {
|
|
16361
16257
|
value,
|
|
16362
16258
|
source: "file"
|
|
@@ -16370,7 +16266,7 @@ async function writeProviderSecret(name, value) {
|
|
|
16370
16266
|
deleteFileSecret(name);
|
|
16371
16267
|
return "keychain";
|
|
16372
16268
|
} catch {}
|
|
16373
|
-
const file = readFile$
|
|
16269
|
+
const file = readFile$2();
|
|
16374
16270
|
file.secrets[name] = normalized;
|
|
16375
16271
|
writeFile$1(file);
|
|
16376
16272
|
return "file";
|
|
@@ -16391,8 +16287,7 @@ async function deleteManagedProviderSecrets() {
|
|
|
16391
16287
|
const failed = results.flatMap((result, index) => result.status === "rejected" ? [MANAGED_PROVIDER_SECRET_NAMES[index]] : []);
|
|
16392
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] : [])) });
|
|
16393
16289
|
}
|
|
16394
|
-
|
|
16395
|
-
//#region packages/core/src/clicky/credentials.ts
|
|
16290
|
+
// packages/core/src/clicky/credentials.ts
|
|
16396
16291
|
const CLICKY_SITEKEY_ENV = "SEO_CLICKY_SITEKEY";
|
|
16397
16292
|
const CLICKY_SITE_KEYS_SECRET = PROVIDER_SECRET_NAMES.clickySiteKeys;
|
|
16398
16293
|
const storedSiteKeysSchema = z.object({
|
|
@@ -16478,8 +16373,7 @@ async function deleteClickySiteKey(siteIdValue) {
|
|
|
16478
16373
|
siteKeys: stored.siteKeys
|
|
16479
16374
|
}));
|
|
16480
16375
|
}
|
|
16481
|
-
|
|
16482
|
-
//#region packages/core/src/provider-extensions/contracts.ts
|
|
16376
|
+
// packages/core/src/provider-extensions/contracts.ts
|
|
16483
16377
|
const identifierPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
|
|
16484
16378
|
const packageNamePattern = /^(?:@[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*|[a-z0-9][a-z0-9._-]*)$/u;
|
|
16485
16379
|
const exactVersionPattern = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u;
|
|
@@ -16630,8 +16524,7 @@ const providerRegistrationSchema = z.object({
|
|
|
16630
16524
|
actionIds.add(action.id);
|
|
16631
16525
|
}
|
|
16632
16526
|
});
|
|
16633
|
-
|
|
16634
|
-
//#region packages/core/src/provider-extensions/credentials.ts
|
|
16527
|
+
// packages/core/src/provider-extensions/credentials.ts
|
|
16635
16528
|
const storedCredentialsSchema = z.object({
|
|
16636
16529
|
version: z.literal(1),
|
|
16637
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))))
|
|
@@ -16701,8 +16594,7 @@ async function deleteProviderExtensionCredentials(input) {
|
|
|
16701
16594
|
}
|
|
16702
16595
|
await writeProviderSecret(PROVIDER_SECRET_NAMES.providerExtensionCredentials, JSON.stringify(stored));
|
|
16703
16596
|
}
|
|
16704
|
-
|
|
16705
|
-
//#region packages/core/src/providers/transport.ts
|
|
16597
|
+
// packages/core/src/providers/transport.ts
|
|
16706
16598
|
function schemaIssueSummary(error) {
|
|
16707
16599
|
const issues = error.issues.slice(0, 3).map((issue) => {
|
|
16708
16600
|
return `${issue.path.flatMap((segment) => {
|
|
@@ -16819,8 +16711,7 @@ async function providerRequestJson(input) {
|
|
|
16819
16711
|
}
|
|
16820
16712
|
return parsed.data;
|
|
16821
16713
|
}
|
|
16822
|
-
|
|
16823
|
-
//#region packages/core/src/provider-extensions/registry.ts
|
|
16714
|
+
// packages/core/src/provider-extensions/registry.ts
|
|
16824
16715
|
var ProviderExtensionRegistry = class {
|
|
16825
16716
|
#providers = /* @__PURE__ */ new Map();
|
|
16826
16717
|
hostFor(input) {
|
|
@@ -16853,8 +16744,7 @@ var ProviderExtensionRegistry = class {
|
|
|
16853
16744
|
return [...this.#providers.values()].sort((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0);
|
|
16854
16745
|
}
|
|
16855
16746
|
};
|
|
16856
|
-
|
|
16857
|
-
//#region packages/core/src/provider-extensions/store.ts
|
|
16747
|
+
// packages/core/src/provider-extensions/store.ts
|
|
16858
16748
|
const PRIVATE_FILE_MODE = 384;
|
|
16859
16749
|
function emptyStore() {
|
|
16860
16750
|
return {
|
|
@@ -16905,8 +16795,7 @@ function removeInstalledProviderPackage(id) {
|
|
|
16905
16795
|
});
|
|
16906
16796
|
return removed;
|
|
16907
16797
|
}
|
|
16908
|
-
|
|
16909
|
-
//#region packages/core/src/provider-extensions/loader.ts
|
|
16798
|
+
// packages/core/src/provider-extensions/loader.ts
|
|
16910
16799
|
const installedPackageJsonSchema = z.object({
|
|
16911
16800
|
name: z.string(),
|
|
16912
16801
|
version: z.string(),
|
|
@@ -16985,8 +16874,7 @@ async function loadInstalledProviderExtensions(options = {}) {
|
|
|
16985
16874
|
failures
|
|
16986
16875
|
};
|
|
16987
16876
|
}
|
|
16988
|
-
|
|
16989
|
-
//#region packages/core/src/provider-extensions/runtime.ts
|
|
16877
|
+
// packages/core/src/provider-extensions/runtime.ts
|
|
16990
16878
|
const DEFAULT_MAX_REQUESTS = 8;
|
|
16991
16879
|
const MAX_RESPONSE_BYTES$1 = 10485760;
|
|
16992
16880
|
const MAX_REQUEST_BODY_BYTES = 65536;
|
|
@@ -17057,8 +16945,7 @@ async function verifyProviderExtension(input) {
|
|
|
17057
16945
|
throw safeProviderAdapterError(input.providerId, error, input.credentials);
|
|
17058
16946
|
}
|
|
17059
16947
|
}
|
|
17060
|
-
|
|
17061
|
-
//#region packages/core/src/provider-extensions/analytics.ts
|
|
16948
|
+
// packages/core/src/provider-extensions/analytics.ts
|
|
17062
16949
|
const CACHE_TTL_MS$1 = 864e5;
|
|
17063
16950
|
const landingPageResultSchema = z.object({
|
|
17064
16951
|
metric: z.literal("landing-page-visits"),
|
|
@@ -17139,8 +17026,7 @@ async function runAnalyticsProviderLandingPages(input) {
|
|
|
17139
17026
|
});
|
|
17140
17027
|
return parsed;
|
|
17141
17028
|
}
|
|
17142
|
-
|
|
17143
|
-
//#region packages/core/src/analyze/workflows/analytics-value.ts
|
|
17029
|
+
// packages/core/src/analyze/workflows/analytics-value.ts
|
|
17144
17030
|
const defaultDependencies$10 = {
|
|
17145
17031
|
runGa4Report,
|
|
17146
17032
|
analyticsLandingPages: runAnalyticsProviderLandingPages
|
|
@@ -17367,8 +17253,7 @@ async function fetchLandingPageValues(input, dependencies = defaultDependencies$
|
|
|
17367
17253
|
}
|
|
17368
17254
|
}[connection.provider](connection);
|
|
17369
17255
|
}
|
|
17370
|
-
|
|
17371
|
-
//#region packages/core/src/analyze/crawler/site-crawl-dependencies.ts
|
|
17256
|
+
// packages/core/src/analyze/crawler/site-crawl-dependencies.ts
|
|
17372
17257
|
function resolveCrawlSiteDependencies(dependencies = {}) {
|
|
17373
17258
|
const hasInjectedPerPageSearchProvider = Boolean(dependencies.queryPageMetrics || dependencies.queryPageTopQuery);
|
|
17374
17259
|
const hasInjectedLegacyBulkSearchProvider = Boolean(dependencies.queryPagesMetrics || dependencies.queryPagesTopQueries);
|
|
@@ -17393,8 +17278,7 @@ function resolveCrawlSiteDependencies(dependencies = {}) {
|
|
|
17393
17278
|
totalMemory: dependencies.totalMemory ?? totalmem
|
|
17394
17279
|
};
|
|
17395
17280
|
}
|
|
17396
|
-
|
|
17397
|
-
//#region packages/core/src/analyze/crawler/sitemap-discovery.ts
|
|
17281
|
+
// packages/core/src/analyze/crawler/sitemap-discovery.ts
|
|
17398
17282
|
const MAX_SITEMAP_ROOTS = 20;
|
|
17399
17283
|
function isSameOrigin(url, origin) {
|
|
17400
17284
|
try {
|
|
@@ -17482,8 +17366,7 @@ async function sitemapSeeds(input) {
|
|
|
17482
17366
|
}
|
|
17483
17367
|
};
|
|
17484
17368
|
}
|
|
17485
|
-
|
|
17486
|
-
//#region packages/core/src/analyze/crawler/site-crawl.ts
|
|
17369
|
+
// packages/core/src/analyze/crawler/site-crawl.ts
|
|
17487
17370
|
function normalizeUrl$1(value, base) {
|
|
17488
17371
|
try {
|
|
17489
17372
|
const url = base ? new URL(value, base) : new URL(value);
|
|
@@ -18087,8 +17970,7 @@ async function crawlSite(input, dependencies) {
|
|
|
18087
17970
|
await renderer?.close();
|
|
18088
17971
|
}
|
|
18089
17972
|
}
|
|
18090
|
-
|
|
18091
|
-
//#region packages/core/src/brand-evidence.ts
|
|
17973
|
+
// packages/core/src/brand-evidence.ts
|
|
18092
17974
|
const NAVIGATIONAL_STEM_THRESHOLDS = {
|
|
18093
17975
|
maximumPosition: 2,
|
|
18094
17976
|
minimumCtr: .2,
|
|
@@ -18220,8 +18102,7 @@ function resolveReportBrandTerms(input) {
|
|
|
18220
18102
|
heuristic: true
|
|
18221
18103
|
};
|
|
18222
18104
|
}
|
|
18223
|
-
|
|
18224
|
-
//#region packages/core/src/analyze/min-impressions-policy.ts
|
|
18105
|
+
// packages/core/src/analyze/min-impressions-policy.ts
|
|
18225
18106
|
const POSITION_MIN_IMPRESSIONS_SCALE = {
|
|
18226
18107
|
share: 2e-4,
|
|
18227
18108
|
floor: 10,
|
|
@@ -18273,8 +18154,7 @@ function minImpressionsCaveat(input) {
|
|
|
18273
18154
|
const oneIn = Math.round(1 / policy.scale.share).toLocaleString("en-US");
|
|
18274
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.`;
|
|
18275
18156
|
}
|
|
18276
|
-
|
|
18277
|
-
//#region packages/core/src/analyze/page-fragments.ts
|
|
18157
|
+
// packages/core/src/analyze/page-fragments.ts
|
|
18278
18158
|
function compareCodepoints(left, right) {
|
|
18279
18159
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
18280
18160
|
}
|
|
@@ -18350,8 +18230,7 @@ function collapseFragmentPageRows(rows, options = {}) {
|
|
|
18350
18230
|
}
|
|
18351
18231
|
};
|
|
18352
18232
|
}
|
|
18353
|
-
|
|
18354
|
-
//#region packages/core/src/analyze/site-ctr-curve.ts
|
|
18233
|
+
// packages/core/src/analyze/site-ctr-curve.ts
|
|
18355
18234
|
const SITE_CTR_CURVE_FALLBACK_SOURCE = "builtin_position_ctr_curve_v1";
|
|
18356
18235
|
const SITE_CTR_CURVE_METHOD = {
|
|
18357
18236
|
method: "position_bucket_url_p75_v2",
|
|
@@ -18480,8 +18359,7 @@ function createSiteCtrCurve(rows) {
|
|
|
18480
18359
|
fallbackSource: SITE_CTR_CURVE_FALLBACK_SOURCE
|
|
18481
18360
|
});
|
|
18482
18361
|
}
|
|
18483
|
-
|
|
18484
|
-
//#region packages/core/src/analyze/ctr-underperformers-analysis.ts
|
|
18362
|
+
// packages/core/src/analyze/ctr-underperformers-analysis.ts
|
|
18485
18363
|
const CTR_MAX_MIN_IMPRESSIONS = 1e9;
|
|
18486
18364
|
const CTR_DEFAULT_LIMIT = 25;
|
|
18487
18365
|
const CTR_MAX_LIMIT = 100;
|
|
@@ -18619,8 +18497,7 @@ function analyzeCtrUnderperformersFromRows(input) {
|
|
|
18619
18497
|
benchmarkByPosition: benchmarkContext.byPosition
|
|
18620
18498
|
};
|
|
18621
18499
|
}
|
|
18622
|
-
|
|
18623
|
-
//#region packages/core/src/analyze/ctr-underperformers-report.ts
|
|
18500
|
+
// packages/core/src/analyze/ctr-underperformers-report.ts
|
|
18624
18501
|
const MAX_GSC_ROWS$3 = 1e5;
|
|
18625
18502
|
const DEFAULT_DAYS$3 = 28;
|
|
18626
18503
|
const MAX_DAYS$2 = 548;
|
|
@@ -18754,8 +18631,7 @@ async function ctrUnderperformersReportWith(input, dependencies) {
|
|
|
18754
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."]
|
|
18755
18632
|
};
|
|
18756
18633
|
}
|
|
18757
|
-
|
|
18758
|
-
//#region packages/core/src/analyze/ctr-underperformers.ts
|
|
18634
|
+
// packages/core/src/analyze/ctr-underperformers.ts
|
|
18759
18635
|
const defaultDependencies$9 = {
|
|
18760
18636
|
searchAnalytics: querySearchAnalytics,
|
|
18761
18637
|
now: () => /* @__PURE__ */ new Date()
|
|
@@ -18763,16 +18639,14 @@ const defaultDependencies$9 = {
|
|
|
18763
18639
|
function ctrUnderperformersReport(input, dependencies = defaultDependencies$9) {
|
|
18764
18640
|
return ctrUnderperformersReportWith(input, dependencies);
|
|
18765
18641
|
}
|
|
18766
|
-
|
|
18767
|
-
//#region packages/core/src/phrasing.ts
|
|
18642
|
+
// packages/core/src/phrasing.ts
|
|
18768
18643
|
function plural(count, singular, pluralLabel = `${singular}s`) {
|
|
18769
18644
|
return count === 1 ? singular : pluralLabel;
|
|
18770
18645
|
}
|
|
18771
18646
|
function countLabel(count, singular, pluralLabel = `${singular}s`) {
|
|
18772
18647
|
return `${count.toLocaleString("en-GB")} ${plural(count, singular, pluralLabel)}`;
|
|
18773
18648
|
}
|
|
18774
|
-
|
|
18775
|
-
//#region packages/core/src/analyze/diagnosis-priorities.ts
|
|
18649
|
+
// packages/core/src/analyze/diagnosis-priorities.ts
|
|
18776
18650
|
function topDelta(report) {
|
|
18777
18651
|
const top = report.items[0];
|
|
18778
18652
|
if (!top) return "No segment movement found.";
|
|
@@ -18830,8 +18704,7 @@ function buildDiagnosisPriorities(input) {
|
|
|
18830
18704
|
}
|
|
18831
18705
|
return priorities.slice(0, 6);
|
|
18832
18706
|
}
|
|
18833
|
-
|
|
18834
|
-
//#region packages/core/src/analyze/diagnosis-status.ts
|
|
18707
|
+
// packages/core/src/analyze/diagnosis-status.ts
|
|
18835
18708
|
function diagnosisPartialReasons(report) {
|
|
18836
18709
|
const skippedSections = new Set(report.skippedSections?.map((section) => section.section));
|
|
18837
18710
|
const segmentReasons = [
|
|
@@ -18883,8 +18756,7 @@ function diagnosisDataStatus(input) {
|
|
|
18883
18756
|
if (input.criticalStatuses.length > 0 && input.criticalStatuses.every((status) => status === "skipped")) return "unavailable";
|
|
18884
18757
|
return input.skippedSections || input.partialReasons ? "partial" : "complete";
|
|
18885
18758
|
}
|
|
18886
|
-
|
|
18887
|
-
//#region packages/core/src/analyze/segment-impact-rows.ts
|
|
18759
|
+
// packages/core/src/analyze/segment-impact-rows.ts
|
|
18888
18760
|
function fixed(value) {
|
|
18889
18761
|
return Number(value.toFixed(3));
|
|
18890
18762
|
}
|
|
@@ -18977,8 +18849,7 @@ function compareRetainedSegmentRows(input) {
|
|
|
18977
18849
|
unmatched
|
|
18978
18850
|
};
|
|
18979
18851
|
}
|
|
18980
|
-
|
|
18981
|
-
//#region packages/core/src/analyze/segment-impact-analysis.ts
|
|
18852
|
+
// packages/core/src/analyze/segment-impact-analysis.ts
|
|
18982
18853
|
function verdict$4(input) {
|
|
18983
18854
|
if (input.status === "empty") return "Search Console returned no retained segment rows for either window.";
|
|
18984
18855
|
if (!input.matchedRows) return "No segment was retained in both windows, so no movement delta was inferred.";
|
|
@@ -19089,8 +18960,7 @@ function analyzeSegmentRows(input) {
|
|
|
19089
18960
|
]
|
|
19090
18961
|
};
|
|
19091
18962
|
}
|
|
19092
|
-
|
|
19093
|
-
//#region packages/core/src/analyze/segment-impact-input.ts
|
|
18963
|
+
// packages/core/src/analyze/segment-impact-input.ts
|
|
19094
18964
|
const DAY_MS$1 = 864e5;
|
|
19095
18965
|
const MAX_WINDOW_DAYS = 240;
|
|
19096
18966
|
function subtractDays(value, days) {
|
|
@@ -19145,8 +19015,7 @@ function resolveSegmentRanges(input) {
|
|
|
19145
19015
|
function validateSegmentSite(site) {
|
|
19146
19016
|
if (!site.trim()) throw new SeoError("INVALID_INPUT", "site must not be empty.");
|
|
19147
19017
|
}
|
|
19148
|
-
|
|
19149
|
-
//#region packages/core/src/analyze/segment-impact-fallback.ts
|
|
19018
|
+
// packages/core/src/analyze/segment-impact-fallback.ts
|
|
19150
19019
|
function unavailableSegmentImpactReport(input) {
|
|
19151
19020
|
const days = segmentRangeDays(input.after);
|
|
19152
19021
|
const before = segmentComparisonRange(input.after, days);
|
|
@@ -19225,8 +19094,7 @@ function unavailableSegmentImpactReport(input) {
|
|
|
19225
19094
|
caveats: ["No segment movement conclusion was produced because the provider evidence was unavailable."]
|
|
19226
19095
|
};
|
|
19227
19096
|
}
|
|
19228
|
-
|
|
19229
|
-
//#region packages/core/src/analyze/segment-impact.ts
|
|
19097
|
+
// packages/core/src/analyze/segment-impact.ts
|
|
19230
19098
|
const DEFAULT_MAX_ROWS = 1e5;
|
|
19231
19099
|
const defaultDependencies$8 = {
|
|
19232
19100
|
searchAnalytics: querySearchAnalytics,
|
|
@@ -19335,8 +19203,7 @@ async function segmentImpact(input, dependencies = defaultDependencies$8) {
|
|
|
19335
19203
|
generatedAt: now.toISOString()
|
|
19336
19204
|
});
|
|
19337
19205
|
}
|
|
19338
|
-
|
|
19339
|
-
//#region packages/core/src/imports/records.ts
|
|
19206
|
+
// packages/core/src/imports/records.ts
|
|
19340
19207
|
const DEFAULT_IMPORT_STREAM_BYTES = 5e7;
|
|
19341
19208
|
async function importFile(input) {
|
|
19342
19209
|
const path = resolve(input.file);
|
|
@@ -19543,8 +19410,7 @@ async function readJsonArray(input) {
|
|
|
19543
19410
|
if (!Array.isArray(parsed)) throw new SeoError("INVALID_INPUT", `${input.label} JSON must be an array of objects.`);
|
|
19544
19411
|
return parsed;
|
|
19545
19412
|
}
|
|
19546
|
-
|
|
19547
|
-
//#region packages/core/src/providers/imports/research-columns.ts
|
|
19413
|
+
// packages/core/src/providers/imports/research-columns.ts
|
|
19548
19414
|
const RESEARCH_IMPORT_COLUMN_KEYS = [
|
|
19549
19415
|
"keyword",
|
|
19550
19416
|
"url",
|
|
@@ -20159,8 +20025,7 @@ async function importResearchRows(source, now = /* @__PURE__ */ new Date()) {
|
|
|
20159
20025
|
warnings
|
|
20160
20026
|
};
|
|
20161
20027
|
}
|
|
20162
|
-
|
|
20163
|
-
//#region packages/core/src/providers/imports/research-provider.ts
|
|
20028
|
+
// packages/core/src/providers/imports/research-provider.ts
|
|
20164
20029
|
function compareText$11(left, right) {
|
|
20165
20030
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
20166
20031
|
}
|
|
@@ -20201,8 +20066,7 @@ async function combineResearchRows(sources, now = /* @__PURE__ */ new Date()) {
|
|
|
20201
20066
|
warnings
|
|
20202
20067
|
};
|
|
20203
20068
|
}
|
|
20204
|
-
|
|
20205
|
-
//#region packages/core/src/analyze/page-patterns.ts
|
|
20069
|
+
// packages/core/src/analyze/page-patterns.ts
|
|
20206
20070
|
const LOCAL_QUERY_TERMS = [
|
|
20207
20071
|
"near me",
|
|
20208
20072
|
"today",
|
|
@@ -20373,8 +20237,7 @@ function dominantTemplateFamily(rows) {
|
|
|
20373
20237
|
share: top.count / rows.length
|
|
20374
20238
|
};
|
|
20375
20239
|
}
|
|
20376
|
-
|
|
20377
|
-
//#region packages/core/src/analyze/site-diagnostics/cannibal-analysis-primitives.ts
|
|
20240
|
+
// packages/core/src/analyze/site-diagnostics/cannibal-analysis-primitives.ts
|
|
20378
20241
|
function compareCannibalText(left, right) {
|
|
20379
20242
|
const leftPoints = [...left].map((value) => value.codePointAt(0) ?? 0);
|
|
20380
20243
|
const rightPoints = [...right].map((value) => value.codePointAt(0) ?? 0);
|
|
@@ -20419,8 +20282,7 @@ function addCannibalRow(group, url, row) {
|
|
|
20419
20282
|
position: weightedPosition / impressions
|
|
20420
20283
|
});
|
|
20421
20284
|
}
|
|
20422
|
-
|
|
20423
|
-
//#region packages/core/src/analyze/site-diagnostics/cannibal-item.ts
|
|
20285
|
+
// packages/core/src/analyze/site-diagnostics/cannibal-item.ts
|
|
20424
20286
|
function cannibalReviewContext(query, rows) {
|
|
20425
20287
|
const context = [];
|
|
20426
20288
|
if (isQuotedQuery(query)) context.push("quoted-query");
|
|
@@ -20498,8 +20360,7 @@ function createCannibalItem(input) {
|
|
|
20498
20360
|
function compareCannibalItems(left, right) {
|
|
20499
20361
|
return right.priority.score - left.priority.score || right.priority.demandImpressions - left.priority.demandImpressions || compareCannibalText(left.query, right.query);
|
|
20500
20362
|
}
|
|
20501
|
-
|
|
20502
|
-
//#region packages/core/src/analyze/site-diagnostics/cannibal-import.ts
|
|
20363
|
+
// packages/core/src/analyze/site-diagnostics/cannibal-import.ts
|
|
20503
20364
|
const MAX_SUPPRESSIONS$1 = 100;
|
|
20504
20365
|
function siteScope(site) {
|
|
20505
20366
|
const withoutScDomain = site.trim().replace(/^sc-domain:/iu, "");
|
|
@@ -20777,8 +20638,7 @@ async function cannibalImportReport(input, dependencies = {}) {
|
|
|
20777
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."]
|
|
20778
20639
|
};
|
|
20779
20640
|
}
|
|
20780
|
-
|
|
20781
|
-
//#region packages/core/src/storage/ledger.ts
|
|
20641
|
+
// packages/core/src/storage/ledger.ts
|
|
20782
20642
|
var SessionLedger = class {
|
|
20783
20643
|
entries = /* @__PURE__ */ new Map();
|
|
20784
20644
|
gscCalls = 0;
|
|
@@ -20808,8 +20668,7 @@ var SessionLedger = class {
|
|
|
20808
20668
|
return providerBits.join(" · ");
|
|
20809
20669
|
}
|
|
20810
20670
|
};
|
|
20811
|
-
|
|
20812
|
-
//#region packages/core/src/analyze/site-diagnostics/cannibal-analysis.ts
|
|
20671
|
+
// packages/core/src/analyze/site-diagnostics/cannibal-analysis.ts
|
|
20813
20672
|
const MINIMUM_PAGE_SHARE = .1;
|
|
20814
20673
|
const MINIMUM_PAGE_IMPRESSIONS = 10;
|
|
20815
20674
|
const MAXIMUM_DOMINANT_SHARE = .8;
|
|
@@ -20971,10 +20830,20 @@ function analyzeCannibalRows(input) {
|
|
|
20971
20830
|
templates: summarizeTemplates(returnedItems.flatMap((item) => item.pages))
|
|
20972
20831
|
};
|
|
20973
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;
|
|
20974
20844
|
const CANNIBAL_MINIMUM_PAGE_SHARE = MINIMUM_PAGE_SHARE;
|
|
20975
20845
|
const CANNIBAL_MAXIMUM_DOMINANT_SHARE = MAXIMUM_DOMINANT_SHARE;
|
|
20976
|
-
|
|
20977
|
-
//#region packages/core/src/analyze/site-diagnostics/cannibal-report.ts
|
|
20846
|
+
// packages/core/src/analyze/site-diagnostics/cannibal-report.ts
|
|
20978
20847
|
const MAX_GSC_ROWS$2 = 1e5;
|
|
20979
20848
|
function verdict$3(input) {
|
|
20980
20849
|
if (input.status === "empty") return "Search Console returned no retained query/page rows for this window.";
|
|
@@ -21043,7 +20912,7 @@ async function cannibalReportWith(input, dependencies) {
|
|
|
21043
20912
|
const possiblyTruncated = pageTruncated || propertyTruncated;
|
|
21044
20913
|
const partialValidation = analysis.selection.invalidRows + analysis.selection.propertyInvalidRows > 0;
|
|
21045
20914
|
const completeness = possiblyTruncated && partialValidation ? "partial-and-possibly-truncated" : possiblyTruncated ? "possibly-truncated" : partialValidation ? "partial" : "complete";
|
|
21046
|
-
const dataStatus = analysis.selection
|
|
20915
|
+
const dataStatus = cannibalDataStatus(analysis.selection, possiblyTruncated);
|
|
21047
20916
|
return {
|
|
21048
20917
|
schemaVersion: 1,
|
|
21049
20918
|
dataSource: "search-console-api",
|
|
@@ -21127,8 +20996,7 @@ async function cannibalReportWith(input, dependencies) {
|
|
|
21127
20996
|
ledgerSummary: ledger.summary()
|
|
21128
20997
|
};
|
|
21129
20998
|
}
|
|
21130
|
-
|
|
21131
|
-
//#region packages/core/src/analyze/site-diagnostics/cannibal.ts
|
|
20999
|
+
// packages/core/src/analyze/site-diagnostics/cannibal.ts
|
|
21132
21000
|
const defaultDependencies$7 = {
|
|
21133
21001
|
searchAnalytics: querySearchAnalytics,
|
|
21134
21002
|
now: () => /* @__PURE__ */ new Date()
|
|
@@ -21153,8 +21021,7 @@ async function cannibalReport(input, dependencies = defaultDependencies$7) {
|
|
|
21153
21021
|
}
|
|
21154
21022
|
return cannibalReportWith(input, dependencies);
|
|
21155
21023
|
}
|
|
21156
|
-
|
|
21157
|
-
//#region packages/core/src/analyze/site-diagnostics/decay-analysis-primitives.ts
|
|
21024
|
+
// packages/core/src/analyze/site-diagnostics/decay-analysis-primitives.ts
|
|
21158
21025
|
function compareDecayText(left, right) {
|
|
21159
21026
|
const leftPoints = [...left].map((value) => value.codePointAt(0) ?? 0);
|
|
21160
21027
|
const rightPoints = [...right].map((value) => value.codePointAt(0) ?? 0);
|
|
@@ -21293,8 +21160,7 @@ function boundedDecayNumber(value, fallback, min, max) {
|
|
|
21293
21160
|
if (value === void 0 || !Number.isFinite(value)) return fallback;
|
|
21294
21161
|
return Math.min(max, Math.max(min, value));
|
|
21295
21162
|
}
|
|
21296
|
-
|
|
21297
|
-
//#region packages/core/src/analyze/site-diagnostics/decay-analysis.ts
|
|
21163
|
+
// packages/core/src/analyze/site-diagnostics/decay-analysis.ts
|
|
21298
21164
|
function analyzeDecay(input) {
|
|
21299
21165
|
const minDropPct = boundedDecayNumber(input.minDropPct, 20, 0, 100);
|
|
21300
21166
|
const minPreviousClicks = boundedDecayNumber(input.minPreviousClicks, 2, 0, 1e9);
|
|
@@ -21417,8 +21283,7 @@ function analyzeDecay(input) {
|
|
|
21417
21283
|
templates: summarizeTemplates(returned)
|
|
21418
21284
|
};
|
|
21419
21285
|
}
|
|
21420
|
-
|
|
21421
|
-
//#region packages/core/src/analyze/site-diagnostics/decaying-report.ts
|
|
21286
|
+
// packages/core/src/analyze/site-diagnostics/decaying-report.ts
|
|
21422
21287
|
const MAX_GSC_ROWS$1 = 1e5;
|
|
21423
21288
|
function verdict$2(report) {
|
|
21424
21289
|
if (report.dataStatus === "empty") return "Search Console returned no retained query/page rows for either comparison window.";
|
|
@@ -21589,8 +21454,7 @@ async function decayingReportWith(input, dependencies) {
|
|
|
21589
21454
|
]
|
|
21590
21455
|
};
|
|
21591
21456
|
}
|
|
21592
|
-
|
|
21593
|
-
//#region packages/core/src/analyze/site-diagnostics/decaying.ts
|
|
21457
|
+
// packages/core/src/analyze/site-diagnostics/decaying.ts
|
|
21594
21458
|
const defaultDependencies$6 = {
|
|
21595
21459
|
searchAnalytics: querySearchAnalytics,
|
|
21596
21460
|
now: () => /* @__PURE__ */ new Date()
|
|
@@ -21598,8 +21462,7 @@ const defaultDependencies$6 = {
|
|
|
21598
21462
|
function decayingReport(input, dependencies = defaultDependencies$6) {
|
|
21599
21463
|
return decayingReportWith(input, dependencies);
|
|
21600
21464
|
}
|
|
21601
|
-
|
|
21602
|
-
//#region packages/core/src/analyze/site-diagnostics/quick-win-groups.ts
|
|
21465
|
+
// packages/core/src/analyze/site-diagnostics/quick-win-groups.ts
|
|
21603
21466
|
function normalizedQuery(value) {
|
|
21604
21467
|
return value.normalize("NFKC").toLocaleLowerCase("und").replace(/\s+/gu, " ").trim();
|
|
21605
21468
|
}
|
|
@@ -21644,8 +21507,7 @@ function groupQuickWins(items) {
|
|
|
21644
21507
|
return group;
|
|
21645
21508
|
}).filter((group) => group !== void 0).sort((left, right) => right.totalEstimatedCtrClickShortfall - left.totalEstimatedCtrClickShortfall || right.totalImpressions - left.totalImpressions || compareText$10(left.id, right.id));
|
|
21646
21509
|
}
|
|
21647
|
-
|
|
21648
|
-
//#region packages/core/src/analyze/site-diagnostics/quick-wins-analysis-primitives.ts
|
|
21510
|
+
// packages/core/src/analyze/site-diagnostics/quick-wins-analysis-primitives.ts
|
|
21649
21511
|
function boundedInteger$3(value, fallback, minimum, maximum) {
|
|
21650
21512
|
if (value === void 0 || !Number.isFinite(value)) return fallback;
|
|
21651
21513
|
return Math.min(maximum, Math.max(minimum, Math.floor(value)));
|
|
@@ -21745,8 +21607,7 @@ function compareQuickWinItems(left, right) {
|
|
|
21745
21607
|
function roundedSum(values) {
|
|
21746
21608
|
return Number(values.reduce((sum, value) => sum + value, 0).toFixed(2));
|
|
21747
21609
|
}
|
|
21748
|
-
|
|
21749
|
-
//#region packages/core/src/analyze/site-diagnostics/quick-wins-analysis.ts
|
|
21610
|
+
// packages/core/src/analyze/site-diagnostics/quick-wins-analysis.ts
|
|
21750
21611
|
const MAX_MIN_IMPRESSIONS$2 = 1e9;
|
|
21751
21612
|
const DEFAULT_LIMIT$2 = 25;
|
|
21752
21613
|
const MAX_LIMIT$2 = 100;
|
|
@@ -21853,8 +21714,7 @@ function analyzeQuickWinsFromRows(input) {
|
|
|
21853
21714
|
benchmarkByPosition: benchmarkContext.byPosition
|
|
21854
21715
|
};
|
|
21855
21716
|
}
|
|
21856
|
-
|
|
21857
|
-
//#region packages/core/src/analyze/site-diagnostics/quick-wins-templates.ts
|
|
21717
|
+
// packages/core/src/analyze/site-diagnostics/quick-wins-templates.ts
|
|
21858
21718
|
function compareText$8(left, right) {
|
|
21859
21719
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
21860
21720
|
}
|
|
@@ -21903,8 +21763,7 @@ function quickWinTemplateSummaries(items) {
|
|
|
21903
21763
|
sampleUrls: [...item.urls].sort(compareText$8).slice(0, 3)
|
|
21904
21764
|
})).sort((left, right) => right.urlCount - left.urlCount || right.rowCount - left.rowCount || compareText$8(left.id, right.id)).slice(0, 5);
|
|
21905
21765
|
}
|
|
21906
|
-
|
|
21907
|
-
//#region packages/core/src/analyze/site-diagnostics/quick-wins-report.ts
|
|
21766
|
+
// packages/core/src/analyze/site-diagnostics/quick-wins-report.ts
|
|
21908
21767
|
const DEFAULT_DAYS$2 = 28;
|
|
21909
21768
|
const MAX_DAYS$1 = 548;
|
|
21910
21769
|
const DEFAULT_VERIFY_LIMIT$1 = 5;
|
|
@@ -22090,8 +21949,7 @@ async function quickWinsReportWith(input, dependencies) {
|
|
|
22090
21949
|
warnings
|
|
22091
21950
|
};
|
|
22092
21951
|
}
|
|
22093
|
-
|
|
22094
|
-
//#region packages/core/src/analyze/site-diagnostics/quick-wins.ts
|
|
21952
|
+
// packages/core/src/analyze/site-diagnostics/quick-wins.ts
|
|
22095
21953
|
const defaultDependencies$5 = {
|
|
22096
21954
|
searchAnalytics: querySearchAnalytics,
|
|
22097
21955
|
fetch: fetchPage,
|
|
@@ -22101,8 +21959,7 @@ const defaultDependencies$5 = {
|
|
|
22101
21959
|
function quickWinsReport(input, dependencies = defaultDependencies$5) {
|
|
22102
21960
|
return quickWinsReportWith(input, dependencies);
|
|
22103
21961
|
}
|
|
22104
|
-
|
|
22105
|
-
//#region packages/core/src/analyze/striking-distance-analysis-primitives.ts
|
|
21962
|
+
// packages/core/src/analyze/striking-distance-analysis-primitives.ts
|
|
22106
21963
|
function compareText$7(left, right) {
|
|
22107
21964
|
const leftPoints = [...left].map((character) => character.codePointAt(0) ?? 0);
|
|
22108
21965
|
const rightPoints = [...right].map((character) => character.codePointAt(0) ?? 0);
|
|
@@ -22209,8 +22066,7 @@ function groupStrikingDistanceItems(items) {
|
|
|
22209
22066
|
};
|
|
22210
22067
|
}).filter((group) => group !== void 0).sort((left, right) => right.totalImpressions - left.totalImpressions || right.uniqueUrls - left.uniqueUrls || compareText$7(left.id, right.id));
|
|
22211
22068
|
}
|
|
22212
|
-
|
|
22213
|
-
//#region packages/core/src/analyze/striking-distance-analysis.ts
|
|
22069
|
+
// packages/core/src/analyze/striking-distance-analysis.ts
|
|
22214
22070
|
const MAX_MIN_IMPRESSIONS$1 = 1e9;
|
|
22215
22071
|
const DEFAULT_LIMIT$1 = 25;
|
|
22216
22072
|
const MAX_LIMIT$1 = 100;
|
|
@@ -22349,8 +22205,7 @@ function analyzeStrikingDistanceRows(input) {
|
|
|
22349
22205
|
items
|
|
22350
22206
|
};
|
|
22351
22207
|
}
|
|
22352
|
-
|
|
22353
|
-
//#region packages/core/src/analyze/striking-distance-report.ts
|
|
22208
|
+
// packages/core/src/analyze/striking-distance-report.ts
|
|
22354
22209
|
const DEFAULT_DAYS$1 = 28;
|
|
22355
22210
|
const MAX_DAYS = 548;
|
|
22356
22211
|
const DEFAULT_VERIFY_LIMIT = 5;
|
|
@@ -22505,8 +22360,7 @@ async function strikingDistanceWith(input, dependencies) {
|
|
|
22505
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."]
|
|
22506
22361
|
};
|
|
22507
22362
|
}
|
|
22508
|
-
|
|
22509
|
-
//#region packages/core/src/analyze/striking-distance.ts
|
|
22363
|
+
// packages/core/src/analyze/striking-distance.ts
|
|
22510
22364
|
const defaultDependencies$4 = {
|
|
22511
22365
|
searchAnalytics: querySearchAnalytics,
|
|
22512
22366
|
verifyContent: verifyQueryContent,
|
|
@@ -22515,8 +22369,7 @@ const defaultDependencies$4 = {
|
|
|
22515
22369
|
function strikingDistance(input, dependencies = defaultDependencies$4) {
|
|
22516
22370
|
return strikingDistanceWith(input, dependencies);
|
|
22517
22371
|
}
|
|
22518
|
-
|
|
22519
|
-
//#region packages/core/src/updates/search-status.ts
|
|
22372
|
+
// packages/core/src/updates/search-status.ts
|
|
22520
22373
|
const INCIDENTS_URL = "https://status.search.google.com/incidents.json";
|
|
22521
22374
|
async function fetchSearchStatusIncidents() {
|
|
22522
22375
|
const response = await fetch$1(INCIDENTS_URL, { signal: AbortSignal.timeout(8e3) });
|
|
@@ -22526,8 +22379,7 @@ async function fetchSearchStatusIncidents() {
|
|
|
22526
22379
|
async function listSearchUpdates(input = {}) {
|
|
22527
22380
|
return searchUpdatesFromIncidents(withRankingHistory(await fetchSearchStatusIncidents()), input);
|
|
22528
22381
|
}
|
|
22529
|
-
|
|
22530
|
-
//#region packages/core/src/analyze/traffic-anomaly-analysis.ts
|
|
22382
|
+
// packages/core/src/analyze/traffic-anomaly-analysis.ts
|
|
22531
22383
|
/** The step-change heuristic and its thresholds, returned with the report. */
|
|
22532
22384
|
const STEP_CHANGE_RULES = {
|
|
22533
22385
|
method: "best-split",
|
|
@@ -22883,8 +22735,7 @@ function analyzeTrafficAnomalyRows(input) {
|
|
|
22883
22735
|
}
|
|
22884
22736
|
};
|
|
22885
22737
|
}
|
|
22886
|
-
|
|
22887
|
-
//#region packages/core/src/analyze/traffic-anomaly-report.ts
|
|
22738
|
+
// packages/core/src/analyze/traffic-anomaly-report.ts
|
|
22888
22739
|
async function trafficAnomalyWith(input, dependencies) {
|
|
22889
22740
|
const days = input.days ?? 90;
|
|
22890
22741
|
const recentDays = input.recentDays ?? 7;
|
|
@@ -22927,8 +22778,7 @@ async function trafficAnomalyWith(input, dependencies) {
|
|
|
22927
22778
|
detection: analyzed.detection
|
|
22928
22779
|
};
|
|
22929
22780
|
}
|
|
22930
|
-
|
|
22931
|
-
//#region packages/core/src/analyze/traffic-anomaly.ts
|
|
22781
|
+
// packages/core/src/analyze/traffic-anomaly.ts
|
|
22932
22782
|
const defaultDependencies$3 = {
|
|
22933
22783
|
searchAnalytics: querySearchAnalytics,
|
|
22934
22784
|
now: () => /* @__PURE__ */ new Date()
|
|
@@ -22975,8 +22825,7 @@ async function updateCorrelation(input) {
|
|
|
22975
22825
|
})
|
|
22976
22826
|
};
|
|
22977
22827
|
}
|
|
22978
|
-
|
|
22979
|
-
//#region packages/core/src/analyze/diagnose-property.ts
|
|
22828
|
+
// packages/core/src/analyze/diagnose-property.ts
|
|
22980
22829
|
function errorReason(error) {
|
|
22981
22830
|
return error instanceof Error ? error.message : String(error);
|
|
22982
22831
|
}
|
|
@@ -23512,8 +23361,7 @@ async function diagnoseProperty(input, dependencies = {}) {
|
|
|
23512
23361
|
quickWins
|
|
23513
23362
|
};
|
|
23514
23363
|
}
|
|
23515
|
-
|
|
23516
|
-
//#region packages/core/src/decision-model.ts
|
|
23364
|
+
// packages/core/src/decision-model.ts
|
|
23517
23365
|
var DecisionModelError = class extends Error {
|
|
23518
23366
|
kind;
|
|
23519
23367
|
retryAfterMs;
|
|
@@ -23666,8 +23514,7 @@ var DecisionRunner = class {
|
|
|
23666
23514
|
return null;
|
|
23667
23515
|
}
|
|
23668
23516
|
};
|
|
23669
|
-
|
|
23670
|
-
//#region packages/core/src/analyze/internal-links-index.ts
|
|
23517
|
+
// packages/core/src/analyze/internal-links-index.ts
|
|
23671
23518
|
const PAGE_TERMS = 300;
|
|
23672
23519
|
const VECTOR_TERMS = 100;
|
|
23673
23520
|
var SiteIndex = class {
|
|
@@ -23794,7 +23641,7 @@ const GENERIC_ANCHORS = /^(click here|here|read more|learn more|more|this|this p
|
|
|
23794
23641
|
function normalise(value) {
|
|
23795
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();
|
|
23796
23643
|
}
|
|
23797
|
-
const words$
|
|
23644
|
+
const words$2 = (value) => normalise(value).split(" ").filter(Boolean);
|
|
23798
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(" "));
|
|
23799
23646
|
const NOT_PLURAL = /* @__PURE__ */ new Set([
|
|
23800
23647
|
"always",
|
|
@@ -23855,7 +23702,7 @@ function stem(word) {
|
|
|
23855
23702
|
if (one.length > 6 && one.endsWith("ing")) return one.slice(0, -3);
|
|
23856
23703
|
return one;
|
|
23857
23704
|
}
|
|
23858
|
-
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);
|
|
23859
23706
|
const slugWords = (url) => {
|
|
23860
23707
|
try {
|
|
23861
23708
|
return new URL(url).pathname.split(/[/._-]+/).filter(Boolean).join(" ");
|
|
@@ -24141,8 +23988,7 @@ function placementPage(input) {
|
|
|
24141
23988
|
sentences: pageSentences(input.html)
|
|
24142
23989
|
};
|
|
24143
23990
|
}
|
|
24144
|
-
|
|
24145
|
-
//#region packages/core/src/analyze/internal-links-crawl.ts
|
|
23991
|
+
// packages/core/src/analyze/internal-links-crawl.ts
|
|
24146
23992
|
const DEFAULT_MAX_PAGES = 500;
|
|
24147
23993
|
const MAX_MAX_PAGES = 5e3;
|
|
24148
23994
|
const FETCH_CONCURRENCY = 6;
|
|
@@ -24514,8 +24360,7 @@ async function crawl(input, robots, sentences, renderer, warnings) {
|
|
|
24514
24360
|
warnings
|
|
24515
24361
|
};
|
|
24516
24362
|
}
|
|
24517
|
-
|
|
24518
|
-
//#region packages/core/src/analyze/internal-links-judge.ts
|
|
24363
|
+
// packages/core/src/analyze/internal-links-judge.ts
|
|
24519
24364
|
const PAGE_TYPES = {
|
|
24520
24365
|
pillar_guide: "A long, broad guide that covers a whole topic and would naturally link out to many narrower pages",
|
|
24521
24366
|
support_article: "A focused article, post, tutorial, or explainer about one narrower subject",
|
|
@@ -24739,8 +24584,7 @@ function decideWithoutModel(placement, similarity) {
|
|
|
24739
24584
|
anchor: placement.anchors.find((option) => option.sentence === sentence?.id)?.text ?? null
|
|
24740
24585
|
};
|
|
24741
24586
|
}
|
|
24742
|
-
|
|
24743
|
-
//#region packages/core/src/analyze/internal-links-report.ts
|
|
24587
|
+
// packages/core/src/analyze/internal-links-report.ts
|
|
24744
24588
|
const DAY$1 = 864e5;
|
|
24745
24589
|
const ISO_DAY = /^\d{4}-\d{2}-\d{2}$/;
|
|
24746
24590
|
function internalLinksReportRange(input, fallbackDays, now) {
|
|
@@ -24768,8 +24612,7 @@ function internalLinksVerdict(report) {
|
|
|
24768
24612
|
const capped = report.crawl.capReached ? ` The crawl stopped at its limit of ${report.crawl.pageLimit} pages, so pages beyond it were not checked.` : "";
|
|
24769
24613
|
return `${judged} ${scope}${counts.heldBack ? `; ${counts.heldBack} held back by the evidence floors` : ""}.${capped}`;
|
|
24770
24614
|
}
|
|
24771
|
-
|
|
24772
|
-
//#region packages/core/src/analyze/internal-links.ts
|
|
24615
|
+
// packages/core/src/analyze/internal-links.ts
|
|
24773
24616
|
const GSC_DAYS = 90;
|
|
24774
24617
|
const MAX_GSC_ROWS = 1e5;
|
|
24775
24618
|
const MAX_PAGES_PER_QUERY = 25;
|
|
@@ -25278,8 +25121,7 @@ function caveats$1(report) {
|
|
|
25278
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."
|
|
25279
25122
|
];
|
|
25280
25123
|
}
|
|
25281
|
-
|
|
25282
|
-
//#region packages/core/src/analyze/url-templates.ts
|
|
25124
|
+
// packages/core/src/analyze/url-templates.ts
|
|
25283
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;
|
|
25284
25126
|
const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
25285
25127
|
function compareText$6(left, right) {
|
|
@@ -25408,8 +25250,7 @@ function clusterPseoTemplates(urls, opts = {}) {
|
|
|
25408
25250
|
sampleUrls: clusterUrls.sort(compareText$6).slice(0, sampleSize)
|
|
25409
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);
|
|
25410
25252
|
}
|
|
25411
|
-
|
|
25412
|
-
//#region packages/core/src/analyze/monitoring/index-coverage.ts
|
|
25253
|
+
// packages/core/src/analyze/monitoring/index-coverage.ts
|
|
25413
25254
|
const DEFAULT_ITEMS_PER_SECTION = 100;
|
|
25414
25255
|
const MAX_ITEMS_PER_SECTION = 1e3;
|
|
25415
25256
|
const DEFAULT_TEMPLATE_LIMIT = 50;
|
|
@@ -25752,8 +25593,7 @@ function analyzeIndexCoverageSignals(input) {
|
|
|
25752
25593
|
})
|
|
25753
25594
|
};
|
|
25754
25595
|
}
|
|
25755
|
-
|
|
25756
|
-
//#region packages/core/src/analyze/monitoring/index-coverage-report.ts
|
|
25596
|
+
// packages/core/src/analyze/monitoring/index-coverage-report.ts
|
|
25757
25597
|
const MAX_SITEMAP_DOCUMENTS = 20;
|
|
25758
25598
|
const MAX_SEARCH_ROWS = 25e4;
|
|
25759
25599
|
const MAX_SITEMAP_URLS = 25e4;
|
|
@@ -25883,8 +25723,7 @@ async function indexCoverageSignals(input, dependencies = defaultDependencies$1)
|
|
|
25883
25723
|
}
|
|
25884
25724
|
};
|
|
25885
25725
|
}
|
|
25886
|
-
|
|
25887
|
-
//#region packages/core/src/analyze/monitoring/redirect-trace.ts
|
|
25726
|
+
// packages/core/src/analyze/monitoring/redirect-trace.ts
|
|
25888
25727
|
function resolveNextUrl(location, baseUrl) {
|
|
25889
25728
|
try {
|
|
25890
25729
|
const next = new URL(location, baseUrl);
|
|
@@ -25986,8 +25825,7 @@ async function redirectTrace(input) {
|
|
|
25986
25825
|
warnings
|
|
25987
25826
|
};
|
|
25988
25827
|
}
|
|
25989
|
-
|
|
25990
|
-
//#region packages/core/src/analyze/monitoring/link-recover.ts
|
|
25828
|
+
// packages/core/src/analyze/monitoring/link-recover.ts
|
|
25991
25829
|
function primaryIssue(issues) {
|
|
25992
25830
|
return [
|
|
25993
25831
|
"final-5xx",
|
|
@@ -26124,8 +25962,7 @@ async function linkRecover(input) {
|
|
|
26124
25962
|
warnings: [...new Set(warnings)].slice(0, 50)
|
|
26125
25963
|
};
|
|
26126
25964
|
}
|
|
26127
|
-
|
|
26128
|
-
//#region packages/core/src/analyze/performance-analysis.ts
|
|
25965
|
+
// packages/core/src/analyze/performance-analysis.ts
|
|
26129
25966
|
const FIELD_THRESHOLDS = {
|
|
26130
25967
|
CLS: {
|
|
26131
25968
|
good: .1,
|
|
@@ -26333,8 +26170,7 @@ function performanceActions(report) {
|
|
|
26333
26170
|
});
|
|
26334
26171
|
return actions;
|
|
26335
26172
|
}
|
|
26336
|
-
|
|
26337
|
-
//#region packages/core/src/analyze/performance-crux.ts
|
|
26173
|
+
// packages/core/src/analyze/performance-crux.ts
|
|
26338
26174
|
const CRUX_METRICS = [
|
|
26339
26175
|
"cumulative_layout_shift",
|
|
26340
26176
|
"interaction_to_next_paint",
|
|
@@ -26463,8 +26299,7 @@ async function fetchCruxFieldData(input) {
|
|
|
26463
26299
|
};
|
|
26464
26300
|
}
|
|
26465
26301
|
}
|
|
26466
|
-
|
|
26467
|
-
//#region packages/core/src/analyze/performance-lighthouse.ts
|
|
26302
|
+
// packages/core/src/analyze/performance-lighthouse.ts
|
|
26468
26303
|
const execFileAsync$1 = promisify(execFile);
|
|
26469
26304
|
const INSIGHT_BYTE_BUDGET = 24e3;
|
|
26470
26305
|
function compactRecord(value) {
|
|
@@ -26655,8 +26490,7 @@ async function runLighthouse(input) {
|
|
|
26655
26490
|
}
|
|
26656
26491
|
return report;
|
|
26657
26492
|
}
|
|
26658
|
-
|
|
26659
|
-
//#region packages/core/src/analyze/performance.ts
|
|
26493
|
+
// packages/core/src/analyze/performance.ts
|
|
26660
26494
|
const CACHE_TTL_MS = 864e5;
|
|
26661
26495
|
function reportId(input) {
|
|
26662
26496
|
return `perf_${createHash("sha256").update(JSON.stringify({
|
|
@@ -26846,8 +26680,7 @@ async function performanceAudit(input) {
|
|
|
26846
26680
|
})) cacheSet(report, now.getTime());
|
|
26847
26681
|
return report;
|
|
26848
26682
|
}
|
|
26849
|
-
|
|
26850
|
-
//#region packages/core/src/analyze/query-cluster-primitives.ts
|
|
26683
|
+
// packages/core/src/analyze/query-cluster-primitives.ts
|
|
26851
26684
|
const CLUSTER_THRESHOLD = .5;
|
|
26852
26685
|
const MAX_TOKEN_BUCKET = 250;
|
|
26853
26686
|
const MAX_TOKEN_SHARE = .08;
|
|
@@ -26963,8 +26796,7 @@ function* iterateQueryClusters(rows) {
|
|
|
26963
26796
|
yield group;
|
|
26964
26797
|
}
|
|
26965
26798
|
}
|
|
26966
|
-
|
|
26967
|
-
//#region packages/core/src/analyze/query-cluster-analysis.ts
|
|
26799
|
+
// packages/core/src/analyze/query-cluster-analysis.ts
|
|
26968
26800
|
function quotedLabel(label) {
|
|
26969
26801
|
return label.includes("\"") ? `'${label}'` : `"${label}"`;
|
|
26970
26802
|
}
|
|
@@ -27184,8 +27016,7 @@ function analyzeQueryClustersFromRows(input) {
|
|
|
27184
27016
|
limit
|
|
27185
27017
|
};
|
|
27186
27018
|
}
|
|
27187
|
-
|
|
27188
|
-
//#region packages/core/src/analyze/query-cluster.ts
|
|
27019
|
+
// packages/core/src/analyze/query-cluster.ts
|
|
27189
27020
|
function* retainedQueryClusterRows(input) {
|
|
27190
27021
|
for (const row of input.rows) {
|
|
27191
27022
|
const query = row.keys[0] ?? "";
|
|
@@ -27292,15 +27123,13 @@ function buildQueryClusterReportFromRows(input) {
|
|
|
27292
27123
|
recommendations: clusters.filter((cluster) => cluster.recommendation).slice(0, 5).map((cluster) => cluster.recommendation ?? "")
|
|
27293
27124
|
};
|
|
27294
27125
|
}
|
|
27295
|
-
|
|
27296
|
-
//#region packages/core/src/analyze/reports/dates.ts
|
|
27126
|
+
// packages/core/src/analyze/reports/dates.ts
|
|
27297
27127
|
function rangeDays(range) {
|
|
27298
27128
|
const start = Date.parse(`${range.startDate}T00:00:00Z`);
|
|
27299
27129
|
const end = Date.parse(`${range.endDate}T00:00:00Z`);
|
|
27300
27130
|
return Math.floor((end - start) / 864e5) + 1;
|
|
27301
27131
|
}
|
|
27302
|
-
|
|
27303
|
-
//#region packages/core/src/analyze/reports/markdown.ts
|
|
27132
|
+
// packages/core/src/analyze/reports/markdown.ts
|
|
27304
27133
|
function renderMarkdown(report) {
|
|
27305
27134
|
const lines = [
|
|
27306
27135
|
`# SEO report: ${report.site}`,
|
|
@@ -27324,8 +27153,7 @@ function renderMarkdown(report) {
|
|
|
27324
27153
|
for (const priority of report.priorities) lines.push(`- ${priority.title} (${priority.confidence}): ${priority.action}`);
|
|
27325
27154
|
return lines.join("\n");
|
|
27326
27155
|
}
|
|
27327
|
-
|
|
27328
|
-
//#region packages/core/src/analyze/reports/sections.ts
|
|
27156
|
+
// packages/core/src/analyze/reports/sections.ts
|
|
27329
27157
|
function skippedReason(report, section) {
|
|
27330
27158
|
return report.skippedSections?.find((item) => item.section === section)?.reason;
|
|
27331
27159
|
}
|
|
@@ -27460,8 +27288,7 @@ function contentOpportunityBullets(report) {
|
|
|
27460
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.");
|
|
27461
27289
|
return bullets;
|
|
27462
27290
|
}
|
|
27463
|
-
|
|
27464
|
-
//#region packages/core/src/analyze/reports/narrative.ts
|
|
27291
|
+
// packages/core/src/analyze/reports/narrative.ts
|
|
27465
27292
|
function reportCaveats(input) {
|
|
27466
27293
|
return [
|
|
27467
27294
|
`Date window: ${input.period.startDate} to ${input.period.endDate}.`,
|
|
@@ -27543,8 +27370,7 @@ async function reportNarrative(input) {
|
|
|
27543
27370
|
markdown: renderMarkdown(report)
|
|
27544
27371
|
};
|
|
27545
27372
|
}
|
|
27546
|
-
|
|
27547
|
-
//#region packages/core/src/analyze/second-page-analysis.ts
|
|
27373
|
+
// packages/core/src/analyze/second-page-analysis.ts
|
|
27548
27374
|
const DEFAULT_MIN_IMPRESSIONS = 50;
|
|
27549
27375
|
const MAX_MIN_IMPRESSIONS = 1e9;
|
|
27550
27376
|
const DEFAULT_LIMIT = 10;
|
|
@@ -27748,8 +27574,7 @@ function analyzeSecondPageRows(input) {
|
|
|
27748
27574
|
items
|
|
27749
27575
|
};
|
|
27750
27576
|
}
|
|
27751
|
-
|
|
27752
|
-
//#region packages/core/src/analyze/second-page-report.ts
|
|
27577
|
+
// packages/core/src/analyze/second-page-report.ts
|
|
27753
27578
|
const MAX_SOURCE_ROWS = 1e5;
|
|
27754
27579
|
function integerOption(input) {
|
|
27755
27580
|
if (input.value === void 0) return input.fallback;
|
|
@@ -27966,8 +27791,7 @@ async function secondPageWith(input, dependencies) {
|
|
|
27966
27791
|
warnings: uniqueWarnings(warnings)
|
|
27967
27792
|
};
|
|
27968
27793
|
}
|
|
27969
|
-
|
|
27970
|
-
//#region packages/core/src/analyze/second-page.ts
|
|
27794
|
+
// packages/core/src/analyze/second-page.ts
|
|
27971
27795
|
const defaultDependencies = {
|
|
27972
27796
|
searchAnalytics: querySearchAnalytics,
|
|
27973
27797
|
fetch: fetchPage,
|
|
@@ -27977,8 +27801,7 @@ const defaultDependencies = {
|
|
|
27977
27801
|
function secondPage(input, dependencies = defaultDependencies) {
|
|
27978
27802
|
return secondPageWith(input, dependencies);
|
|
27979
27803
|
}
|
|
27980
|
-
|
|
27981
|
-
//#region packages/core/src/analyze/workflows/priority-scoring.ts
|
|
27804
|
+
// packages/core/src/analyze/workflows/priority-scoring.ts
|
|
27982
27805
|
const SOURCE_WEIGHT = {
|
|
27983
27806
|
decay: 1.25,
|
|
27984
27807
|
"quick-win": 1.15,
|
|
@@ -28047,8 +27870,7 @@ function scorePriority(input) {
|
|
|
28047
27870
|
final: Number(final.toFixed(2))
|
|
28048
27871
|
};
|
|
28049
27872
|
}
|
|
28050
|
-
|
|
28051
|
-
//#region packages/core/src/analyze/workflows/report.ts
|
|
27873
|
+
// packages/core/src/analyze/workflows/report.ts
|
|
28052
27874
|
function workflowReport(input) {
|
|
28053
27875
|
return {
|
|
28054
27876
|
workflow: input.workflow,
|
|
@@ -28060,8 +27882,7 @@ function workflowReport(input) {
|
|
|
28060
27882
|
output: input.output
|
|
28061
27883
|
};
|
|
28062
27884
|
}
|
|
28063
|
-
|
|
28064
|
-
//#region packages/core/src/analyze/workflows/crawl-implementation-queue.ts
|
|
27885
|
+
// packages/core/src/analyze/workflows/crawl-implementation-queue.ts
|
|
28065
27886
|
const CATEGORY_TO_PRIORITY = {
|
|
28066
27887
|
canonical: "technical",
|
|
28067
27888
|
content: "content",
|
|
@@ -28197,8 +28018,7 @@ async function crawlImplementationQueueWorkflow(input, dependencies) {
|
|
|
28197
28018
|
}
|
|
28198
28019
|
});
|
|
28199
28020
|
}
|
|
28200
|
-
|
|
28201
|
-
//#region packages/core/src/analyze/workflows/diagnose-property.ts
|
|
28021
|
+
// packages/core/src/analyze/workflows/diagnose-property.ts
|
|
28202
28022
|
async function diagnosePropertyWorkflow(input) {
|
|
28203
28023
|
const narrative = await reportNarrative(input);
|
|
28204
28024
|
const skippedCount = narrative.diagnosis.skippedSections?.length ?? 0;
|
|
@@ -28220,8 +28040,7 @@ async function diagnosePropertyWorkflow(input) {
|
|
|
28220
28040
|
output: { narrative }
|
|
28221
28041
|
});
|
|
28222
28042
|
}
|
|
28223
|
-
|
|
28224
|
-
//#region packages/core/src/markdown.ts
|
|
28043
|
+
// packages/core/src/markdown.ts
|
|
28225
28044
|
function tableCell(value) {
|
|
28226
28045
|
return String(value ?? "-").replaceAll("\n", " ").replaceAll("|", "\\|");
|
|
28227
28046
|
}
|
|
@@ -28248,8 +28067,7 @@ function renderPresentationTablesMarkdown(tables) {
|
|
|
28248
28067
|
];
|
|
28249
28068
|
});
|
|
28250
28069
|
}
|
|
28251
|
-
|
|
28252
|
-
//#region packages/core/src/analyze/workflows/presentation.ts
|
|
28070
|
+
// packages/core/src/analyze/workflows/presentation.ts
|
|
28253
28071
|
function isRecord(value) {
|
|
28254
28072
|
return typeof value === "object" && value !== null;
|
|
28255
28073
|
}
|
|
@@ -28384,8 +28202,7 @@ function workflowPresentation(report, options = {}) {
|
|
|
28384
28202
|
}] : []
|
|
28385
28203
|
};
|
|
28386
28204
|
}
|
|
28387
|
-
|
|
28388
|
-
//#region packages/core/src/analyze/workflows/markdown.ts
|
|
28205
|
+
// packages/core/src/analyze/workflows/markdown.ts
|
|
28389
28206
|
function renderWorkflowMarkdown(report, options = {}) {
|
|
28390
28207
|
const presentation = workflowPresentation(report, options);
|
|
28391
28208
|
return [
|
|
@@ -28399,8 +28216,7 @@ function renderWorkflowMarkdown(report, options = {}) {
|
|
|
28399
28216
|
...renderPresentationTablesMarkdown(presentation.tables)
|
|
28400
28217
|
].join("\n").trim();
|
|
28401
28218
|
}
|
|
28402
|
-
|
|
28403
|
-
//#region packages/core/src/analyze/workflows/priority-grouping.ts
|
|
28219
|
+
// packages/core/src/analyze/workflows/priority-grouping.ts
|
|
28404
28220
|
const GROUPABLE_SOURCES = /* @__PURE__ */ new Set(["quick-win", "striking-distance"]);
|
|
28405
28221
|
function queueTemplateFamilyId(template) {
|
|
28406
28222
|
if (!template) return "no-template";
|
|
@@ -28496,8 +28312,7 @@ function groupBy(items, keyFor) {
|
|
|
28496
28312
|
function groupPriorityQueue(items) {
|
|
28497
28313
|
return groupBy(groupBy(items, targetGroupKey), groupKey).sort((a, b) => b.score - a.score);
|
|
28498
28314
|
}
|
|
28499
|
-
|
|
28500
|
-
//#region packages/core/src/analyze/workflows/refresh-priorities.ts
|
|
28315
|
+
// packages/core/src/analyze/workflows/refresh-priorities.ts
|
|
28501
28316
|
function templateCount(input) {
|
|
28502
28317
|
const template = input.templates?.find((item) => item.id === input.id);
|
|
28503
28318
|
return template?.urlCount ?? template?.count;
|
|
@@ -28704,8 +28519,7 @@ async function refreshPrioritiesWorkflow(input) {
|
|
|
28704
28519
|
}
|
|
28705
28520
|
});
|
|
28706
28521
|
}
|
|
28707
|
-
|
|
28708
|
-
//#region packages/core/src/analyze/workflows/segments.ts
|
|
28522
|
+
// packages/core/src/analyze/workflows/segments.ts
|
|
28709
28523
|
function compareText$3(left, right) {
|
|
28710
28524
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
28711
28525
|
}
|
|
@@ -28733,8 +28547,7 @@ function splitSegments(report) {
|
|
|
28733
28547
|
losers
|
|
28734
28548
|
};
|
|
28735
28549
|
}
|
|
28736
|
-
|
|
28737
|
-
//#region packages/core/src/analyze/workflows/update-postmortem.ts
|
|
28550
|
+
// packages/core/src/analyze/workflows/update-postmortem.ts
|
|
28738
28551
|
function formatMagnitude(value) {
|
|
28739
28552
|
return Math.abs(value).toLocaleString("en-GB");
|
|
28740
28553
|
}
|
|
@@ -29006,8 +28819,7 @@ async function updatePostmortemWorkflow(input, dependencies = {}) {
|
|
|
29006
28819
|
}
|
|
29007
28820
|
});
|
|
29008
28821
|
}
|
|
29009
|
-
|
|
29010
|
-
//#region packages/core/src/brand-detect.ts
|
|
28822
|
+
// packages/core/src/brand-detect.ts
|
|
29011
28823
|
function defaultDateRange(days = 28) {
|
|
29012
28824
|
return finalGscDateRange(days);
|
|
29013
28825
|
}
|
|
@@ -29074,8 +28886,7 @@ async function detectBrandTerms(input) {
|
|
|
29074
28886
|
candidates: ranked
|
|
29075
28887
|
};
|
|
29076
28888
|
}
|
|
29077
|
-
|
|
29078
|
-
//#region packages/core/src/clicky/api.ts
|
|
28889
|
+
// packages/core/src/clicky/api.ts
|
|
29079
28890
|
const ENDPOINT$1 = "https://api.clicky.com/api/stats/4";
|
|
29080
28891
|
const MAX_BYTES$3 = 5e6;
|
|
29081
28892
|
var ClickyError = class extends Error {
|
|
@@ -29129,8 +28940,7 @@ async function clickyDomain(site) {
|
|
|
29129
28940
|
return null;
|
|
29130
28941
|
}
|
|
29131
28942
|
}
|
|
29132
|
-
|
|
29133
|
-
//#region packages/core/src/analyze/ai-queries.ts
|
|
28943
|
+
// packages/core/src/analyze/ai-queries.ts
|
|
29134
28944
|
const RULES = [
|
|
29135
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"],
|
|
29136
28946
|
["tracker", "\\. ?my location is |\\?\\. ?my( |$)|^evaluate the .+ on "],
|
|
@@ -29172,7 +28982,7 @@ function aiQueryKind(query) {
|
|
|
29172
28982
|
return MATCHERS.find(([, pattern]) => pattern.test(text))?.[0] ?? null;
|
|
29173
28983
|
}
|
|
29174
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(" "));
|
|
29175
|
-
function words(query) {
|
|
28985
|
+
function words$1(query) {
|
|
29176
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));
|
|
29177
28987
|
}
|
|
29178
28988
|
const SAME_QUESTION = .6;
|
|
@@ -29215,7 +29025,7 @@ async function aiQueriesReport(input, searchAnalytics) {
|
|
|
29215
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])));
|
|
29216
29026
|
const usage = /* @__PURE__ */ new Map();
|
|
29217
29027
|
const unique = new Set(rows.map((row) => String(row.keys[0] ?? "")));
|
|
29218
|
-
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);
|
|
29219
29029
|
const weight = (word) => Math.log((unique.size + 1) / (usage.get(word) ?? 1));
|
|
29220
29030
|
const groups = [];
|
|
29221
29031
|
const byWord = /* @__PURE__ */ new Map();
|
|
@@ -29235,7 +29045,7 @@ async function aiQueriesReport(input, searchAnalytics) {
|
|
|
29235
29045
|
continue;
|
|
29236
29046
|
}
|
|
29237
29047
|
const text = query.trim().toLowerCase();
|
|
29238
|
-
const own = words(query);
|
|
29048
|
+
const own = words$1(query);
|
|
29239
29049
|
let group = byText.get(text);
|
|
29240
29050
|
if (!group) {
|
|
29241
29051
|
let checked = 0;
|
|
@@ -29350,8 +29160,7 @@ async function aiQueriesReport(input, searchAnalytics) {
|
|
|
29350
29160
|
]
|
|
29351
29161
|
};
|
|
29352
29162
|
}
|
|
29353
|
-
|
|
29354
|
-
//#region packages/core/src/commands/research-providers.ts
|
|
29163
|
+
// packages/core/src/commands/research-providers.ts
|
|
29355
29164
|
function need(headers, provider) {
|
|
29356
29165
|
const values = provider === "dataforseo" ? [headers.get("x-dataforseo-login"), headers.get("x-dataforseo-password")] : [headers.get("x-ahrefs-key")];
|
|
29357
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\`.`);
|
|
@@ -29943,8 +29752,7 @@ async function runAiAnswers(input, headers, deps, more) {
|
|
|
29943
29752
|
}
|
|
29944
29753
|
};
|
|
29945
29754
|
}
|
|
29946
|
-
|
|
29947
|
-
//#region packages/core/src/commands/analytics.ts
|
|
29755
|
+
// packages/core/src/commands/analytics.ts
|
|
29948
29756
|
async function analyticsSignIn(accounts, property, signIn) {
|
|
29949
29757
|
const only = accounts.length === 1 ? accounts[0] : void 0;
|
|
29950
29758
|
if (only) return only.google;
|
|
@@ -30006,8 +29814,7 @@ async function runAnalyticsRows(input, google, options) {
|
|
|
30006
29814
|
}
|
|
30007
29815
|
};
|
|
30008
29816
|
}
|
|
30009
|
-
|
|
30010
|
-
//#region packages/core/src/ga4/matching.ts
|
|
29817
|
+
// packages/core/src/ga4/matching.ts
|
|
30011
29818
|
function hostnameFromUrl(value) {
|
|
30012
29819
|
try {
|
|
30013
29820
|
const url = new URL(value);
|
|
@@ -30049,8 +29856,7 @@ function ga4MatchReason(match, site) {
|
|
|
30049
29856
|
const location = match.match === "domain" ? "is inside" : "matches";
|
|
30050
29857
|
return `Matched Google Analytics web stream ${stream} (${match.defaultUri}): its hostname ${location} ${site}.`;
|
|
30051
29858
|
}
|
|
30052
|
-
|
|
30053
|
-
//#region packages/core/src/commands/analytics-site.ts
|
|
29859
|
+
// packages/core/src/commands/analytics-site.ts
|
|
30054
29860
|
const STREAM_CHECKS = 30;
|
|
30055
29861
|
const AT_ONCE = 5;
|
|
30056
29862
|
const TIED = 3;
|
|
@@ -30356,10 +30162,9 @@ async function analyticsBeside(access, site, range, before, pages) {
|
|
|
30356
30162
|
};
|
|
30357
30163
|
}
|
|
30358
30164
|
}
|
|
30359
|
-
|
|
30360
|
-
//#region packages/core/src/commands/audit.ts
|
|
30165
|
+
// packages/core/src/commands/audit.ts
|
|
30361
30166
|
const AUDIT_SECTION = "weekly-audit";
|
|
30362
|
-
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), with what is new, back or resolved since the crawl before. Only for sites added to the weekly site audit beta.";
|
|
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.";
|
|
30363
30168
|
const AUDIT_RULES = {
|
|
30364
30169
|
link_to_error: "Internal links to a page that returns an error",
|
|
30365
30170
|
link_to_redirect: "Internal links to a redirect",
|
|
@@ -30372,25 +30177,37 @@ const AUDIT_RULES = {
|
|
|
30372
30177
|
canonical_elsewhere: "Pages whose canonical is another URL",
|
|
30373
30178
|
sitemap_url_not_ok: "Sitemap URLs that do not return 200",
|
|
30374
30179
|
sitemap_page_unlinked: "Sitemap pages no crawled page links to (heuristic: the crawl may not reach every page)",
|
|
30375
|
-
thin_page: "Pages under 100 words (heuristic)"
|
|
30180
|
+
thin_page: "Pages under 100 words (heuristic)",
|
|
30181
|
+
js_changed_tags: "Pages whose raw HTML has another title, canonical or robots tag than the page after JavaScript runs"
|
|
30376
30182
|
};
|
|
30377
|
-
const PAGE_SIZE$
|
|
30378
|
-
const SUMMARY_ROWS = 20;
|
|
30379
|
-
const day$
|
|
30380
|
-
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) {
|
|
30381
30187
|
return origin && value.startsWith(origin) ? value.slice(origin.length) || "/" : value;
|
|
30382
30188
|
}
|
|
30383
30189
|
function evidence(finding, origin) {
|
|
30384
30190
|
const details = JSON.parse(finding.details);
|
|
30385
30191
|
const parts = [];
|
|
30386
30192
|
if (typeof details.status === "number") parts.push(`${details.status}`);
|
|
30387
|
-
if (typeof details.location === "string") parts.push(`to ${relative$
|
|
30388
|
-
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)}`);
|
|
30389
30195
|
if (typeof details.words === "number") parts.push(`${details.words} words`);
|
|
30196
|
+
if (details.raw && typeof details.raw === "object") {
|
|
30197
|
+
const raw = details.raw;
|
|
30198
|
+
for (const key of [
|
|
30199
|
+
"title",
|
|
30200
|
+
"canonical",
|
|
30201
|
+
"robots"
|
|
30202
|
+
]) if (key in raw) {
|
|
30203
|
+
const value = raw[key];
|
|
30204
|
+
parts.push(`raw ${key} ${typeof value === "string" ? key === "canonical" ? relative$3(value, origin) : value : "none"}`);
|
|
30205
|
+
}
|
|
30206
|
+
}
|
|
30390
30207
|
const linking = typeof details.linking_pages === "number";
|
|
30391
30208
|
const count = linking ? details.linking_pages : details.pages;
|
|
30392
30209
|
if (typeof count === "number") {
|
|
30393
|
-
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);
|
|
30394
30211
|
const noun = `${linking ? "linking page" : "page"}${count === 1 ? "" : "s"}`;
|
|
30395
30212
|
parts.push(`${count} ${noun}${shown.length ? ` (${shown.join(", ")}${count > shown.length ? ", and more" : ""})` : ""}`);
|
|
30396
30213
|
}
|
|
@@ -30400,13 +30217,13 @@ function state(finding, since) {
|
|
|
30400
30217
|
if (finding.state === "resolved") return "resolved";
|
|
30401
30218
|
if (finding.reopened_at && finding.reopened_at >= since) return "back";
|
|
30402
30219
|
if (finding.first_seen_at >= since) return "new";
|
|
30403
|
-
return `open since ${day$
|
|
30220
|
+
return `open since ${day$2(finding.first_seen_at)}`;
|
|
30404
30221
|
}
|
|
30405
30222
|
function table(rows, origin, since) {
|
|
30406
30223
|
return ["state | finding | subject | evidence", ...rows.map((finding) => [
|
|
30407
30224
|
state(finding, since),
|
|
30408
30225
|
finding.rule,
|
|
30409
|
-
relative$
|
|
30226
|
+
relative$3(finding.subject, origin),
|
|
30410
30227
|
evidence(finding, origin)
|
|
30411
30228
|
].join(" | "))].join("\n");
|
|
30412
30229
|
}
|
|
@@ -30417,6 +30234,14 @@ function ordered(findings, since) {
|
|
|
30417
30234
|
};
|
|
30418
30235
|
return [...findings].sort((a, b) => rank(a) - rank(b) || a.rule.localeCompare(b.rule) || (a.subject < b.subject ? -1 : a.subject > b.subject ? 1 : 0));
|
|
30419
30236
|
}
|
|
30237
|
+
function renderingText(rendering) {
|
|
30238
|
+
if (!rendering) return null;
|
|
30239
|
+
const pages = (n) => n === 1 ? "1 page" : `${n} pages`;
|
|
30240
|
+
if (rendering.mode === "on") return `JavaScript changed what some pages show, so the crawl rendered the parts of the site that need it (${pages(rendering.rendered)} rendered${rendering.failed ? `; ${pages(rendering.failed)} could not be rendered, so ${rendering.failed === 1 ? "it is" : "they are"} left out of the tag and text checks` : ""}).`;
|
|
30241
|
+
if (rendering.mode === "off") return "JavaScript did not change the pages checked, so the crawl read raw HTML.";
|
|
30242
|
+
if (rendering.mode === "unavailable") return "JavaScript rendering failed on the pages checked, so the crawl read raw HTML. If the site builds its pages with JavaScript, titles, canonicals and links may be missing here.";
|
|
30243
|
+
return rendering.failed && !rendering.rendered ? "JavaScript rendering failed, so the crawl read raw HTML." : null;
|
|
30244
|
+
}
|
|
30420
30245
|
function runAuditReport(input, snapshot, call) {
|
|
30421
30246
|
const site = input.site;
|
|
30422
30247
|
const detail = input.detail ?? "summary";
|
|
@@ -30463,9 +30288,10 @@ function runAuditReport(input, snapshot, call) {
|
|
|
30463
30288
|
const pages = summary.pages;
|
|
30464
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`;
|
|
30465
30290
|
const head = [
|
|
30466
|
-
`Weekly site audit of ${site}, crawled ${day$
|
|
30467
|
-
previous ? `Since the crawl of ${day$
|
|
30468
|
-
summary.
|
|
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.`,
|
|
30293
|
+
renderingText(summary.rendering),
|
|
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,
|
|
30469
30295
|
summary.findings.capped_rules.length ? `Capped at 1,000 findings each, so these counts are lower bounds: ${summary.findings.capped_rules.join(", ")}.` : null,
|
|
30470
30296
|
running
|
|
30471
30297
|
].filter(Boolean).join("\n");
|
|
@@ -30475,8 +30301,8 @@ function runAuditReport(input, snapshot, call) {
|
|
|
30475
30301
|
"change | url | date | reason now | top page",
|
|
30476
30302
|
...index.rows.map((row) => [
|
|
30477
30303
|
row.change === "dropped" ? "dropped out" : "back in",
|
|
30478
|
-
relative$
|
|
30479
|
-
day$
|
|
30304
|
+
relative$3(row.url, origin),
|
|
30305
|
+
day$2(row.at),
|
|
30480
30306
|
row.coverage ?? "",
|
|
30481
30307
|
row.top ? "yes" : "no"
|
|
30482
30308
|
].join(" | ")),
|
|
@@ -30484,7 +30310,7 @@ function runAuditReport(input, snapshot, call) {
|
|
|
30484
30310
|
].filter(Boolean).join("\n") : null;
|
|
30485
30311
|
if (detail === "rows") {
|
|
30486
30312
|
const offset = input.offset ?? 0;
|
|
30487
|
-
const slice = findings.slice(offset, offset + PAGE_SIZE$
|
|
30313
|
+
const slice = findings.slice(offset, offset + PAGE_SIZE$2);
|
|
30488
30314
|
const end = offset + slice.length;
|
|
30489
30315
|
return {
|
|
30490
30316
|
text: [
|
|
@@ -30518,7 +30344,7 @@ function runAuditReport(input, snapshot, call) {
|
|
|
30518
30344
|
head,
|
|
30519
30345
|
indexText,
|
|
30520
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.",
|
|
30521
|
-
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,
|
|
30522
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,
|
|
30523
30349
|
`More: ${call("report", {
|
|
30524
30350
|
site,
|
|
@@ -30532,7 +30358,7 @@ function runAuditReport(input, snapshot, call) {
|
|
|
30532
30358
|
site,
|
|
30533
30359
|
finished_at: snapshot.finished_at,
|
|
30534
30360
|
summary,
|
|
30535
|
-
fresh: fresh.slice(0, SUMMARY_ROWS)
|
|
30361
|
+
fresh: fresh.slice(0, SUMMARY_ROWS$1)
|
|
30536
30362
|
}
|
|
30537
30363
|
};
|
|
30538
30364
|
}
|
|
@@ -30540,12 +30366,12 @@ function auditPreview(snapshot) {
|
|
|
30540
30366
|
if (!snapshot?.summary || snapshot.finished_at === null) return snapshot ? "The first weekly crawl has not finished." : null;
|
|
30541
30367
|
const { findings } = snapshot.summary;
|
|
30542
30368
|
const index = snapshot.index_changes;
|
|
30543
|
-
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.`;
|
|
30544
30370
|
}
|
|
30545
30371
|
const WEEK_MS = 6048e5;
|
|
30546
|
-
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);
|
|
30547
30373
|
const count$2 = (value) => value.toLocaleString("en-US");
|
|
30548
|
-
function relative$
|
|
30374
|
+
function relative$2(value, origin) {
|
|
30549
30375
|
if (!value) return "";
|
|
30550
30376
|
return origin && value.startsWith(origin) ? value.slice(origin.length) || "/" : value;
|
|
30551
30377
|
}
|
|
@@ -30559,15 +30385,15 @@ function rowsTable(rows, checks) {
|
|
|
30559
30385
|
const since = checks.as_of - WEEK_MS;
|
|
30560
30386
|
const changed = (row) => row.changed_at !== null && row.changed_at > since;
|
|
30561
30387
|
return ["url | index change | reason now | before | changed | checked | clicks (28 days before) | in_sitemap | google_canonical | error", ...rows.map((row) => [
|
|
30562
|
-
relative$
|
|
30563
|
-
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)}` : "",
|
|
30564
30390
|
row.coverage ?? (row.checked_at === null ? "not checked yet" : ""),
|
|
30565
30391
|
changed(row) ? row.prev_coverage ?? "" : "",
|
|
30566
|
-
changed(row) ? day(row.changed_at) : "",
|
|
30567
|
-
day(row.checked_at),
|
|
30392
|
+
changed(row) ? day$1(row.changed_at) : "",
|
|
30393
|
+
day$1(row.checked_at),
|
|
30568
30394
|
`${count$2(row.clicks)} (${count$2(row.previous_clicks)})${row.lost ? " lost" : ""}`,
|
|
30569
30395
|
row.in_sitemap ? "yes" : "no",
|
|
30570
|
-
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) : "",
|
|
30571
30397
|
row.error ?? ""
|
|
30572
30398
|
].join(" | "))].join("\n");
|
|
30573
30399
|
}
|
|
@@ -30649,8 +30475,7 @@ function runAuditChecks(input, checks, call) {
|
|
|
30649
30475
|
}
|
|
30650
30476
|
};
|
|
30651
30477
|
}
|
|
30652
|
-
|
|
30653
|
-
//#region packages/core/src/commands/bing.ts
|
|
30478
|
+
// packages/core/src/commands/bing.ts
|
|
30654
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."];
|
|
30655
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.";
|
|
30656
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\`.`;
|
|
@@ -30966,8 +30791,7 @@ function bingOverviewText(report, heading, origin, rowsPerList) {
|
|
|
30966
30791
|
`Limits:\n${report.caveats.map((line) => `- ${line}`).join("\n")}`
|
|
30967
30792
|
].filter(Boolean).join("\n\n");
|
|
30968
30793
|
}
|
|
30969
|
-
|
|
30970
|
-
//#region packages/core/src/commands/clicky.ts
|
|
30794
|
+
// packages/core/src/commands/clicky.ts
|
|
30971
30795
|
const CLICKY_KEY_PREFIX = "x-clicky-";
|
|
30972
30796
|
function clickySites(keys) {
|
|
30973
30797
|
const sites = [];
|
|
@@ -31121,8 +30945,7 @@ async function runClickyRows(input, keys, options) {
|
|
|
31121
30945
|
}
|
|
31122
30946
|
};
|
|
31123
30947
|
}
|
|
31124
|
-
|
|
31125
|
-
//#region packages/core/src/commands/domain-rating.ts
|
|
30948
|
+
// packages/core/src/commands/domain-rating.ts
|
|
31126
30949
|
const DR_ATTRIBUTION = "domain_rating: Domain Rating by Ahrefs (https://ahrefs.com/).";
|
|
31127
30950
|
const ENDPOINT = "https://api.ahrefs.com/v3/public/domain-rating-free";
|
|
31128
30951
|
const MAX_DOMAINS = 25;
|
|
@@ -31383,9 +31206,9 @@ function indexingRow(e, sources, inspectedAt) {
|
|
|
31383
31206
|
...live.first.error ? { live_error: live.first.error } : {}
|
|
31384
31207
|
};
|
|
31385
31208
|
}
|
|
31386
|
-
const byText = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
31209
|
+
const byText$1 = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
31387
31210
|
function sortRows(rows) {
|
|
31388
|
-
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));
|
|
31389
31212
|
}
|
|
31390
31213
|
function byReason(rows) {
|
|
31391
31214
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -31399,7 +31222,7 @@ function byReason(rows) {
|
|
|
31399
31222
|
counts[row.label] += 1;
|
|
31400
31223
|
groups.set(row.reason, counts);
|
|
31401
31224
|
}
|
|
31402
|
-
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]) => ({
|
|
31403
31226
|
reason,
|
|
31404
31227
|
urls: counts.fix + counts.check + counts.expected + counts.indexed,
|
|
31405
31228
|
...counts
|
|
@@ -31413,7 +31236,7 @@ function knownUrls(input) {
|
|
|
31413
31236
|
if (!sources.includes(source)) sources.push(source);
|
|
31414
31237
|
found.set(url, sources);
|
|
31415
31238
|
};
|
|
31416
|
-
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));
|
|
31417
31240
|
for (const url of input.lost) add(url, "lost_clicks");
|
|
31418
31241
|
for (const page of pages) add(page.url, "search_console");
|
|
31419
31242
|
for (const url of input.sitemap.urls) add(url, "sitemap");
|
|
@@ -31504,7 +31327,7 @@ async function inspectKnown(site, known, deps, asked = 100) {
|
|
|
31504
31327
|
inspected: rows.length,
|
|
31505
31328
|
fromSaved: reused.length,
|
|
31506
31329
|
oldestSaved: oldest,
|
|
31507
|
-
failed: failed.sort((a, b) => byText(a.url, b.url)),
|
|
31330
|
+
failed: failed.sort((a, b) => byText$1(a.url, b.url)),
|
|
31508
31331
|
stoppedBy: quotaStop || quotaShort ? "quota" : known.urls.length > wanted.length ? "limit" : null,
|
|
31509
31332
|
quota
|
|
31510
31333
|
};
|
|
@@ -31571,8 +31394,441 @@ function indexingText(site, run, page, origin, askMore, now) {
|
|
|
31571
31394
|
if (!page.summary && more) lines.push(`More rows: ${page.more(end)}.`);
|
|
31572
31395
|
return lines;
|
|
31573
31396
|
}
|
|
31574
|
-
|
|
31575
|
-
|
|
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
|
|
31576
31832
|
const MAX_PAGES = 20;
|
|
31577
31833
|
const TIMEOUT_MS$2 = 6e3;
|
|
31578
31834
|
const MAX_BYTES$2 = 1e6;
|
|
@@ -31723,8 +31979,7 @@ function temporaryRedirectNote(pages) {
|
|
|
31723
31979
|
if (!temporary.length) return null;
|
|
31724
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("; ")}.`;
|
|
31725
31981
|
}
|
|
31726
|
-
|
|
31727
|
-
//#region packages/core/src/commands/report.ts
|
|
31982
|
+
// packages/core/src/commands/report.ts
|
|
31728
31983
|
const SECTIONS = {
|
|
31729
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.",
|
|
31730
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.",
|
|
@@ -32093,7 +32348,17 @@ async function allSections(input, deps) {
|
|
|
32093
32348
|
parts.push(`## ${AUDIT_SECTION}\n${audit ?? "This site is not in the weekly site audit."}`);
|
|
32094
32349
|
sections[AUDIT_SECTION] = { line: audit };
|
|
32095
32350
|
}
|
|
32096
|
-
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
|
+
];
|
|
32097
32362
|
parts.push(`More: ${deps.call("report", {
|
|
32098
32363
|
site: input.site,
|
|
32099
32364
|
section: "<section>"
|
|
@@ -32187,6 +32452,27 @@ async function oneSection(input, deps) {
|
|
|
32187
32452
|
};
|
|
32188
32453
|
}
|
|
32189
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
|
+
}
|
|
32190
32476
|
if (input.section === "weekly-audit") {
|
|
32191
32477
|
const audit = deps.audit;
|
|
32192
32478
|
if (!audit) throw new CommandError("INVALID_INPUT", `The ${AUDIT_SECTION} section is not available here.`);
|
|
@@ -32218,8 +32504,7 @@ async function runBingReport(input, deps) {
|
|
|
32218
32504
|
data: report
|
|
32219
32505
|
};
|
|
32220
32506
|
}
|
|
32221
|
-
|
|
32222
|
-
//#region packages/core/src/commands/research.ts
|
|
32507
|
+
// packages/core/src/commands/research.ts
|
|
32223
32508
|
const RESEARCH_ACTIONS = [
|
|
32224
32509
|
"keyword_metrics",
|
|
32225
32510
|
"ranked_keywords",
|
|
@@ -32324,8 +32609,7 @@ async function runResearch(input, keys, options) {
|
|
|
32324
32609
|
}
|
|
32325
32610
|
};
|
|
32326
32611
|
}
|
|
32327
|
-
|
|
32328
|
-
//#region packages/core/src/commands/sitemap-urls.ts
|
|
32612
|
+
// packages/core/src/commands/sitemap-urls.ts
|
|
32329
32613
|
const MAX_FILES = 20;
|
|
32330
32614
|
const MAX_URLS = 5e4;
|
|
32331
32615
|
const MAX_BYTES$1 = 2e7;
|
|
@@ -32438,8 +32722,7 @@ async function sitemapUrls(site) {
|
|
|
32438
32722
|
notes: [...new Set(notes)]
|
|
32439
32723
|
};
|
|
32440
32724
|
}
|
|
32441
|
-
|
|
32442
|
-
//#region packages/core/src/commands/site.ts
|
|
32725
|
+
// packages/core/src/commands/site.ts
|
|
32443
32726
|
function indexStatusFrom(result) {
|
|
32444
32727
|
const status = result?.inspectionResult?.indexStatusResult ?? result?.indexStatusResult ?? {};
|
|
32445
32728
|
const text = (value) => typeof value === "string" ? value : null;
|
|
@@ -32772,8 +33055,7 @@ async function runLostPages(input, deps) {
|
|
|
32772
33055
|
}
|
|
32773
33056
|
};
|
|
32774
33057
|
}
|
|
32775
|
-
|
|
32776
|
-
//#region packages/core/src/commands/week.ts
|
|
33058
|
+
// packages/core/src/commands/week.ts
|
|
32777
33059
|
const DAY_MS = 864e5;
|
|
32778
33060
|
function weekLabel(start, end) {
|
|
32779
33061
|
const parts = (date) => {
|
|
@@ -32795,6 +33077,7 @@ const siteName = (property) => property.replace(/^sc-domain:|^https?:\/\//, "").
|
|
|
32795
33077
|
const count = (n) => n.toLocaleString("en-US");
|
|
32796
33078
|
const plural$1 = (n, one, many = `${one}s`) => `${count(n)} ${n === 1 ? one : many}`;
|
|
32797
33079
|
const list = (items) => items.length <= 1 ? items[0] ?? "" : `${items.slice(0, -1).join(", ")} and ${items.at(-1)}`;
|
|
33080
|
+
const sentence = (text) => /[.!?]$/.test(text) ? text : `${text}.`;
|
|
32798
33081
|
function path(url) {
|
|
32799
33082
|
try {
|
|
32800
33083
|
const parsed = new URL(url);
|
|
@@ -32817,60 +33100,58 @@ const percent = (site) => {
|
|
|
32817
33100
|
return Math.round(Math.abs(clicks.clicks - clicks.previous) / clicks.previous * 100);
|
|
32818
33101
|
};
|
|
32819
33102
|
function issues(site, startMs, endMs) {
|
|
32820
|
-
const name = siteName(site.property);
|
|
32821
33103
|
const found = [];
|
|
33104
|
+
if (site.index?.dropped) found.push({
|
|
33105
|
+
rank: 0,
|
|
33106
|
+
heading: `${plural$1(site.index.dropped, "page")} dropped`
|
|
33107
|
+
});
|
|
33108
|
+
const crawl = site.crawl;
|
|
33109
|
+
const crawled = crawl !== null && crawl.finished_at >= startMs && crawl.finished_at < endMs;
|
|
33110
|
+
if (crawled && crawl.status === "failed") found.push({
|
|
33111
|
+
rank: 1,
|
|
33112
|
+
heading: "crawl stopped early"
|
|
33113
|
+
});
|
|
33114
|
+
else if (!crawled && site.added_at < startMs) found.push({
|
|
33115
|
+
rank: 1,
|
|
33116
|
+
heading: "no crawl this week"
|
|
33117
|
+
});
|
|
33118
|
+
if (clicksChange(site) === "fell") found.push({
|
|
33119
|
+
rank: 2,
|
|
33120
|
+
heading: `clicks down ${percent(site)}%`
|
|
33121
|
+
});
|
|
33122
|
+
if (crawled && crawl.new_issues > 0) found.push({
|
|
33123
|
+
rank: 3,
|
|
33124
|
+
heading: plural$1(crawl.new_issues, "new issue")
|
|
33125
|
+
});
|
|
33126
|
+
return found;
|
|
33127
|
+
}
|
|
33128
|
+
function siteLines(site, startMs, endMs) {
|
|
33129
|
+
const lines = [];
|
|
32822
33130
|
const index = site.index;
|
|
32823
|
-
if (index
|
|
33131
|
+
if (!index) lines.push("Google index: no checks ran this week yet.");
|
|
33132
|
+
else if (index.dropped) {
|
|
32824
33133
|
const paths = index.dropped_pages.map((page) => path(page.url));
|
|
32825
33134
|
const more = index.dropped - paths.length;
|
|
32826
33135
|
const shown = more > 0 ? `${paths.join(", ")} and ${plural$1(more, "more")}` : list(paths);
|
|
32827
33136
|
const reasons = new Set(index.dropped_pages.map((page) => page.coverage));
|
|
32828
33137
|
const reason = reasons.size === 1 ? [...reasons][0] : null;
|
|
32829
|
-
|
|
32830
|
-
|
|
32831
|
-
heading: `${plural$1(index.dropped, "page")} dropped`,
|
|
32832
|
-
line: `${shown} dropped out of Google’s index.${reason ? ` Google says: ${reason}.` : ""}`,
|
|
32833
|
-
next: `check ${index.dropped === 1 ? "it" : "they"} still return${index.dropped === 1 ? "s" : ""} 200 and ${index.dropped === 1 ? "is" : "are"} not noindexed.`,
|
|
32834
|
-
prompt: `Why did ${paths[0] ?? "a page"} on ${name} drop out of Google’s index this week, and how do I fix it?`
|
|
32835
|
-
});
|
|
32836
|
-
}
|
|
33138
|
+
lines.push(`Google index: ${plural$1(index.dropped, "page")} dropped out (${shown}).${reason ? ` Google says: ${reason}.` : ""}${index.returned ? ` ${plural$1(index.returned, "page")} came back.` : ""} ${plural$1(index.checked, "page")} checked.`);
|
|
33139
|
+
} else lines.push(`Google index: ${plural$1(index.checked, "page")} checked, none dropped${index.returned ? `, ${plural$1(index.returned, "page")} came back` : ""}.`);
|
|
32837
33140
|
const crawl = site.crawl;
|
|
32838
33141
|
const crawled = crawl !== null && crawl.finished_at >= startMs && crawl.finished_at < endMs;
|
|
32839
|
-
if (crawled && crawl.status === "failed")
|
|
32840
|
-
|
|
32841
|
-
heading: "crawl stopped early",
|
|
32842
|
-
line: `The weekly crawl stopped after ${plural$1(crawl.pages, "page")}${crawl.error ? `: ${crawl.error}` : "."}`,
|
|
32843
|
-
next: "check the site is up. The next weekly crawl tries again.",
|
|
32844
|
-
prompt: `Why did the weekly crawl of ${name} stop early, and what should I check?`
|
|
32845
|
-
});
|
|
32846
|
-
else if (!crawled && site.added_at < startMs) found.push({
|
|
32847
|
-
rank: 1,
|
|
32848
|
-
heading: "no crawl this week",
|
|
32849
|
-
line: `No weekly crawl finished this week${site.start_error ? `: ${site.start_error}` : "."}`,
|
|
32850
|
-
next: "check the site is up. The crawl tries again each hour.",
|
|
32851
|
-
prompt: `Why has the weekly crawl of ${name} not finished, and what should I check?`
|
|
32852
|
-
});
|
|
32853
|
-
if (clicksChange(site) === "fell") {
|
|
32854
|
-
const clicks = site.clicks;
|
|
32855
|
-
found.push({
|
|
32856
|
-
rank: 2,
|
|
32857
|
-
heading: `clicks down ${percent(site)}%`,
|
|
32858
|
-
line: `Search clicks fell ${percent(site)}%, from ${count(clicks.previous)} to ${count(clicks.clicks)} (${weekLabel(clicks.start, clicks.end)}, against the 7 days before).`,
|
|
32859
|
-
next: "ask your agent which pages and queries lost them.",
|
|
32860
|
-
prompt: `Which pages and queries lost search clicks on ${name} in the last week, and why?`
|
|
32861
|
-
});
|
|
32862
|
-
}
|
|
32863
|
-
if (crawled && crawl.new_issues > 0) {
|
|
33142
|
+
if (crawled && crawl.status === "failed") lines.push(`Weekly crawl: stopped after ${plural$1(crawl.pages, "page")}${crawl.error ? `: ${sentence(crawl.error)}` : "."} It tries again next week.`);
|
|
33143
|
+
else if (crawled) {
|
|
32864
33144
|
const rule = crawl.top_rule ? AUDIT_RULES[crawl.top_rule] : void 0;
|
|
32865
|
-
|
|
32866
|
-
|
|
32867
|
-
|
|
32868
|
-
|
|
32869
|
-
|
|
32870
|
-
|
|
32871
|
-
})
|
|
32872
|
-
|
|
32873
|
-
|
|
33145
|
+
lines.push(crawl.new_issues ? `Weekly crawl: ${plural$1(crawl.pages, "page")}, ${plural$1(crawl.new_issues, "new issue")}${rule ? `, most of them ${rule.charAt(0).toLowerCase()}${rule.slice(1)}` : ""}.` : `Weekly crawl: ${plural$1(crawl.pages, "page")}, no new issues.`);
|
|
33146
|
+
} else if (site.added_at < startMs) lines.push(`Weekly crawl: none finished this week${site.start_error ? `: ${sentence(site.start_error)}` : "."} It tries again each hour.`);
|
|
33147
|
+
else lines.push("Weekly crawl: the first crawl is still running.");
|
|
33148
|
+
const clicks = site.clicks;
|
|
33149
|
+
if (clicks) {
|
|
33150
|
+
const change = clicks.previous > 0 ? Math.round((clicks.clicks - clicks.previous) / clicks.previous * 100) : null;
|
|
33151
|
+
const trend = change === null ? `from ${count(clicks.previous)}` : change === 0 ? "level with" : `${change > 0 ? "up" : "down"} ${Math.abs(change)}% on`;
|
|
33152
|
+
lines.push(`Search clicks: ${count(clicks.clicks)} (${weekLabel(clicks.start, clicks.end)}), ${trend}${change === null ? "" : ` ${count(clicks.previous)}`} the 7 days before.`);
|
|
33153
|
+
} else lines.push("Search clicks: not available this week.");
|
|
33154
|
+
return lines;
|
|
32874
33155
|
}
|
|
32875
33156
|
function weekMarkdown(data) {
|
|
32876
33157
|
const startMs = Date.parse(`${data.start}T00:00:00Z`);
|
|
@@ -32878,43 +33159,21 @@ function weekMarkdown(data) {
|
|
|
32878
33159
|
const title = `# ${weekTitle(data)}`;
|
|
32879
33160
|
if (!data.sites.length) return `${title}\n\nNo site is in your weekly audit yet. Add one on the Sites tab: ${data.account}#sites`;
|
|
32880
33161
|
const byName = (a, b) => siteName(a.property).localeCompare(siteName(b.property)) || a.property.localeCompare(b.property);
|
|
32881
|
-
const
|
|
32882
|
-
|
|
32883
|
-
|
|
32884
|
-
|
|
32885
|
-
|
|
32886
|
-
|
|
32887
|
-
|
|
32888
|
-
|
|
32889
|
-
|
|
32890
|
-
});
|
|
32891
|
-
else if (!site.crawl || !site.index) waiting.push(site);
|
|
32892
|
-
else quiet.push(site);
|
|
32893
|
-
}
|
|
32894
|
-
attention.sort((a, b) => a.found[0].rank - b.found[0].rank || byName(a.site, b.site));
|
|
32895
|
-
const total = sites.length;
|
|
33162
|
+
const rows = data.sites.map((site) => ({
|
|
33163
|
+
site,
|
|
33164
|
+
found: issues(site, startMs, endMs),
|
|
33165
|
+
waiting: !site.crawl || !site.index
|
|
33166
|
+
}));
|
|
33167
|
+
const attention = rows.filter((row) => row.found.length);
|
|
33168
|
+
const quiet = rows.filter((row) => !row.found.length && !row.waiting);
|
|
33169
|
+
rows.sort((a, b) => (a.found[0]?.rank ?? Number.POSITIVE_INFINITY) - (b.found[0]?.rank ?? Number.POSITIVE_INFINITY) || byName(a.site, b.site));
|
|
33170
|
+
const total = rows.length;
|
|
32896
33171
|
const out = [
|
|
32897
33172
|
title,
|
|
32898
33173
|
"",
|
|
32899
|
-
attention.length ? total === 1 ? "Your site
|
|
33174
|
+
attention.length ? total === 1 ? "Your site had changes worth a look this week." : `${attention.length} of your ${total} sites had changes worth a look this week.` : quiet.length ? total === 1 ? "Your site had a quiet week." : quiet.length === total ? `All ${total} of your sites had a quiet week.` : `${quiet.length} of your ${total} sites had a quiet week.` : "Your sites are still on their first checks."
|
|
32900
33175
|
];
|
|
32901
|
-
|
|
32902
|
-
out.push("", "## Needs attention");
|
|
32903
|
-
for (const { site, found } of attention) {
|
|
32904
|
-
const [first, ...rest] = found;
|
|
32905
|
-
const also = rest.length ? ` Also: ${list(rest.map((item) => item.heading))}.` : "";
|
|
32906
|
-
const rose = clicksChange(site) === "rose" ? ` Search clicks rose ${percent(site)}%.` : "";
|
|
32907
|
-
out.push("", `### ${siteName(site.property)}: ${first.heading}`, "", `- ${first.line}${also}${rose}`, `- Next step: ${first.next}`, ...site.link ? [`- [View site](${site.link})`] : []);
|
|
32908
|
-
}
|
|
32909
|
-
}
|
|
32910
|
-
if (quiet.length) {
|
|
32911
|
-
const risen = quiet.filter((site) => clicksChange(site) === "rose");
|
|
32912
|
-
out.push("", "## All quiet", "", `${list(quiet.map((site) => siteName(site.property)))}: no drops, no new issues.${risen.map((site) => ` Search clicks rose ${percent(site)}% on ${siteName(site.property)}.`).join("")}`);
|
|
32913
|
-
}
|
|
32914
|
-
if (waiting.length) out.push("", "## Not checked yet", "", `${list(waiting.map((site) => siteName(site.property)))}: the first crawl or Google checks are still running.`);
|
|
32915
|
-
const first = attention[0];
|
|
32916
|
-
const prompt = first ? first.found[0].prompt : `What are the best quick wins on ${siteName((quiet[0] ?? sites[0]).property)} this month?`;
|
|
32917
|
-
out.push("", "## Ask your agent", "", "Paste this into your agent:", "", "```", prompt, "```");
|
|
33176
|
+
for (const { site, found } of rows) out.push("", `## ${siteName(site.property)}${found.length ? `: ${list(found.map((item) => item.heading))}` : ""}`, "", ...siteLines(site, startMs, endMs).map((line) => `- ${line}`), ...site.link ? [`- [View site](${site.link})`] : []);
|
|
32918
33177
|
return out.join("\n");
|
|
32919
33178
|
}
|
|
32920
33179
|
async function runAllSites(deps, sites) {
|
|
@@ -32937,8 +33196,7 @@ async function runAllSites(deps, sites) {
|
|
|
32937
33196
|
data
|
|
32938
33197
|
};
|
|
32939
33198
|
}
|
|
32940
|
-
|
|
32941
|
-
//#region packages/core/src/commands/tools.ts
|
|
33199
|
+
// packages/core/src/commands/tools.ts
|
|
32942
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.");
|
|
32943
33201
|
const dates = {
|
|
32944
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."),
|
|
@@ -32952,6 +33210,12 @@ const detail = {
|
|
|
32952
33210
|
]).default("summary").describe("summary first. rows: the next page of rows. full: every field as JSON."),
|
|
32953
33211
|
offset: z.number().int().min(0).default(0).describe("rows only: the first row to return.")
|
|
32954
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
|
+
};
|
|
32955
33219
|
const DATA_SOURCES = [
|
|
32956
33220
|
"search_console",
|
|
32957
33221
|
"google_analytics",
|
|
@@ -32974,20 +33238,44 @@ const SHARED_TOOLS = [
|
|
|
32974
33238
|
name: "seo_sites",
|
|
32975
33239
|
command: "data",
|
|
32976
33240
|
title: "List sites",
|
|
32977
|
-
description: "Lists the Search Console and Google Analytics properties the connected Google accounts can read, the Bing Webmaster site for each, and which research keys are set. Call it first.",
|
|
32978
|
-
input: z.object({}),
|
|
32979
|
-
run:
|
|
33241
|
+
description: "Lists the Search Console and Google Analytics properties the connected Google accounts can read, the Bing Webmaster site for each, and which research keys are set. Call it first. With add: starts the weekly site audit of that site on the hosted server (a weekly crawl, daily Google index checks, a Monday email and webhook with what changed, and alerts when a page drops out of Google's index). A site that needs paying for returns a Stripe Checkout link: give it to the person to open, and the audit starts once they pay.",
|
|
33242
|
+
input: z.object({ add: site.optional().describe("Optional. A site to add to the weekly site audit. Only when the person asks for it: a site can cost money.") }),
|
|
33243
|
+
run: async (surface, input) => {
|
|
33244
|
+
if (!input.add) return listSites$1(surface);
|
|
33245
|
+
const start = surface.deps.startAudit;
|
|
33246
|
+
if (!start) throw new CommandError("INVALID_INPUT", "The weekly site audit runs on the hosted server only. Connect https://seoskill.dev/mcp and call seo_sites with add there.");
|
|
33247
|
+
const property = await surface.resolveSite(input.add);
|
|
33248
|
+
const result = await start(property);
|
|
33249
|
+
return {
|
|
33250
|
+
text: {
|
|
33251
|
+
added: `Added ${property} to the weekly site audit. Its first crawl and index checks have started; ask ${surface.deps.call("report", {
|
|
33252
|
+
site: property,
|
|
33253
|
+
section: AUDIT_SECTION
|
|
33254
|
+
})} for the findings once the crawl is done.`,
|
|
33255
|
+
exists: `${property} is already in the weekly site audit.`,
|
|
33256
|
+
pending: "The payment went through and the plan is starting. Call seo_sites with add again in a minute.",
|
|
33257
|
+
checkout: result.status === "checkout" ? `${property} needs a plan: $${result.price} a month per site, cancel at any time. Give the person this Stripe Checkout link to open and pay: ${result.url}\nThe audit of ${property} starts once they pay. Call seo_sites with add again afterwards to confirm.` : "",
|
|
33258
|
+
full: result.status === "full" ? `This account already audits ${result.limit} sites, the most it can. Remove one on the account page first.` : "",
|
|
33259
|
+
payment_failed: `Stripe could not take the payment for another site, so ${property} was not added. The person can check their card under Manage billing on the account page.`
|
|
33260
|
+
}[result.status],
|
|
33261
|
+
data: {
|
|
33262
|
+
property,
|
|
33263
|
+
...result
|
|
33264
|
+
}
|
|
33265
|
+
};
|
|
33266
|
+
}
|
|
32980
33267
|
}),
|
|
32981
33268
|
tool({
|
|
32982
33269
|
name: "seo_report",
|
|
32983
33270
|
command: "report",
|
|
32984
33271
|
title: "Search Console report",
|
|
32985
|
-
description: `Start here. Without site: a short summary of every site in the person's weekly site audit (beta) for last week, with
|
|
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.`,
|
|
32986
33273
|
input: z.object({
|
|
32987
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."),
|
|
32988
|
-
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."),
|
|
32989
33276
|
...dates,
|
|
32990
|
-
...detail
|
|
33277
|
+
...detail,
|
|
33278
|
+
...marks
|
|
32991
33279
|
}),
|
|
32992
33280
|
run: async (surface, input) => {
|
|
32993
33281
|
if (!input.site) {
|
|
@@ -32995,9 +33283,22 @@ const SHARED_TOOLS = [
|
|
|
32995
33283
|
site: "<site>",
|
|
32996
33284
|
section: input.section
|
|
32997
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
|
+
})}.`);
|
|
32998
33291
|
return runAllSites(surface.deps, () => listSites$1(surface));
|
|
32999
33292
|
}
|
|
33000
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);
|
|
33001
33302
|
const resolved = await surface.resolveSite(target).catch((error) => error instanceof Error ? error : new Error(String(error)));
|
|
33002
33303
|
if (resolved instanceof Error && !input.section && surface.deps.bing) return runBingReport({
|
|
33003
33304
|
site: target,
|
|
@@ -33013,7 +33314,8 @@ const SHARED_TOOLS = [
|
|
|
33013
33314
|
startDate: input.start_date,
|
|
33014
33315
|
endDate: input.end_date,
|
|
33015
33316
|
detail: input.detail,
|
|
33016
|
-
offset: input.offset
|
|
33317
|
+
offset: input.offset,
|
|
33318
|
+
saved: input.saved
|
|
33017
33319
|
}, surface.deps);
|
|
33018
33320
|
}
|
|
33019
33321
|
}),
|
|
@@ -33208,10 +33510,13 @@ function indexFromStore(shared) {
|
|
|
33208
33510
|
SHARED_TOOLS.map((shared) => shared.name === "seo_index" ? indexFromStore(shared) : shared.name === "seo_report" ? {
|
|
33209
33511
|
...shared,
|
|
33210
33512
|
description: `${shared.description}\n- ${AUDIT_SECTION}: ${AUDIT_SECTION_TEXT}`,
|
|
33211
|
-
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.") })
|
|
33212
33518
|
} : shared);
|
|
33213
|
-
|
|
33214
|
-
//#region packages/core/src/gsc/client/inspection-cache.ts
|
|
33519
|
+
// packages/core/src/gsc/client/inspection-cache.ts
|
|
33215
33520
|
const INSPECTION_FRESH_MS = 6048e5;
|
|
33216
33521
|
function readSavedInspections(site, urls, now = Date.now()) {
|
|
33217
33522
|
const found = /* @__PURE__ */ new Map();
|
|
@@ -33240,8 +33545,7 @@ function saveInspection(site, url, status, now = Date.now()) {
|
|
|
33240
33545
|
noteCacheWrite(Buffer.byteLength(json) + url.length, database);
|
|
33241
33546
|
});
|
|
33242
33547
|
}
|
|
33243
|
-
|
|
33244
|
-
//#region packages/core/src/providers/api-key-credentials.ts
|
|
33548
|
+
// packages/core/src/providers/api-key-credentials.ts
|
|
33245
33549
|
function createApiKeyCredentialStore(input) {
|
|
33246
33550
|
const normalize = (value) => {
|
|
33247
33551
|
const apiKey = value.trim();
|
|
@@ -33273,8 +33577,7 @@ function createApiKeyCredentialStore(input) {
|
|
|
33273
33577
|
}
|
|
33274
33578
|
};
|
|
33275
33579
|
}
|
|
33276
|
-
|
|
33277
|
-
//#region packages/core/src/providers/ahrefs/credentials.ts
|
|
33580
|
+
// packages/core/src/providers/ahrefs/credentials.ts
|
|
33278
33581
|
const AHREFS_API_KEY_ENV = "SEO_AHREFS_API_KEY";
|
|
33279
33582
|
const AHREFS_API_KEY_SECRET = PROVIDER_SECRET_NAMES.ahrefsApiKey;
|
|
33280
33583
|
const store$1 = createApiKeyCredentialStore({
|
|
@@ -33292,8 +33595,7 @@ async function readAhrefsApiKey(input = {}) {
|
|
|
33292
33595
|
async function deleteAhrefsApiKey() {
|
|
33293
33596
|
await store$1.remove();
|
|
33294
33597
|
}
|
|
33295
|
-
|
|
33296
|
-
//#region packages/core/src/providers/bing/credentials.ts
|
|
33598
|
+
// packages/core/src/providers/bing/credentials.ts
|
|
33297
33599
|
const BING_API_KEY_ENV = "SEO_BING_API_KEY";
|
|
33298
33600
|
const BING_API_KEY_SECRET = PROVIDER_SECRET_NAMES.bingApiKey;
|
|
33299
33601
|
const store = createApiKeyCredentialStore({
|
|
@@ -33311,8 +33613,7 @@ async function readBingApiKey(input = {}) {
|
|
|
33311
33613
|
async function deleteBingApiKey() {
|
|
33312
33614
|
await store.remove();
|
|
33313
33615
|
}
|
|
33314
|
-
|
|
33315
|
-
//#region packages/core/src/providers/dataforseo/credentials.ts
|
|
33616
|
+
// packages/core/src/providers/dataforseo/credentials.ts
|
|
33316
33617
|
const DATAFORSEO_LOGIN_ENV = "SEO_DATAFORSEO_LOGIN";
|
|
33317
33618
|
const DATAFORSEO_PASSWORD_ENV = "SEO_DATAFORSEO_PASSWORD";
|
|
33318
33619
|
const DATAFORSEO_CREDENTIAL_SECRET = PROVIDER_SECRET_NAMES.dataForSeoCredentials;
|
|
@@ -33427,8 +33728,24 @@ async function deleteDataForSeoCredentials() {
|
|
|
33427
33728
|
await deleteProviderSecret(DATAFORSEO_CREDENTIAL_SECRET);
|
|
33428
33729
|
clearLegacyConfigCredentials();
|
|
33429
33730
|
}
|
|
33430
|
-
|
|
33431
|
-
|
|
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
|
|
33432
33749
|
function nodeCommandDeps(options) {
|
|
33433
33750
|
return {
|
|
33434
33751
|
searchAnalytics: (site, body) => querySearchAnalytics(site, body, {
|
|
@@ -33458,6 +33775,7 @@ function nodeCommandDeps(options) {
|
|
|
33458
33775
|
accounts: localAccounts,
|
|
33459
33776
|
signIn: LOCAL_SIGN_IN.signIn
|
|
33460
33777
|
},
|
|
33778
|
+
issues: localIssues,
|
|
33461
33779
|
...cloudLinked() ? { week: cloudWeek } : {}
|
|
33462
33780
|
};
|
|
33463
33781
|
}
|
|
@@ -33543,8 +33861,7 @@ async function localGoogleFetch(account) {
|
|
|
33543
33861
|
const { client } = await getAuthorized(account);
|
|
33544
33862
|
return googleFetchFor(client);
|
|
33545
33863
|
}
|
|
33546
|
-
|
|
33547
|
-
//#region packages/core/src/doctor.ts
|
|
33864
|
+
// packages/core/src/doctor.ts
|
|
33548
33865
|
const REQUIRED_SCOPES = ["https://www.googleapis.com/auth/webmasters.readonly", "https://www.googleapis.com/auth/analytics.readonly"];
|
|
33549
33866
|
async function runDoctor(dependencies = {}) {
|
|
33550
33867
|
const paths = getSeoCliPaths();
|
|
@@ -33612,8 +33929,7 @@ async function runDoctor(dependencies = {}) {
|
|
|
33612
33929
|
checks
|
|
33613
33930
|
};
|
|
33614
33931
|
}
|
|
33615
|
-
|
|
33616
|
-
//#region packages/core/src/export/update-postmortem.ts
|
|
33932
|
+
// packages/core/src/export/update-postmortem.ts
|
|
33617
33933
|
const HEADERS = {
|
|
33618
33934
|
summary: [
|
|
33619
33935
|
"workflow",
|
|
@@ -33935,8 +34251,7 @@ function updatePostmortemCsvFiles(report) {
|
|
|
33935
34251
|
}))
|
|
33936
34252
|
];
|
|
33937
34253
|
}
|
|
33938
|
-
|
|
33939
|
-
//#region packages/core/src/gsc/local-export.ts
|
|
34254
|
+
// packages/core/src/gsc/local-export.ts
|
|
33940
34255
|
const DEFAULT_SEARCH_CONSOLE_EXPORT_ROW_LIMIT = 5e3;
|
|
33941
34256
|
const MAX_SEARCH_CONSOLE_EXPORT_ROW_LIMIT = 1e5;
|
|
33942
34257
|
const MAX_SEARCH_CONSOLE_EXPORT_FILES = 20;
|
|
@@ -34214,8 +34529,7 @@ async function loadSearchConsoleExport(input) {
|
|
|
34214
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."]
|
|
34215
34530
|
};
|
|
34216
34531
|
}
|
|
34217
|
-
|
|
34218
|
-
//#region packages/core/src/gsc/local-export-reconcile.ts
|
|
34532
|
+
// packages/core/src/gsc/local-export-reconcile.ts
|
|
34219
34533
|
const DEFAULT_UNREACHED_PAGE_LIMIT = 100;
|
|
34220
34534
|
function unreachedLimit(value) {
|
|
34221
34535
|
const result = value ?? 100;
|
|
@@ -34272,8 +34586,7 @@ function reconcileExportPagesWithCrawl(input) {
|
|
|
34272
34586
|
capped: unreached.length > limit
|
|
34273
34587
|
};
|
|
34274
34588
|
}
|
|
34275
|
-
|
|
34276
|
-
//#region packages/core/src/storage/log-retention.ts
|
|
34589
|
+
// packages/core/src/storage/log-retention.ts
|
|
34277
34590
|
const MEBIBYTE = 1048576;
|
|
34278
34591
|
const LOG_MAX_FILE_BYTES = 8 * MEBIBYTE;
|
|
34279
34592
|
const LOG_MAX_TOTAL_BYTES = 64 * MEBIBYTE;
|
|
@@ -34336,8 +34649,7 @@ function pruneLogs(options = {}) {
|
|
|
34336
34649
|
sizeBytes
|
|
34337
34650
|
};
|
|
34338
34651
|
}
|
|
34339
|
-
|
|
34340
|
-
//#region packages/core/src/logger.ts
|
|
34652
|
+
// packages/core/src/logger.ts
|
|
34341
34653
|
let loggerInstance;
|
|
34342
34654
|
function createLogger(runId) {
|
|
34343
34655
|
if (loggerInstance) return loggerInstance.child({ runId });
|
|
@@ -34355,8 +34667,7 @@ function createLogger(runId) {
|
|
|
34355
34667
|
function makeRunId() {
|
|
34356
34668
|
return Math.random().toString(36).slice(2, 10);
|
|
34357
34669
|
}
|
|
34358
|
-
|
|
34359
|
-
//#region packages/core/src/provider-extensions/actions.ts
|
|
34670
|
+
// packages/core/src/provider-extensions/actions.ts
|
|
34360
34671
|
const MAX_ACTION_PARAM_BYTES = 65536;
|
|
34361
34672
|
const MAX_ACTION_PARAM_DEPTH = 16;
|
|
34362
34673
|
const MAX_ACTION_PARAM_VALUES = 5e3;
|
|
@@ -34489,8 +34800,7 @@ async function runProviderExtensionAction(input) {
|
|
|
34489
34800
|
data
|
|
34490
34801
|
};
|
|
34491
34802
|
}
|
|
34492
|
-
|
|
34493
|
-
//#region packages/core/src/provider-extensions/connections.ts
|
|
34803
|
+
// packages/core/src/provider-extensions/connections.ts
|
|
34494
34804
|
function readProviderExtensionAccount(providerIdValue) {
|
|
34495
34805
|
const providerId = providerExtensionIdSchema.parse(providerIdValue);
|
|
34496
34806
|
return { ...readConfig().providers.connections?.[providerId] ?? {} };
|
|
@@ -34524,8 +34834,7 @@ function deleteProviderExtensionAccount(providerIdValue) {
|
|
|
34524
34834
|
}
|
|
34525
34835
|
});
|
|
34526
34836
|
}
|
|
34527
|
-
|
|
34528
|
-
//#region packages/core/src/provider-extensions/discovery.ts
|
|
34837
|
+
// packages/core/src/provider-extensions/discovery.ts
|
|
34529
34838
|
async function listInstalledProviderExtensions() {
|
|
34530
34839
|
const installed = readInstalledProviderPackages().packages;
|
|
34531
34840
|
const loaded = await loadInstalledProviderExtensions({ installed });
|
|
@@ -34572,8 +34881,7 @@ async function describeInstalledProviderExtension(id) {
|
|
|
34572
34881
|
}))
|
|
34573
34882
|
};
|
|
34574
34883
|
}
|
|
34575
|
-
|
|
34576
|
-
//#region packages/core/src/provider-extensions/installer.ts
|
|
34884
|
+
// packages/core/src/provider-extensions/installer.ts
|
|
34577
34885
|
const execFileAsync = promisify(execFile);
|
|
34578
34886
|
const MAX_PROVIDER_UNPACKED_BYTES = 5242880;
|
|
34579
34887
|
const MAX_NPM_OUTPUT_BYTES = 1048576;
|
|
@@ -34785,8 +35093,7 @@ async function uninstallProviderExtension(id, options = {}) {
|
|
|
34785
35093
|
], { cwd: packagesDir });
|
|
34786
35094
|
return removeInstalledProviderPackage(id);
|
|
34787
35095
|
}
|
|
34788
|
-
|
|
34789
|
-
//#region packages/core/src/providers/ahrefs/schema.ts
|
|
35096
|
+
// packages/core/src/providers/ahrefs/schema.ts
|
|
34790
35097
|
const ahrefsLimitsAndUsageResponseSchema = z.object({ limits_and_usage: z.object({
|
|
34791
35098
|
api_key_expiration_date: z.string().trim().min(1).max(100),
|
|
34792
35099
|
subscription: z.string().trim().min(1).max(200),
|
|
@@ -34796,8 +35103,7 @@ const ahrefsLimitsAndUsageResponseSchema = z.object({ limits_and_usage: z.object
|
|
|
34796
35103
|
units_usage_workspace: z.number().int().nonnegative().nullable(),
|
|
34797
35104
|
usage_reset_date: z.string().trim().min(1).max(100)
|
|
34798
35105
|
}).strict() }).strict();
|
|
34799
|
-
|
|
34800
|
-
//#region packages/core/src/providers/ahrefs/client.ts
|
|
35106
|
+
// packages/core/src/providers/ahrefs/client.ts
|
|
34801
35107
|
const DEFAULT_BASE_URL$1 = "https://api.ahrefs.com/v3/";
|
|
34802
35108
|
const LIMITS_AND_USAGE_PATH = "subscription-info/limits-and-usage";
|
|
34803
35109
|
const DEFAULT_TIMEOUT_MS$1 = 1e4;
|
|
@@ -34884,8 +35190,7 @@ var AhrefsClient = class {
|
|
|
34884
35190
|
return this.limitsAndUsageForKey(await this.getApiKey("limits-and-usage"));
|
|
34885
35191
|
}
|
|
34886
35192
|
};
|
|
34887
|
-
|
|
34888
|
-
//#region packages/core/src/providers/dataforseo/account-schema.ts
|
|
35193
|
+
// packages/core/src/providers/dataforseo/account-schema.ts
|
|
34889
35194
|
const userDataMoneySchema = z.object({
|
|
34890
35195
|
total: z.number().finite().nonnegative().optional(),
|
|
34891
35196
|
balance: z.number().finite().optional()
|
|
@@ -34912,8 +35217,7 @@ const dataForSeoUserDataResponseSchema = z.object({
|
|
|
34912
35217
|
tasks_error: z.number().int().nonnegative(),
|
|
34913
35218
|
tasks: z.array(userDataTaskSchema).max(10)
|
|
34914
35219
|
}).passthrough();
|
|
34915
|
-
|
|
34916
|
-
//#region packages/core/src/providers/dataforseo/response.ts
|
|
35220
|
+
// packages/core/src/providers/dataforseo/response.ts
|
|
34917
35221
|
function dataForSeoUsdToMicros(value) {
|
|
34918
35222
|
if (value === void 0) return null;
|
|
34919
35223
|
return Math.round(value * 1e6);
|
|
@@ -34923,8 +35227,7 @@ function dataForSeoTaskErrorCode(statusCode) {
|
|
|
34923
35227
|
if (statusCode === 40202) return "rate-limit";
|
|
34924
35228
|
return "remote-error";
|
|
34925
35229
|
}
|
|
34926
|
-
|
|
34927
|
-
//#region packages/core/src/providers/dataforseo/client.ts
|
|
35230
|
+
// packages/core/src/providers/dataforseo/client.ts
|
|
34928
35231
|
const DEFAULT_BASE_URL = "https://api.dataforseo.com/";
|
|
34929
35232
|
const USER_DATA_PATH = "v3/appendix/user_data";
|
|
34930
35233
|
const DEFAULT_TIMEOUT_MS = 15e3;
|
|
@@ -35019,8 +35322,7 @@ var DataForSeoClient = class {
|
|
|
35019
35322
|
return this.userDataForCredentials(await this.getCredentials("account-status"));
|
|
35020
35323
|
}
|
|
35021
35324
|
};
|
|
35022
|
-
|
|
35023
|
-
//#region packages/core/src/runtime/interfaces.ts
|
|
35325
|
+
// packages/core/src/runtime/interfaces.ts
|
|
35024
35326
|
var LocalCredentialsProvider = class {
|
|
35025
35327
|
getGoogleOAuthClient() {
|
|
35026
35328
|
const byo = readOauthClient();
|
|
@@ -35086,8 +35388,621 @@ var JsonFileStorageAdapter = class {
|
|
|
35086
35388
|
}
|
|
35087
35389
|
};
|
|
35088
35390
|
const localCredentialsProvider = new LocalCredentialsProvider();
|
|
35089
|
-
|
|
35090
|
-
|
|
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
|
|
35091
36006
|
async function resetSeoData() {
|
|
35092
36007
|
const failures = (await Promise.allSettled([deleteTokens(), deleteManagedProviderSecrets()])).flatMap((result) => result.status === "rejected" ? [result.reason] : []);
|
|
35093
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) });
|
|
@@ -35105,8 +36020,7 @@ async function resetSeoData() {
|
|
|
35105
36020
|
force: true
|
|
35106
36021
|
});
|
|
35107
36022
|
}
|
|
35108
|
-
|
|
35109
|
-
//#region packages/core/src/update-notice.ts
|
|
36023
|
+
// packages/core/src/update-notice.ts
|
|
35110
36024
|
const REGISTRY_URL = "https://registry.npmjs.org/seo/latest";
|
|
35111
36025
|
const CHECK_EVERY_MS = 864e5;
|
|
35112
36026
|
const TIMEOUT_MS = 2e3;
|
|
@@ -35193,8 +36107,7 @@ function updateNotice(options = {}) {
|
|
|
35193
36107
|
return;
|
|
35194
36108
|
}
|
|
35195
36109
|
}
|
|
35196
|
-
|
|
35197
|
-
//#region packages/core/src/utils/time.ts
|
|
36110
|
+
// packages/core/src/utils/time.ts
|
|
35198
36111
|
function parseOlderThan(input) {
|
|
35199
36112
|
if (!input) return;
|
|
35200
36113
|
const match = input.trim().match(/^(\d+)([smhdw])$/i);
|
|
@@ -35227,7 +36140,6 @@ function isoDate(daysAgo = 0) {
|
|
|
35227
36140
|
date.setUTCDate(date.getUTCDate() - daysAgo);
|
|
35228
36141
|
return date.toISOString().slice(0, 10);
|
|
35229
36142
|
}
|
|
35230
|
-
|
|
35231
|
-
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 };
|
|
35232
36144
|
|
|
35233
36145
|
//# sourceMappingURL=index.js.map
|