@prenta/core 5.3.1 → 5.4.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/CHANGELOG.md +26 -0
- package/dist/ai/content-writing-generate.d.ts.map +1 -1
- package/dist/ai/content-writing-generate.js +3 -0
- package/dist/ai/content-writing-generate.js.map +1 -1
- package/dist/ai/feature-defaults.d.ts +1 -1
- package/dist/ai/feature-defaults.d.ts.map +1 -1
- package/dist/ai/feature-defaults.js +3 -13
- package/dist/ai/feature-defaults.js.map +1 -1
- package/dist/ai/governance.d.ts +31 -0
- package/dist/ai/governance.d.ts.map +1 -0
- package/dist/ai/governance.js +56 -0
- package/dist/ai/governance.js.map +1 -0
- package/dist/ai/index.d.ts +2 -1
- package/dist/ai/index.d.ts.map +1 -1
- package/dist/ai/index.js +2 -1
- package/dist/ai/index.js.map +1 -1
- package/dist/ai/providers/custom.d.ts +2 -1
- package/dist/ai/providers/custom.d.ts.map +1 -1
- package/dist/ai/providers/custom.js +7 -1
- package/dist/ai/providers/custom.js.map +1 -1
- package/dist/ai/providers/http.d.ts +13 -0
- package/dist/ai/providers/http.d.ts.map +1 -1
- package/dist/ai/providers/http.js +33 -0
- package/dist/ai/providers/http.js.map +1 -1
- package/dist/ai/providers/index.d.ts +5 -2
- package/dist/ai/providers/index.d.ts.map +1 -1
- package/dist/ai/providers/index.js +10 -0
- package/dist/ai/providers/index.js.map +1 -1
- package/dist/ai/providers/openai.d.ts +2 -1
- package/dist/ai/providers/openai.d.ts.map +1 -1
- package/dist/ai/providers/openai.js +4 -1
- package/dist/ai/providers/openai.js.map +1 -1
- package/dist/ai/runtime.d.ts +28 -1
- package/dist/ai/runtime.d.ts.map +1 -1
- package/dist/ai/runtime.js +73 -2
- package/dist/ai/runtime.js.map +1 -1
- package/dist/ai/usage.d.ts +6 -0
- package/dist/ai/usage.d.ts.map +1 -1
- package/dist/ai/usage.js +29 -0
- package/dist/ai/usage.js.map +1 -1
- package/dist/api/route-helpers.d.ts +2 -0
- package/dist/api/route-helpers.d.ts.map +1 -1
- package/dist/api/route-helpers.js +14 -0
- package/dist/api/route-helpers.js.map +1 -1
- package/dist/api/routes/ai-authoring.js +1 -1
- package/dist/api/routes/ai-authoring.js.map +1 -1
- package/dist/api/routes/ai-seo.d.ts.map +1 -1
- package/dist/api/routes/ai-seo.js +13 -5
- package/dist/api/routes/ai-seo.js.map +1 -1
- package/dist/api/routes/ai-settings.d.ts.map +1 -1
- package/dist/api/routes/ai-settings.js +39 -1
- package/dist/api/routes/ai-settings.js.map +1 -1
- package/dist/api/routes/redirects.d.ts.map +1 -1
- package/dist/api/routes/redirects.js +3 -2
- package/dist/api/routes/redirects.js.map +1 -1
- package/dist/api/routes/scheduling.d.ts.map +1 -1
- package/dist/api/routes/scheduling.js +17 -0
- package/dist/api/routes/scheduling.js.map +1 -1
- package/dist/api/routes/seo-public.d.ts.map +1 -1
- package/dist/api/routes/seo-public.js +205 -1
- package/dist/api/routes/seo-public.js.map +1 -1
- package/dist/api/routes/seo.d.ts.map +1 -1
- package/dist/api/routes/seo.js +216 -16
- package/dist/api/routes/seo.js.map +1 -1
- package/dist/db/model-types.d.ts +32 -0
- package/dist/db/model-types.d.ts.map +1 -1
- package/dist/generated/browser.d.ts +17 -0
- package/dist/generated/client.d.ts +17 -0
- package/dist/generated/commonInputTypes.d.ts +30 -30
- package/dist/generated/internal/class.d.ts +22 -0
- package/dist/generated/internal/class.js +4 -4
- package/dist/generated/internal/prismaNamespace.d.ts +198 -1
- package/dist/generated/internal/prismaNamespace.js +43 -0
- package/dist/generated/internal/prismaNamespaceBrowser.d.ts +47 -0
- package/dist/generated/internal/prismaNamespaceBrowser.js +43 -0
- package/dist/generated/models/ApiRequestMetric.d.ts +0 -7
- package/dist/generated/models/SearchPageInsight.d.ts +1460 -0
- package/dist/generated/models/SearchPageInsight.js +1 -0
- package/dist/generated/models/SearchPerfDaily.d.ts +1167 -0
- package/dist/generated/models/SearchPerfDaily.js +1 -0
- package/dist/generated/models.d.ts +2 -0
- package/dist/redirects/recovery.d.ts +12 -0
- package/dist/redirects/recovery.d.ts.map +1 -1
- package/dist/redirects/recovery.js +42 -2
- package/dist/redirects/recovery.js.map +1 -1
- package/dist/redirects/rerank.d.ts +14 -1
- package/dist/redirects/rerank.d.ts.map +1 -1
- package/dist/redirects/rerank.js +66 -37
- package/dist/redirects/rerank.js.map +1 -1
- package/dist/seo/audit-engine.d.ts +21 -3
- package/dist/seo/audit-engine.d.ts.map +1 -1
- package/dist/seo/audit-engine.js +51 -6
- package/dist/seo/audit-engine.js.map +1 -1
- package/dist/seo/audit-runner.d.ts.map +1 -1
- package/dist/seo/audit-runner.js +18 -0
- package/dist/seo/audit-runner.js.map +1 -1
- package/dist/seo/config-store.d.ts +13 -0
- package/dist/seo/config-store.d.ts.map +1 -1
- package/dist/seo/config-store.js +6 -0
- package/dist/seo/config-store.js.map +1 -1
- package/dist/seo/crux.d.ts +58 -0
- package/dist/seo/crux.d.ts.map +1 -0
- package/dist/seo/crux.js +101 -0
- package/dist/seo/crux.js.map +1 -0
- package/dist/seo/issue-content-brief.d.ts.map +1 -1
- package/dist/seo/issue-content-brief.js +11 -0
- package/dist/seo/issue-content-brief.js.map +1 -1
- package/dist/seo/issue-content-research.d.ts.map +1 -1
- package/dist/seo/issue-content-research.js +3 -0
- package/dist/seo/issue-content-research.js.map +1 -1
- package/dist/seo/issue-fix-ai.d.ts.map +1 -1
- package/dist/seo/issue-fix-ai.js +3 -0
- package/dist/seo/issue-fix-ai.js.map +1 -1
- package/dist/seo/issue-fix-redirect.d.ts.map +1 -1
- package/dist/seo/issue-fix-redirect.js +1 -0
- package/dist/seo/issue-fix-redirect.js.map +1 -1
- package/dist/seo/search-console/client.d.ts +39 -0
- package/dist/seo/search-console/client.d.ts.map +1 -0
- package/dist/seo/search-console/client.js +226 -0
- package/dist/seo/search-console/client.js.map +1 -0
- package/dist/seo/search-console/prompt-context.d.ts +21 -0
- package/dist/seo/search-console/prompt-context.d.ts.map +1 -0
- package/dist/seo/search-console/prompt-context.js +93 -0
- package/dist/seo/search-console/prompt-context.js.map +1 -0
- package/dist/seo/search-console/sync.d.ts +67 -0
- package/dist/seo/search-console/sync.d.ts.map +1 -0
- package/dist/seo/search-console/sync.js +469 -0
- package/dist/seo/search-console/sync.js.map +1 -0
- package/dist/seo/search-console/types.d.ts +75 -0
- package/dist/seo/search-console/types.d.ts.map +1 -0
- package/dist/seo/search-console/types.js +16 -0
- package/dist/seo/search-console/types.js.map +1 -0
- package/dist/seo/search-insight-signals.d.ts +29 -0
- package/dist/seo/search-insight-signals.d.ts.map +1 -0
- package/dist/seo/search-insight-signals.js +103 -0
- package/dist/seo/search-insight-signals.js.map +1 -0
- package/package.json +1 -1
- package/prisma/migrations/0016_search_console_tables/migration.sql +56 -0
- package/prisma/schema.prisma +49 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { PrentaCMSConfig } from '../../config/types.js';
|
|
2
|
+
import { type SearchConsoleClientOptions, type SearchSyncStatus, type TopQueryEntry } from './types.js';
|
|
3
|
+
/** Rolling retention window for daily rows. */
|
|
4
|
+
export declare const SEARCH_PERF_RETENTION_DAYS = 90;
|
|
5
|
+
/** CrUX URLs checked per invocation (fits `maxDuration: 30`). */
|
|
6
|
+
export declare const CRUX_URLS_PER_RUN = 20;
|
|
7
|
+
export interface SearchSyncOptions extends SearchConsoleClientOptions {
|
|
8
|
+
/** CrUX per-run cap override (tests). */
|
|
9
|
+
cruxUrlsPerRun?: number;
|
|
10
|
+
/** Effective config override (tests); defaults to the runtime config + DB overrides. */
|
|
11
|
+
effectiveConfig?: PrentaCMSConfig | null;
|
|
12
|
+
/** Attribution for the settings write. */
|
|
13
|
+
userId?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface SearchSyncResult {
|
|
16
|
+
status: SearchSyncStatus;
|
|
17
|
+
rowsUpserted: number;
|
|
18
|
+
pagesUpdated: number;
|
|
19
|
+
cruxChecked: number;
|
|
20
|
+
pruned: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Add calendar days to a `YYYY-MM-DD` string. Uses a UTC-noon anchor so the
|
|
24
|
+
* math is immune to DST — GSC dates are calendar days, not instants.
|
|
25
|
+
*/
|
|
26
|
+
export declare function addDays(date: string, days: number): string;
|
|
27
|
+
/**
|
|
28
|
+
* Normalize a GSC page URL to the site-relative path used everywhere else
|
|
29
|
+
* (`documentPublicPath` form: no trailing slash, no query/hash). Returns null
|
|
30
|
+
* for URLs on other hosts — required for `sc-domain:` properties, which
|
|
31
|
+
* report every subdomain and protocol.
|
|
32
|
+
*/
|
|
33
|
+
export declare function normalizeGscPageUrl(pageUrl: string, siteHost: string | null): string | null;
|
|
34
|
+
/** Derive the host to filter GSC rows by: public site URL, else the property itself. */
|
|
35
|
+
export declare function resolveFilterHost(siteUrl: string | null, property: string): string | null;
|
|
36
|
+
/**
|
|
37
|
+
* Collapse GSC `(date, page)` rows onto normalized paths. Several URL variants
|
|
38
|
+
* (protocol, www, trailing slash, query strings) can merge into one path, so
|
|
39
|
+
* clicks/impressions sum and position becomes the impressions-weighted mean.
|
|
40
|
+
*/
|
|
41
|
+
export declare function aggregateDailyRows(rows: Array<{
|
|
42
|
+
keys: string[];
|
|
43
|
+
clicks: number;
|
|
44
|
+
impressions: number;
|
|
45
|
+
position: number;
|
|
46
|
+
}>, siteHost: string | null): Array<{
|
|
47
|
+
date: string;
|
|
48
|
+
path: string;
|
|
49
|
+
clicks: number;
|
|
50
|
+
impressions: number;
|
|
51
|
+
ctr: number;
|
|
52
|
+
position: number;
|
|
53
|
+
}>;
|
|
54
|
+
/** Group `(query, page)` rows into per-path top-query lists (by clicks, then impressions). */
|
|
55
|
+
export declare function buildTopQueries(rows: Array<{
|
|
56
|
+
keys: string[];
|
|
57
|
+
clicks: number;
|
|
58
|
+
impressions: number;
|
|
59
|
+
position: number;
|
|
60
|
+
}>, siteHost: string | null): Map<string, TopQueryEntry[]>;
|
|
61
|
+
/**
|
|
62
|
+
* Run one search-data sync pass. Never throws for expected failure modes —
|
|
63
|
+
* the returned `status` carries per-part outcomes, and the same status object
|
|
64
|
+
* is persisted to the SEO settings for the admin card.
|
|
65
|
+
*/
|
|
66
|
+
export declare function runSearchSync(db: any, options?: SearchSyncOptions): Promise<SearchSyncResult>;
|
|
67
|
+
//# sourceMappingURL=sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../../src/seo/search-console/sync.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAY5D,OAAO,EAEL,KAAK,0BAA0B,EAC/B,KAAK,gBAAgB,EACrB,KAAK,aAAa,EACnB,MAAM,YAAY,CAAA;AAEnB,+CAA+C;AAC/C,eAAO,MAAM,0BAA0B,KAAK,CAAA;AAO5C,iEAAiE;AACjE,eAAO,MAAM,iBAAiB,KAAK,CAAA;AAMnC,MAAM,WAAW,iBAAkB,SAAQ,0BAA0B;IACnE,yCAAyC;IACzC,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,wFAAwF;IACxF,eAAe,CAAC,EAAE,eAAe,GAAG,IAAI,CAAA;IACxC,0CAA0C;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,gBAAgB,CAAA;IACxB,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAM1D;AAaD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAe3F;AAED,wFAAwF;AACxF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAezF;AAcD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,EACtF,QAAQ,EAAE,MAAM,GAAG,IAAI,GACtB,KAAK,CAAC;IACP,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAC,CAqCD;AAED,8FAA8F;AAC9F,wBAAgB,eAAe,CAC7B,IAAI,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,EACtF,QAAQ,EAAE,MAAM,GAAG,IAAI,GACtB,GAAG,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC,CAqB9B;AA6ED;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,EAAE,EAAE,GAAG,EACP,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,gBAAgB,CAAC,CAgQ3B"}
|
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nightly search-data sync: Google Search Console analytics + CrUX Core Web
|
|
3
|
+
* Vitals → `prenta_search_perf_daily` / `prenta_search_page_insights`.
|
|
4
|
+
*
|
|
5
|
+
* Design contract (spec: 2026-08-29-search-console-integration-design.md):
|
|
6
|
+
*
|
|
7
|
+
* - **Sync writes data only — never SeoIssue rows.** The audit engine is the
|
|
8
|
+
* single issue writer; it reads the insight table on its own runs.
|
|
9
|
+
* - **Timezone rule:** GSC reports dates in the property's timezone. Dates are
|
|
10
|
+
* stored exactly as reported (`YYYY-MM-DD` → UTC-midnight DATE column) and
|
|
11
|
+
* all window math happens on those as-reported calendar days.
|
|
12
|
+
* - **Chunked for serverless:** the GSC fetch is one paginated request; CrUX
|
|
13
|
+
* is capped per invocation with a rotating cursor persisted in settings, so
|
|
14
|
+
* every run fits Vercel's 30s function budget.
|
|
15
|
+
* - **CrUX 404 = no field data** (normal for low-traffic URLs) — recorded as
|
|
16
|
+
* `cwvStatus: null`, never a failure.
|
|
17
|
+
*/
|
|
18
|
+
import { hasModel } from '../../api/route-helpers.js';
|
|
19
|
+
import { getPrentaConfig } from '../../config/runtime.js';
|
|
20
|
+
import { decryptSecret } from '../../security/secret-storage.js';
|
|
21
|
+
import { applySeoOverrides, getSeoOverrides, patchSeoModuleSettings, resolveSeoModuleSettings, } from '../config-store.js';
|
|
22
|
+
import { CruxError, fetchCruxMetrics } from '../crux.js';
|
|
23
|
+
import { applyTrailingSlashToPathname, documentPublicPath } from '../document-path.js';
|
|
24
|
+
import { resolvePublicSiteUrlFromSnapshot } from '../public-site-url.js';
|
|
25
|
+
import { parseServiceAccountKey, queryAllSearchAnalytics, toGscDate } from './client.js';
|
|
26
|
+
import { SearchConsoleError, } from './types.js';
|
|
27
|
+
/** Rolling retention window for daily rows. */
|
|
28
|
+
export const SEARCH_PERF_RETENTION_DAYS = 90;
|
|
29
|
+
/** GSC data lags ~2 days; always re-request this trailing window to self-heal. */
|
|
30
|
+
const LAG_REFRESH_DAYS = 3;
|
|
31
|
+
/** Trailing window for query snapshots, totals, and deltas. */
|
|
32
|
+
const INSIGHT_WINDOW_DAYS = 28;
|
|
33
|
+
/** Top queries kept per page in the insight snapshot. */
|
|
34
|
+
const TOP_QUERIES_PER_PAGE = 20;
|
|
35
|
+
/** CrUX URLs checked per invocation (fits `maxDuration: 30`). */
|
|
36
|
+
export const CRUX_URLS_PER_RUN = 20;
|
|
37
|
+
/** How many top-by-impressions pages participate in the CrUX rotation. */
|
|
38
|
+
const CRUX_ROTATION_POOL = 500;
|
|
39
|
+
/** Documents loaded for path→entity matching (mirrors AUDIT_MAX_ENTITIES). */
|
|
40
|
+
const MATCH_MAX_DOCUMENTS = 5000;
|
|
41
|
+
/**
|
|
42
|
+
* Add calendar days to a `YYYY-MM-DD` string. Uses a UTC-noon anchor so the
|
|
43
|
+
* math is immune to DST — GSC dates are calendar days, not instants.
|
|
44
|
+
*/
|
|
45
|
+
export function addDays(date, days) {
|
|
46
|
+
const parts = date.split('-').map(Number);
|
|
47
|
+
const y = parts[0] ?? 1970;
|
|
48
|
+
const m = parts[1] ?? 1;
|
|
49
|
+
const d = parts[2] ?? 1;
|
|
50
|
+
return toGscDate(new Date(Date.UTC(y, m - 1, d + days, 12)));
|
|
51
|
+
}
|
|
52
|
+
/** Store a GSC calendar day in the DATE column (UTC midnight, by convention). */
|
|
53
|
+
function toDbDate(date) {
|
|
54
|
+
return new Date(`${date}T00:00:00.000Z`);
|
|
55
|
+
}
|
|
56
|
+
/** Host comparison with apex/www equivalence (`www.example.com` ≡ `example.com`). */
|
|
57
|
+
function hostsMatch(a, b) {
|
|
58
|
+
const strip = (h) => h.toLowerCase().replace(/^www\./, '');
|
|
59
|
+
return strip(a) === strip(b);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Normalize a GSC page URL to the site-relative path used everywhere else
|
|
63
|
+
* (`documentPublicPath` form: no trailing slash, no query/hash). Returns null
|
|
64
|
+
* for URLs on other hosts — required for `sc-domain:` properties, which
|
|
65
|
+
* report every subdomain and protocol.
|
|
66
|
+
*/
|
|
67
|
+
export function normalizeGscPageUrl(pageUrl, siteHost) {
|
|
68
|
+
let parsed;
|
|
69
|
+
try {
|
|
70
|
+
parsed = new URL(pageUrl);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
if (siteHost && !hostsMatch(parsed.hostname, siteHost))
|
|
76
|
+
return null;
|
|
77
|
+
let path = parsed.pathname.replace(/\/+$/, '') || '/';
|
|
78
|
+
try {
|
|
79
|
+
path = decodeURIComponent(path);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// keep the encoded form when decoding fails
|
|
83
|
+
}
|
|
84
|
+
return path.startsWith('/') ? path : `/${path}`;
|
|
85
|
+
}
|
|
86
|
+
/** Derive the host to filter GSC rows by: public site URL, else the property itself. */
|
|
87
|
+
export function resolveFilterHost(siteUrl, property) {
|
|
88
|
+
if (siteUrl) {
|
|
89
|
+
try {
|
|
90
|
+
return new URL(siteUrl).hostname;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// fall through to the property
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const trimmed = property.trim();
|
|
97
|
+
if (trimmed.startsWith('sc-domain:'))
|
|
98
|
+
return trimmed.slice('sc-domain:'.length) || null;
|
|
99
|
+
try {
|
|
100
|
+
return new URL(trimmed).hostname;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Collapse GSC `(date, page)` rows onto normalized paths. Several URL variants
|
|
108
|
+
* (protocol, www, trailing slash, query strings) can merge into one path, so
|
|
109
|
+
* clicks/impressions sum and position becomes the impressions-weighted mean.
|
|
110
|
+
*/
|
|
111
|
+
export function aggregateDailyRows(rows, siteHost) {
|
|
112
|
+
const byKey = new Map();
|
|
113
|
+
for (const row of rows) {
|
|
114
|
+
const [date, page] = row.keys;
|
|
115
|
+
if (!date || !page)
|
|
116
|
+
continue;
|
|
117
|
+
const path = normalizeGscPageUrl(page, siteHost);
|
|
118
|
+
if (!path)
|
|
119
|
+
continue;
|
|
120
|
+
const key = `${date}\u0000${path}`;
|
|
121
|
+
const agg = byKey.get(key) ?? {
|
|
122
|
+
date,
|
|
123
|
+
path,
|
|
124
|
+
clicks: 0,
|
|
125
|
+
impressions: 0,
|
|
126
|
+
positionWeight: 0,
|
|
127
|
+
positionSum: 0,
|
|
128
|
+
rows: 0,
|
|
129
|
+
};
|
|
130
|
+
agg.clicks += row.clicks;
|
|
131
|
+
agg.impressions += row.impressions;
|
|
132
|
+
agg.positionWeight += row.position * row.impressions;
|
|
133
|
+
agg.positionSum += row.position;
|
|
134
|
+
agg.rows += 1;
|
|
135
|
+
byKey.set(key, agg);
|
|
136
|
+
}
|
|
137
|
+
return [...byKey.values()].map((agg) => ({
|
|
138
|
+
date: agg.date,
|
|
139
|
+
path: agg.path,
|
|
140
|
+
clicks: agg.clicks,
|
|
141
|
+
impressions: agg.impressions,
|
|
142
|
+
ctr: agg.impressions > 0 ? agg.clicks / agg.impressions : 0,
|
|
143
|
+
position: agg.impressions > 0
|
|
144
|
+
? agg.positionWeight / agg.impressions
|
|
145
|
+
: agg.rows > 0
|
|
146
|
+
? agg.positionSum / agg.rows
|
|
147
|
+
: 0,
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
150
|
+
/** Group `(query, page)` rows into per-path top-query lists (by clicks, then impressions). */
|
|
151
|
+
export function buildTopQueries(rows, siteHost) {
|
|
152
|
+
const byPath = new Map();
|
|
153
|
+
for (const row of rows) {
|
|
154
|
+
const [query, page] = row.keys;
|
|
155
|
+
if (!query || !page)
|
|
156
|
+
continue;
|
|
157
|
+
const path = normalizeGscPageUrl(page, siteHost);
|
|
158
|
+
if (!path)
|
|
159
|
+
continue;
|
|
160
|
+
const list = byPath.get(path) ?? [];
|
|
161
|
+
list.push({
|
|
162
|
+
query,
|
|
163
|
+
clicks: row.clicks,
|
|
164
|
+
impressions: row.impressions,
|
|
165
|
+
position: Math.round(row.position * 10) / 10,
|
|
166
|
+
});
|
|
167
|
+
byPath.set(path, list);
|
|
168
|
+
}
|
|
169
|
+
for (const [path, list] of byPath) {
|
|
170
|
+
list.sort((a, b) => b.clicks - a.clicks || b.impressions - a.impressions);
|
|
171
|
+
byPath.set(path, list.slice(0, TOP_QUERIES_PER_PAGE));
|
|
172
|
+
}
|
|
173
|
+
return byPath;
|
|
174
|
+
}
|
|
175
|
+
/** Build a path → document map from published front-end documents. */
|
|
176
|
+
async function buildEntityMap(db, effectiveConfig) {
|
|
177
|
+
const map = new Map();
|
|
178
|
+
const collections = (effectiveConfig?.collections ?? {});
|
|
179
|
+
const slugs = [];
|
|
180
|
+
const types = {};
|
|
181
|
+
for (const [slug, col] of Object.entries(collections)) {
|
|
182
|
+
if (col?.type !== 'page' && col?.type !== 'post')
|
|
183
|
+
continue;
|
|
184
|
+
if (col?.admin?.hidden)
|
|
185
|
+
continue;
|
|
186
|
+
slugs.push(slug);
|
|
187
|
+
types[slug] = col.type;
|
|
188
|
+
}
|
|
189
|
+
if (slugs.length === 0 || !hasModel(db, 'document'))
|
|
190
|
+
return map;
|
|
191
|
+
try {
|
|
192
|
+
const docs = await db.document.findMany({
|
|
193
|
+
where: { collection: { in: slugs }, status: 'PUBLISHED', deletedAt: null },
|
|
194
|
+
select: { id: true, collection: true, slug: true, data: true },
|
|
195
|
+
take: MATCH_MAX_DOCUMENTS,
|
|
196
|
+
});
|
|
197
|
+
for (const doc of docs) {
|
|
198
|
+
const data = doc.data && typeof doc.data === 'object' && !Array.isArray(doc.data)
|
|
199
|
+
? doc.data
|
|
200
|
+
: {};
|
|
201
|
+
const path = documentPublicPath({
|
|
202
|
+
urlPrefix: collections[doc.collection]?.urlPrefix,
|
|
203
|
+
slug: doc.slug,
|
|
204
|
+
data,
|
|
205
|
+
});
|
|
206
|
+
if (!map.has(path)) {
|
|
207
|
+
map.set(path, { entityId: doc.id, entityType: types[doc.collection] ?? 'page' });
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
// best-effort matching — insights still store path-only rows
|
|
213
|
+
}
|
|
214
|
+
return map;
|
|
215
|
+
}
|
|
216
|
+
/** Sum a date window from the daily table, grouped by path. */
|
|
217
|
+
async function loadWindowTotals(db, start, end) {
|
|
218
|
+
const out = new Map();
|
|
219
|
+
const groups = await db.searchPerfDaily.groupBy({
|
|
220
|
+
by: ['path'],
|
|
221
|
+
where: { date: { gte: toDbDate(start), lte: toDbDate(end) } },
|
|
222
|
+
_sum: { clicks: true, impressions: true },
|
|
223
|
+
_avg: { position: true },
|
|
224
|
+
});
|
|
225
|
+
for (const g of groups) {
|
|
226
|
+
out.set(g.path, {
|
|
227
|
+
clicks: g._sum.clicks ?? 0,
|
|
228
|
+
impressions: g._sum.impressions ?? 0,
|
|
229
|
+
avgPosition: g._avg.position === null ? null : Math.round(g._avg.position * 10) / 10,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
return out;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Run one search-data sync pass. Never throws for expected failure modes —
|
|
236
|
+
* the returned `status` carries per-part outcomes, and the same status object
|
|
237
|
+
* is persisted to the SEO settings for the admin card.
|
|
238
|
+
*/
|
|
239
|
+
export async function runSearchSync(db, options = {}) {
|
|
240
|
+
const overrides = await getSeoOverrides(db);
|
|
241
|
+
const settings = resolveSeoModuleSettings(overrides);
|
|
242
|
+
const empty = {
|
|
243
|
+
status: { state: 'not-configured' },
|
|
244
|
+
rowsUpserted: 0,
|
|
245
|
+
pagesUpdated: 0,
|
|
246
|
+
cruxChecked: 0,
|
|
247
|
+
pruned: 0,
|
|
248
|
+
};
|
|
249
|
+
if (!settings.gscServiceAccountKey || !settings.gscPropertyUrl.trim()) {
|
|
250
|
+
return empty;
|
|
251
|
+
}
|
|
252
|
+
if (!hasModel(db, 'searchPerfDaily') || !hasModel(db, 'searchPageInsight')) {
|
|
253
|
+
return {
|
|
254
|
+
...empty,
|
|
255
|
+
status: {
|
|
256
|
+
state: 'error',
|
|
257
|
+
message: 'Search data tables are missing — run the pending database migration.',
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
const property = settings.gscPropertyUrl.trim();
|
|
262
|
+
const effectiveConfig = options.effectiveConfig !== undefined
|
|
263
|
+
? options.effectiveConfig
|
|
264
|
+
: applySeoOverrides(getPrentaConfig(), overrides);
|
|
265
|
+
const site = resolvePublicSiteUrlFromSnapshot(effectiveConfig, overrides);
|
|
266
|
+
const siteHost = resolveFilterHost(site.siteUrl, property);
|
|
267
|
+
const nowMs = options.now ?? Date.now;
|
|
268
|
+
const today = toGscDate(new Date(nowMs()));
|
|
269
|
+
const endDate = addDays(today, -1);
|
|
270
|
+
const windowStart = addDays(today, -SEARCH_PERF_RETENTION_DAYS);
|
|
271
|
+
const clientOptions = { fetchFn: options.fetchFn, now: options.now };
|
|
272
|
+
const status = { state: 'ok', parts: {} };
|
|
273
|
+
let rowsUpserted = 0;
|
|
274
|
+
let pagesUpdated = 0;
|
|
275
|
+
let gscOk = false;
|
|
276
|
+
// ── Part 1: Search Console ────────────────────────────────────────────
|
|
277
|
+
try {
|
|
278
|
+
const key = parseServiceAccountKey(await decryptSecret(settings.gscServiceAccountKey));
|
|
279
|
+
// Fetch every day missing from the retention window, plus the trailing
|
|
280
|
+
// lag window (GSC finalizes data ~2 days late). One paginated request
|
|
281
|
+
// covers the whole contiguous span; re-fetched days upsert idempotently.
|
|
282
|
+
const existingRows = (await db.searchPerfDaily.findMany({
|
|
283
|
+
where: { date: { gte: toDbDate(windowStart) } },
|
|
284
|
+
select: { date: true },
|
|
285
|
+
distinct: ['date'],
|
|
286
|
+
}));
|
|
287
|
+
const existingDates = new Set(existingRows.map((r) => toGscDate(r.date)));
|
|
288
|
+
let fetchStart = addDays(endDate, -(LAG_REFRESH_DAYS - 1));
|
|
289
|
+
for (let d = windowStart; d <= endDate; d = addDays(d, 1)) {
|
|
290
|
+
if (!existingDates.has(d)) {
|
|
291
|
+
if (d < fetchStart)
|
|
292
|
+
fetchStart = d;
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
const dailyRows = await queryAllSearchAnalytics(key, property, { startDate: fetchStart, endDate, dimensions: ['date', 'page'] }, clientOptions);
|
|
297
|
+
const aggregated = aggregateDailyRows(dailyRows, siteHost);
|
|
298
|
+
// Replace-then-insert keeps the write to two statements regardless of
|
|
299
|
+
// row count (a 90-day backfill can be tens of thousands of upserts).
|
|
300
|
+
await db.searchPerfDaily.deleteMany({
|
|
301
|
+
where: { date: { gte: toDbDate(fetchStart), lte: toDbDate(endDate) } },
|
|
302
|
+
});
|
|
303
|
+
if (aggregated.length > 0) {
|
|
304
|
+
await db.searchPerfDaily.createMany({
|
|
305
|
+
data: aggregated.map((r) => ({
|
|
306
|
+
date: toDbDate(r.date),
|
|
307
|
+
path: r.path,
|
|
308
|
+
clicks: r.clicks,
|
|
309
|
+
impressions: r.impressions,
|
|
310
|
+
ctr: r.ctr,
|
|
311
|
+
position: r.position,
|
|
312
|
+
})),
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
rowsUpserted = aggregated.length;
|
|
316
|
+
// Query snapshot over the trailing 28 days.
|
|
317
|
+
const snapStart = addDays(endDate, -(INSIGHT_WINDOW_DAYS - 1));
|
|
318
|
+
const queryRows = await queryAllSearchAnalytics(key, property, { startDate: snapStart, endDate, dimensions: ['query', 'page'] }, clientOptions);
|
|
319
|
+
const topQueriesByPath = buildTopQueries(queryRows, siteHost);
|
|
320
|
+
// 28d totals + prior-window deltas from the daily table.
|
|
321
|
+
const current = await loadWindowTotals(db, snapStart, endDate);
|
|
322
|
+
const prevEnd = addDays(snapStart, -1);
|
|
323
|
+
const prevStart = addDays(prevEnd, -(INSIGHT_WINDOW_DAYS - 1));
|
|
324
|
+
const previous = await loadWindowTotals(db, prevStart, prevEnd);
|
|
325
|
+
const entityMap = await buildEntityMap(db, effectiveConfig);
|
|
326
|
+
const allPaths = new Set([
|
|
327
|
+
...current.keys(),
|
|
328
|
+
...previous.keys(),
|
|
329
|
+
...topQueriesByPath.keys(),
|
|
330
|
+
]);
|
|
331
|
+
for (const path of allPaths) {
|
|
332
|
+
const cur = current.get(path);
|
|
333
|
+
const prev = previous.get(path);
|
|
334
|
+
const entity = entityMap.get(path);
|
|
335
|
+
const fields = {
|
|
336
|
+
entityId: entity?.entityId ?? null,
|
|
337
|
+
entityType: entity?.entityType ?? null,
|
|
338
|
+
topQueries: topQueriesByPath.get(path) ?? [],
|
|
339
|
+
clicks28d: cur?.clicks ?? 0,
|
|
340
|
+
impressions28d: cur?.impressions ?? 0,
|
|
341
|
+
avgPosition28d: cur?.avgPosition ?? null,
|
|
342
|
+
clicksPrev28d: prev?.clicks ?? 0,
|
|
343
|
+
impressionsPrev28d: prev?.impressions ?? 0,
|
|
344
|
+
avgPositionPrev28d: prev?.avgPosition ?? null,
|
|
345
|
+
};
|
|
346
|
+
await db.searchPageInsight.upsert({
|
|
347
|
+
where: { path },
|
|
348
|
+
update: fields,
|
|
349
|
+
create: { path, ...fields },
|
|
350
|
+
});
|
|
351
|
+
pagesUpdated++;
|
|
352
|
+
}
|
|
353
|
+
// Pages that dropped out of both windows are stale: zero the counters but
|
|
354
|
+
// keep the row (and its CWV data) so the editor pane can still show it.
|
|
355
|
+
if (allPaths.size > 0) {
|
|
356
|
+
await db.searchPageInsight.updateMany({
|
|
357
|
+
where: { path: { notIn: [...allPaths] } },
|
|
358
|
+
data: {
|
|
359
|
+
clicks28d: 0,
|
|
360
|
+
impressions28d: 0,
|
|
361
|
+
avgPosition28d: null,
|
|
362
|
+
clicksPrev28d: 0,
|
|
363
|
+
impressionsPrev28d: 0,
|
|
364
|
+
avgPositionPrev28d: null,
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
gscOk = true;
|
|
369
|
+
status.parts = {
|
|
370
|
+
...status.parts,
|
|
371
|
+
searchConsole: { ok: true, rowsUpserted },
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
catch (err) {
|
|
375
|
+
const message = err instanceof SearchConsoleError
|
|
376
|
+
? err.message
|
|
377
|
+
: `Unexpected sync failure: ${err instanceof Error ? err.message : String(err)}`;
|
|
378
|
+
status.parts = { ...status.parts, searchConsole: { ok: false, message } };
|
|
379
|
+
}
|
|
380
|
+
// ── Part 2: CrUX (independent of the GSC part) ────────────────────────
|
|
381
|
+
let cruxChecked = 0;
|
|
382
|
+
let cruxOk = null;
|
|
383
|
+
let nextCruxCursor = settings.searchSyncCruxCursor;
|
|
384
|
+
if (settings.cruxApiKey) {
|
|
385
|
+
if (!site.siteUrl) {
|
|
386
|
+
cruxOk = false;
|
|
387
|
+
status.parts = {
|
|
388
|
+
...status.parts,
|
|
389
|
+
crux: { ok: false, message: 'Set the public site URL before collecting Core Web Vitals.' },
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
try {
|
|
394
|
+
const cruxKey = await decryptSecret(settings.cruxApiKey);
|
|
395
|
+
const pool = (await db.searchPageInsight.findMany({
|
|
396
|
+
orderBy: { impressions28d: 'desc' },
|
|
397
|
+
select: { id: true, path: true },
|
|
398
|
+
take: CRUX_ROTATION_POOL,
|
|
399
|
+
}));
|
|
400
|
+
const cap = Math.min(options.cruxUrlsPerRun ?? CRUX_URLS_PER_RUN, pool.length);
|
|
401
|
+
const start = pool.length > 0 ? settings.searchSyncCruxCursor % pool.length : 0;
|
|
402
|
+
for (let i = 0; i < cap; i++) {
|
|
403
|
+
const row = pool[(start + i) % pool.length];
|
|
404
|
+
if (!row)
|
|
405
|
+
continue;
|
|
406
|
+
const pathname = applyTrailingSlashToPathname(row.path, site.trailingSlash);
|
|
407
|
+
const url = pathname === '/' ? `${site.siteUrl}/` : `${site.siteUrl}${pathname}`;
|
|
408
|
+
const metrics = await fetchCruxMetrics(cruxKey, url, { fetchFn: options.fetchFn });
|
|
409
|
+
await db.searchPageInsight.update({
|
|
410
|
+
where: { id: row.id },
|
|
411
|
+
data: {
|
|
412
|
+
lcpMs: metrics.lcpMs,
|
|
413
|
+
inpMs: metrics.inpMs,
|
|
414
|
+
cls: metrics.cls,
|
|
415
|
+
cwvStatus: metrics.status,
|
|
416
|
+
cwvCollectedAt: new Date(nowMs()),
|
|
417
|
+
},
|
|
418
|
+
});
|
|
419
|
+
cruxChecked++;
|
|
420
|
+
}
|
|
421
|
+
nextCruxCursor = pool.length > 0 ? (start + cap) % pool.length : 0;
|
|
422
|
+
cruxOk = true;
|
|
423
|
+
status.parts = { ...status.parts, crux: { ok: true, pagesChecked: cruxChecked } };
|
|
424
|
+
}
|
|
425
|
+
catch (err) {
|
|
426
|
+
cruxOk = false;
|
|
427
|
+
const message = err instanceof CruxError
|
|
428
|
+
? err.message
|
|
429
|
+
: `Unexpected CrUX failure: ${err instanceof Error ? err.message : String(err)}`;
|
|
430
|
+
status.parts = { ...status.parts, crux: { ok: false, message, pagesChecked: cruxChecked } };
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
// ── Part 3: prune + status ────────────────────────────────────────────
|
|
435
|
+
let pruned = 0;
|
|
436
|
+
try {
|
|
437
|
+
const del = await db.searchPerfDaily.deleteMany({
|
|
438
|
+
where: { date: { lt: toDbDate(windowStart) } },
|
|
439
|
+
});
|
|
440
|
+
pruned = typeof del?.count === 'number' ? del.count : 0;
|
|
441
|
+
}
|
|
442
|
+
catch {
|
|
443
|
+
// prune is housekeeping — never fail the run over it
|
|
444
|
+
}
|
|
445
|
+
status.state = gscOk
|
|
446
|
+
? cruxOk === false
|
|
447
|
+
? 'partial'
|
|
448
|
+
: 'ok'
|
|
449
|
+
: cruxOk === true
|
|
450
|
+
? 'partial'
|
|
451
|
+
: 'error';
|
|
452
|
+
if (!gscOk)
|
|
453
|
+
status.message = status.parts?.searchConsole?.message;
|
|
454
|
+
else if (cruxOk === false)
|
|
455
|
+
status.message = status.parts?.crux?.message;
|
|
456
|
+
try {
|
|
457
|
+
await patchSeoModuleSettings(db, {
|
|
458
|
+
searchSyncLastRunAt: new Date(nowMs()).toISOString(),
|
|
459
|
+
searchSyncStatus: status,
|
|
460
|
+
searchSyncCruxCursor: nextCruxCursor,
|
|
461
|
+
searchConsoleConnected: gscOk,
|
|
462
|
+
}, options.userId ?? 'system');
|
|
463
|
+
}
|
|
464
|
+
catch {
|
|
465
|
+
// status persistence is best-effort; the sync result still returns it
|
|
466
|
+
}
|
|
467
|
+
return { status, rowsUpserted, pagesUpdated, cruxChecked, pruned };
|
|
468
|
+
}
|
|
469
|
+
//# sourceMappingURL=sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../../../src/seo/search-console/sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the Google Search Console integration (spec:
|
|
3
|
+
* docs/superpowers/specs/2026-08-29-search-console-integration-design.md).
|
|
4
|
+
*
|
|
5
|
+
* Dates flow through this module as `YYYY-MM-DD` strings exactly as GSC
|
|
6
|
+
* reports them (property timezone) — never as Date objects converted to UTC.
|
|
7
|
+
*/
|
|
8
|
+
/** The fields of a Google service-account JSON key this integration uses. */
|
|
9
|
+
export interface GscServiceAccountKey {
|
|
10
|
+
client_email: string;
|
|
11
|
+
private_key: string;
|
|
12
|
+
/** OAuth2 token endpoint. Present in every Google-issued key file. */
|
|
13
|
+
token_uri?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Discriminated failure states, each mapped to an actionable admin hint by
|
|
17
|
+
* the settings/test endpoints. `not-configured` is the quiet no-op state —
|
|
18
|
+
* a fresh install syncs nothing and reports nothing.
|
|
19
|
+
*/
|
|
20
|
+
export type SearchConsoleErrorCode = 'not-configured' | 'auth-failed' | 'property-not-granted' | 'quota-exceeded' | 'network';
|
|
21
|
+
export declare class SearchConsoleError extends Error {
|
|
22
|
+
readonly code: SearchConsoleErrorCode;
|
|
23
|
+
constructor(code: SearchConsoleErrorCode, message: string);
|
|
24
|
+
}
|
|
25
|
+
/** One row from the Search Analytics API. `keys` order matches `dimensions`. */
|
|
26
|
+
export interface SearchAnalyticsRow {
|
|
27
|
+
keys: string[];
|
|
28
|
+
clicks: number;
|
|
29
|
+
impressions: number;
|
|
30
|
+
ctr: number;
|
|
31
|
+
position: number;
|
|
32
|
+
}
|
|
33
|
+
export type SearchAnalyticsDimension = 'date' | 'page' | 'query';
|
|
34
|
+
export interface SearchAnalyticsQuery {
|
|
35
|
+
/** Inclusive, `YYYY-MM-DD` in the property's timezone. */
|
|
36
|
+
startDate: string;
|
|
37
|
+
/** Inclusive, `YYYY-MM-DD` in the property's timezone. */
|
|
38
|
+
endDate: string;
|
|
39
|
+
dimensions: SearchAnalyticsDimension[];
|
|
40
|
+
/** Per-request page size. Defaults to the API maximum (25,000). */
|
|
41
|
+
rowLimit?: number;
|
|
42
|
+
startRow?: number;
|
|
43
|
+
}
|
|
44
|
+
/** Injectable seams so every call is fixture-testable without Google. */
|
|
45
|
+
export interface SearchConsoleClientOptions {
|
|
46
|
+
fetchFn?: typeof fetch;
|
|
47
|
+
/** Clock override for token-expiry tests. */
|
|
48
|
+
now?: () => number;
|
|
49
|
+
}
|
|
50
|
+
/** Sync status persisted in the SEO config store and shown in the admin. */
|
|
51
|
+
export interface SearchSyncStatus {
|
|
52
|
+
state: 'ok' | 'partial' | 'error' | 'not-configured';
|
|
53
|
+
message?: string;
|
|
54
|
+
/** Per-part outcome so a CrUX failure doesn't hide a GSC success. */
|
|
55
|
+
parts?: {
|
|
56
|
+
searchConsole?: {
|
|
57
|
+
ok: boolean;
|
|
58
|
+
message?: string;
|
|
59
|
+
rowsUpserted?: number;
|
|
60
|
+
};
|
|
61
|
+
crux?: {
|
|
62
|
+
ok: boolean;
|
|
63
|
+
message?: string;
|
|
64
|
+
pagesChecked?: number;
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** Top-query entry stored in `SearchPageInsight.topQueries`. */
|
|
69
|
+
export interface TopQueryEntry {
|
|
70
|
+
query: string;
|
|
71
|
+
clicks: number;
|
|
72
|
+
impressions: number;
|
|
73
|
+
position: number;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/seo/search-console/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,6EAA6E;AAC7E,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;GAIG;AACH,MAAM,MAAM,sBAAsB,GAC9B,gBAAgB,GAChB,aAAa,GACb,sBAAsB,GACtB,gBAAgB,GAChB,SAAS,CAAA;AAEb,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAA;gBAEzB,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,MAAM;CAK1D;AAED,gFAAgF;AAChF,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,MAAM,wBAAwB,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;AAEhE,MAAM,WAAW,oBAAoB;IACnC,0DAA0D;IAC1D,SAAS,EAAE,MAAM,CAAA;IACjB,0DAA0D;IAC1D,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,wBAAwB,EAAE,CAAA;IACtC,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,yEAAyE;AACzE,MAAM,WAAW,0BAA0B;IACzC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAA;IACtB,6CAA6C;IAC7C,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB;AAED,4EAA4E;AAC5E,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,IAAI,GAAG,SAAS,GAAG,OAAO,GAAG,gBAAgB,CAAA;IACpD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,qEAAqE;IACrE,KAAK,CAAC,EAAE;QACN,aAAa,CAAC,EAAE;YAAE,EAAE,EAAE,OAAO,CAAC;YAAC,OAAO,CAAC,EAAE,MAAM,CAAC;YAAC,YAAY,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;QACxE,IAAI,CAAC,EAAE;YAAE,EAAE,EAAE,OAAO,CAAC;YAAC,OAAO,CAAC,EAAE,MAAM,CAAC;YAAC,YAAY,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAChE,CAAA;CACF;AAED,gEAAgE;AAChE,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;CACjB"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the Google Search Console integration (spec:
|
|
3
|
+
* docs/superpowers/specs/2026-08-29-search-console-integration-design.md).
|
|
4
|
+
*
|
|
5
|
+
* Dates flow through this module as `YYYY-MM-DD` strings exactly as GSC
|
|
6
|
+
* reports them (property timezone) — never as Date objects converted to UTC.
|
|
7
|
+
*/
|
|
8
|
+
export class SearchConsoleError extends Error {
|
|
9
|
+
code;
|
|
10
|
+
constructor(code, message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = 'SearchConsoleError';
|
|
13
|
+
this.code = code;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=types.js.map
|