@orion-studios/payload-seo-audit 1.0.0 → 1.1.1

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.
Files changed (60) hide show
  1. package/README.md +48 -45
  2. package/dist/api/cron.d.ts.map +1 -1
  3. package/dist/api/cron.js +3 -23
  4. package/dist/api/run-stream.d.ts.map +1 -1
  5. package/dist/api/run-stream.js +19 -215
  6. package/dist/api/run.d.ts.map +1 -1
  7. package/dist/api/run.js +4 -25
  8. package/dist/collections/SeoDashboardView.d.ts +3 -0
  9. package/dist/collections/SeoDashboardView.d.ts.map +1 -0
  10. package/dist/collections/SeoDashboardView.js +30 -0
  11. package/dist/collections/SeoSnapshots.d.ts.map +1 -1
  12. package/dist/collections/SeoSnapshots.js +26 -0
  13. package/dist/components/layout/SeoReportShell.js +2 -2
  14. package/dist/components/types.d.ts +10 -0
  15. package/dist/components/types.d.ts.map +1 -1
  16. package/dist/components/views/SeoDashboard.d.ts.map +1 -1
  17. package/dist/components/views/SeoDashboard.js +68 -53
  18. package/dist/components/views/SeoPageReport.d.ts.map +1 -1
  19. package/dist/components/views/SeoPageReport.js +21 -10
  20. package/dist/components/views/SeoSnapshotReport.d.ts.map +1 -1
  21. package/dist/components/views/SeoSnapshotReport.js +21 -10
  22. package/dist/config.d.ts +1 -0
  23. package/dist/config.d.ts.map +1 -1
  24. package/dist/config.js +1 -0
  25. package/dist/globals/SeoDashboard.js +1 -1
  26. package/dist/globals/SeoIntegrations.d.ts +3 -0
  27. package/dist/globals/SeoIntegrations.d.ts.map +1 -0
  28. package/dist/globals/SeoIntegrations.js +305 -0
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +4 -4
  31. package/dist/utilities/crux.d.ts +6 -0
  32. package/dist/utilities/crux.d.ts.map +1 -0
  33. package/dist/utilities/crux.js +244 -0
  34. package/dist/utilities/dataforseo.d.ts +12 -0
  35. package/dist/utilities/dataforseo.d.ts.map +1 -0
  36. package/dist/utilities/dataforseo.js +169 -0
  37. package/dist/utilities/gsc.d.ts +4 -11
  38. package/dist/utilities/gsc.d.ts.map +1 -1
  39. package/dist/utilities/gsc.js +58 -22
  40. package/dist/utilities/integrationSettings.d.ts +10 -0
  41. package/dist/utilities/integrationSettings.d.ts.map +1 -0
  42. package/dist/utilities/integrationSettings.js +198 -0
  43. package/dist/utilities/opsWebhook.d.ts +15 -0
  44. package/dist/utilities/opsWebhook.d.ts.map +1 -0
  45. package/dist/utilities/opsWebhook.js +130 -0
  46. package/dist/utilities/pagespeed.d.ts +1 -1
  47. package/dist/utilities/pagespeed.d.ts.map +1 -1
  48. package/dist/utilities/pagespeed.js +11 -5
  49. package/dist/utilities/providers.d.ts +2 -2
  50. package/dist/utilities/providers.d.ts.map +1 -1
  51. package/dist/utilities/providers.js +12 -7
  52. package/dist/utilities/runAudit.d.ts +4 -1
  53. package/dist/utilities/runAudit.d.ts.map +1 -1
  54. package/dist/utilities/runAudit.js +112 -11
  55. package/dist/utilities/secrets.d.ts +23 -0
  56. package/dist/utilities/secrets.d.ts.map +1 -0
  57. package/dist/utilities/secrets.js +108 -0
  58. package/dist/utilities/types.d.ts +85 -0
  59. package/dist/utilities/types.d.ts.map +1 -1
  60. package/package.json +1 -1
@@ -0,0 +1,305 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SeoIntegrations = void 0;
4
+ const access_1 = require("../utilities/access");
5
+ const secrets_1 = require("../utilities/secrets");
6
+ const secretPaths = [
7
+ 'gsc.oauthClientSecret',
8
+ 'gsc.oauthRefreshToken',
9
+ 'gsc.serviceAccountPrivateKey',
10
+ 'pageSpeed.apiKey',
11
+ 'crux.apiKey',
12
+ 'authority.dataForSeoLogin',
13
+ 'authority.dataForSeoPassword',
14
+ 'ops.webhookSecret',
15
+ ];
16
+ exports.SeoIntegrations = {
17
+ slug: 'seo-integrations',
18
+ label: 'SEO Integrations',
19
+ admin: {
20
+ group: 'SEO',
21
+ description: 'Configure API integrations for Search Console, PageSpeed, CrUX, and authority providers.',
22
+ },
23
+ access: {
24
+ read: access_1.seoAdminAccess,
25
+ update: access_1.seoAdminAccess,
26
+ },
27
+ hooks: {
28
+ beforeChange: [
29
+ async ({ data, originalDoc, req }) => {
30
+ if (!data || typeof data !== 'object')
31
+ return data;
32
+ return (0, secrets_1.encryptSecretPaths)({
33
+ data: data,
34
+ originalDoc: originalDoc || undefined,
35
+ secretPaths,
36
+ payload: req.payload,
37
+ });
38
+ },
39
+ ],
40
+ afterRead: [
41
+ async ({ doc, req }) => {
42
+ if (!doc || typeof doc !== 'object')
43
+ return doc;
44
+ return (0, secrets_1.maskSecretPaths)({
45
+ doc: doc,
46
+ secretPaths,
47
+ req: req,
48
+ });
49
+ },
50
+ ],
51
+ },
52
+ fields: [
53
+ {
54
+ name: 'gsc',
55
+ type: 'group',
56
+ label: 'Google Search Console',
57
+ fields: [
58
+ {
59
+ name: 'enabled',
60
+ type: 'checkbox',
61
+ defaultValue: false,
62
+ },
63
+ {
64
+ name: 'authMode',
65
+ type: 'select',
66
+ defaultValue: 'oauth',
67
+ options: [
68
+ { label: 'OAuth Refresh Token', value: 'oauth' },
69
+ { label: 'Service Account', value: 'service-account' },
70
+ ],
71
+ },
72
+ {
73
+ name: 'siteUrlOverride',
74
+ type: 'text',
75
+ admin: {
76
+ description: 'Optional site URL for GSC queries. Defaults to canonical host.',
77
+ },
78
+ },
79
+ {
80
+ name: 'rowLimit',
81
+ type: 'number',
82
+ defaultValue: 250,
83
+ min: 25,
84
+ max: 25000,
85
+ },
86
+ {
87
+ name: 'lookbackDays',
88
+ type: 'number',
89
+ defaultValue: 7,
90
+ min: 1,
91
+ max: 90,
92
+ },
93
+ {
94
+ name: 'oauthClientID',
95
+ type: 'text',
96
+ admin: {
97
+ condition: (_, siblingData) => siblingData?.authMode === 'oauth',
98
+ },
99
+ },
100
+ {
101
+ name: 'oauthClientSecret',
102
+ type: 'text',
103
+ admin: {
104
+ condition: (_, siblingData) => siblingData?.authMode === 'oauth',
105
+ },
106
+ },
107
+ {
108
+ name: 'oauthRedirectURI',
109
+ type: 'text',
110
+ admin: {
111
+ condition: (_, siblingData) => siblingData?.authMode === 'oauth',
112
+ },
113
+ },
114
+ {
115
+ name: 'oauthRefreshToken',
116
+ type: 'textarea',
117
+ admin: {
118
+ condition: (_, siblingData) => siblingData?.authMode === 'oauth',
119
+ },
120
+ },
121
+ {
122
+ name: 'serviceAccountEmail',
123
+ type: 'text',
124
+ admin: {
125
+ condition: (_, siblingData) => siblingData?.authMode === 'service-account',
126
+ },
127
+ },
128
+ {
129
+ name: 'serviceAccountPrivateKey',
130
+ type: 'textarea',
131
+ admin: {
132
+ condition: (_, siblingData) => siblingData?.authMode === 'service-account',
133
+ },
134
+ },
135
+ ],
136
+ },
137
+ {
138
+ name: 'pageSpeed',
139
+ type: 'group',
140
+ label: 'Google PageSpeed Insights',
141
+ fields: [
142
+ {
143
+ name: 'enabled',
144
+ type: 'checkbox',
145
+ defaultValue: true,
146
+ },
147
+ {
148
+ name: 'apiKey',
149
+ type: 'text',
150
+ admin: {
151
+ description: 'Optional but recommended for stable quota.',
152
+ },
153
+ },
154
+ ],
155
+ },
156
+ {
157
+ name: 'crux',
158
+ type: 'group',
159
+ label: 'Chrome UX Report (CrUX)',
160
+ fields: [
161
+ {
162
+ name: 'enabled',
163
+ type: 'checkbox',
164
+ defaultValue: false,
165
+ },
166
+ {
167
+ name: 'apiKey',
168
+ type: 'text',
169
+ },
170
+ {
171
+ name: 'queryScope',
172
+ type: 'select',
173
+ defaultValue: 'origin+key-urls',
174
+ options: [
175
+ { label: 'Origin + Key URLs', value: 'origin+key-urls' },
176
+ { label: 'Origin Only', value: 'origin-only' },
177
+ { label: 'Key URLs Only', value: 'key-urls-only' },
178
+ ],
179
+ },
180
+ {
181
+ name: 'formFactor',
182
+ type: 'select',
183
+ defaultValue: 'ALL_FORM_FACTORS',
184
+ options: [
185
+ { label: 'All Form Factors', value: 'ALL_FORM_FACTORS' },
186
+ { label: 'Phone', value: 'PHONE' },
187
+ { label: 'Desktop', value: 'DESKTOP' },
188
+ ],
189
+ },
190
+ {
191
+ name: 'countryCode',
192
+ type: 'text',
193
+ admin: {
194
+ description: 'Optional two-letter ISO country code (for example: US).',
195
+ },
196
+ },
197
+ {
198
+ name: 'lookbackWindowDays',
199
+ type: 'number',
200
+ defaultValue: 28,
201
+ min: 1,
202
+ max: 180,
203
+ },
204
+ ],
205
+ },
206
+ {
207
+ name: 'authority',
208
+ type: 'group',
209
+ label: 'Authority Provider',
210
+ fields: [
211
+ {
212
+ name: 'enabled',
213
+ type: 'checkbox',
214
+ defaultValue: false,
215
+ },
216
+ {
217
+ name: 'provider',
218
+ type: 'select',
219
+ defaultValue: 'none',
220
+ options: [
221
+ { label: 'None', value: 'none' },
222
+ { label: 'DataForSEO', value: 'dataforseo' },
223
+ ],
224
+ },
225
+ {
226
+ name: 'captureTopBacklinksLimit',
227
+ type: 'number',
228
+ defaultValue: 100,
229
+ min: 10,
230
+ max: 1000,
231
+ },
232
+ {
233
+ name: 'runPolicy',
234
+ type: 'select',
235
+ defaultValue: 'manual+scheduled',
236
+ options: [
237
+ { label: 'Manual + Scheduled', value: 'manual+scheduled' },
238
+ { label: 'Scheduled Only', value: 'scheduled-only' },
239
+ { label: 'All Run Types', value: 'all' },
240
+ ],
241
+ },
242
+ {
243
+ name: 'dataForSeoLogin',
244
+ type: 'text',
245
+ admin: {
246
+ condition: (_, siblingData) => siblingData?.provider === 'dataforseo',
247
+ },
248
+ },
249
+ {
250
+ name: 'dataForSeoPassword',
251
+ type: 'text',
252
+ admin: {
253
+ condition: (_, siblingData) => siblingData?.provider === 'dataforseo',
254
+ },
255
+ },
256
+ {
257
+ name: 'dataForSeoBaseURL',
258
+ type: 'text',
259
+ defaultValue: 'https://api.dataforseo.com',
260
+ admin: {
261
+ condition: (_, siblingData) => siblingData?.provider === 'dataforseo',
262
+ },
263
+ },
264
+ ],
265
+ },
266
+ {
267
+ name: 'ops',
268
+ type: 'group',
269
+ label: 'Operator Webhook (Private)',
270
+ fields: [
271
+ {
272
+ name: 'enabled',
273
+ type: 'checkbox',
274
+ defaultValue: false,
275
+ },
276
+ {
277
+ name: 'webhookURL',
278
+ type: 'text',
279
+ },
280
+ {
281
+ name: 'webhookSecret',
282
+ type: 'text',
283
+ },
284
+ {
285
+ name: 'sendPerRun',
286
+ type: 'checkbox',
287
+ defaultValue: true,
288
+ },
289
+ {
290
+ name: 'sendMonthlyRollup',
291
+ type: 'checkbox',
292
+ defaultValue: true,
293
+ },
294
+ {
295
+ name: 'lastRollupMonth',
296
+ type: 'text',
297
+ admin: {
298
+ readOnly: true,
299
+ hidden: true,
300
+ },
301
+ },
302
+ ],
303
+ },
304
+ ],
305
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AACrC,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AAazE,YAAY,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAsCpD,eAAO,MAAM,cAAc,GAAI,cAAc,oBAAoB,MACvD,gBAAgB,MAAM,KAAG,MA+BlC,CAAA;AAMD,wBAAgB,YAAY,CAAC,OAAO,EAAE,GAAG,GAAG,mBAAmB,GAAG,IAAI,CAErE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AACrC,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AAYzE,YAAY,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAEpD,eAAO,MAAM,cAAc,GAAI,cAAc,oBAAoB,MACvD,gBAAgB,MAAM,KAAG,MAwBlC,CAAA;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,GAAG,GAAG,mBAAmB,GAAG,IAAI,CAErE"}
package/dist/index.js CHANGED
@@ -7,21 +7,21 @@ const SeoSnapshots_1 = require("./collections/SeoSnapshots");
7
7
  const SeoPageResults_1 = require("./collections/SeoPageResults");
8
8
  const SeoKeywordVisibility_1 = require("./collections/SeoKeywordVisibility");
9
9
  const SeoAuthoritySnapshots_1 = require("./collections/SeoAuthoritySnapshots");
10
+ const SeoDashboardView_1 = require("./collections/SeoDashboardView");
10
11
  const SeoDashboard_1 = require("./globals/SeoDashboard");
12
+ const SeoIntegrations_1 = require("./globals/SeoIntegrations");
11
13
  const seoAuditPlugin = (pluginConfig) => {
12
14
  return (incomingConfig) => {
13
15
  const seoConfig = (0, config_1.normalizeConfig)(pluginConfig);
14
16
  const collections = [
15
17
  ...(incomingConfig.collections || []),
18
+ SeoDashboardView_1.SeoDashboardView,
16
19
  SeoSnapshots_1.SeoSnapshots,
17
20
  SeoPageResults_1.SeoPageResults,
18
21
  SeoKeywordVisibility_1.SeoKeywordVisibility,
19
22
  SeoAuthoritySnapshots_1.SeoAuthoritySnapshots,
20
23
  ];
21
- const globals = [
22
- ...(incomingConfig.globals || []),
23
- SeoDashboard_1.SeoDashboard,
24
- ];
24
+ const globals = [...(incomingConfig.globals || []), SeoDashboard_1.SeoDashboard, SeoIntegrations_1.SeoIntegrations];
25
25
  return {
26
26
  ...incomingConfig,
27
27
  collections,
@@ -0,0 +1,6 @@
1
+ import type { CrUXSyncResult, IntegrationSettings, SEOSiteRecord } from '../utilities/types';
2
+ export declare const getCruxSummary: ({ site, settings, }: {
3
+ site: SEOSiteRecord;
4
+ settings: IntegrationSettings;
5
+ }) => Promise<CrUXSyncResult>;
6
+ //# sourceMappingURL=crux.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crux.d.ts","sourceRoot":"","sources":["../../src/utilities/crux.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,cAAc,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAsLzG,eAAO,MAAM,cAAc,GAAU,qBAGlC;IACD,IAAI,EAAE,aAAa,CAAA;IACnB,QAAQ,EAAE,mBAAmB,CAAA;CAC9B,KAAG,OAAO,CAAC,cAAc,CA2HzB,CAAA"}
@@ -0,0 +1,244 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getCruxSummary = void 0;
4
+ const CRUX_ENDPOINT = 'https://chromeuxreport.googleapis.com/v1/records:queryRecord';
5
+ const normalizeCLS = (value) => {
6
+ if (typeof value !== 'number' || Number.isNaN(value))
7
+ return undefined;
8
+ if (value > 1)
9
+ return Number((value / 100).toFixed(3));
10
+ return Number(value.toFixed(3));
11
+ };
12
+ const parsePercentile = (metric) => {
13
+ const p75 = Number(metric?.percentiles?.p75 ?? metric?.percentile ?? metric?.p75);
14
+ if (!Number.isFinite(p75))
15
+ return undefined;
16
+ return p75;
17
+ };
18
+ const calculateGoodPercentage = ({ metric, threshold, normalize, }) => {
19
+ const histogram = metric?.histogram;
20
+ if (!Array.isArray(histogram) || !histogram.length)
21
+ return undefined;
22
+ let good = 0;
23
+ for (const bucket of histogram) {
24
+ const density = Number(bucket?.density);
25
+ if (!Number.isFinite(density) || density <= 0)
26
+ continue;
27
+ const start = normalize(Number(bucket?.start));
28
+ const end = normalize(Number(bucket?.end));
29
+ if (typeof start !== 'number' || typeof end !== 'number')
30
+ continue;
31
+ if (threshold >= end) {
32
+ good += density;
33
+ continue;
34
+ }
35
+ if (threshold <= start) {
36
+ continue;
37
+ }
38
+ const span = end - start;
39
+ if (span <= 0) {
40
+ continue;
41
+ }
42
+ const proportional = (threshold - start) / span;
43
+ if (proportional > 0) {
44
+ good += density * proportional;
45
+ }
46
+ }
47
+ return Number((good * 100).toFixed(1));
48
+ };
49
+ const parseCruxRecord = (record) => {
50
+ const metrics = record?.metrics;
51
+ if (!metrics || typeof metrics !== 'object')
52
+ return null;
53
+ const lcpMetric = metrics?.largest_contentful_paint;
54
+ const inpMetric = metrics?.interaction_to_next_paint;
55
+ const clsMetric = metrics?.cumulative_layout_shift;
56
+ const lcpP75 = parsePercentile(lcpMetric);
57
+ const inpP75 = parsePercentile(inpMetric);
58
+ const clsP75Raw = parsePercentile(clsMetric);
59
+ const clsP75 = normalizeCLS(clsP75Raw);
60
+ return {
61
+ lcp: {
62
+ p75: Number.isFinite(lcpP75) ? lcpP75 : undefined,
63
+ goodPct: calculateGoodPercentage({
64
+ metric: lcpMetric,
65
+ threshold: 2500,
66
+ normalize: (value) => value,
67
+ }),
68
+ },
69
+ inp: {
70
+ p75: Number.isFinite(inpP75) ? inpP75 : undefined,
71
+ goodPct: calculateGoodPercentage({
72
+ metric: inpMetric,
73
+ threshold: 200,
74
+ normalize: (value) => value,
75
+ }),
76
+ },
77
+ cls: {
78
+ p75: clsP75,
79
+ goodPct: calculateGoodPercentage({
80
+ metric: clsMetric,
81
+ threshold: 0.1,
82
+ normalize: normalizeCLS,
83
+ }),
84
+ },
85
+ };
86
+ };
87
+ const queryCruxRecord = async ({ apiKey, formFactor, countryCode, origin, url, }) => {
88
+ const endpoint = new URL(CRUX_ENDPOINT);
89
+ endpoint.searchParams.set('key', apiKey);
90
+ const body = {
91
+ formFactor,
92
+ };
93
+ if (origin) {
94
+ body.origin = origin;
95
+ }
96
+ if (url) {
97
+ body.url = url;
98
+ }
99
+ if (countryCode) {
100
+ body.country = countryCode;
101
+ }
102
+ const response = await fetch(endpoint.toString(), {
103
+ method: 'POST',
104
+ cache: 'no-store',
105
+ headers: {
106
+ 'content-type': 'application/json',
107
+ },
108
+ body: JSON.stringify(body),
109
+ });
110
+ if (response.status === 404) {
111
+ return null;
112
+ }
113
+ if (!response.ok) {
114
+ throw new Error(`CrUX API request failed with status ${response.status}`);
115
+ }
116
+ const json = (await response.json());
117
+ return parseCruxRecord(json.record);
118
+ };
119
+ const averageMetric = (values) => {
120
+ const valid = values.filter((value) => typeof value === 'number' && Number.isFinite(value));
121
+ if (!valid.length)
122
+ return undefined;
123
+ return Number((valid.reduce((sum, value) => sum + value, 0) / valid.length).toFixed(2));
124
+ };
125
+ const defaultSummary = () => ({
126
+ cruxAvailable: false,
127
+ cruxSource: 'none',
128
+ cruxSampleUrlsChecked: 0,
129
+ cruxSampleUrlsWithData: 0,
130
+ });
131
+ const getCruxSummary = async ({ site, settings, }) => {
132
+ if (!settings.crux.enabled) {
133
+ return {
134
+ enabled: false,
135
+ available: false,
136
+ skipped: 'disabled',
137
+ summary: defaultSummary(),
138
+ };
139
+ }
140
+ const apiKey = settings.crux.apiKey.trim();
141
+ if (!apiKey) {
142
+ return {
143
+ enabled: true,
144
+ available: false,
145
+ skipped: 'missing-api-key',
146
+ summary: defaultSummary(),
147
+ };
148
+ }
149
+ const canonicalHost = (site.canonicalHost || '').trim();
150
+ if (!canonicalHost) {
151
+ return {
152
+ enabled: true,
153
+ available: false,
154
+ skipped: 'missing-site-host',
155
+ summary: defaultSummary(),
156
+ };
157
+ }
158
+ const origin = (() => {
159
+ try {
160
+ return new URL(canonicalHost).origin;
161
+ }
162
+ catch {
163
+ return canonicalHost;
164
+ }
165
+ })();
166
+ const sampleUrls = (site.keyURLs || [])
167
+ .map((entry) => entry?.url?.trim())
168
+ .filter((value) => Boolean(value))
169
+ .slice(0, 5);
170
+ const includeOrigin = settings.crux.queryScope !== 'key-urls-only';
171
+ const includeUrls = settings.crux.queryScope !== 'origin-only';
172
+ try {
173
+ const records = [];
174
+ let urlChecks = 0;
175
+ let urlHits = 0;
176
+ let hasOriginData = false;
177
+ if (includeOrigin) {
178
+ const originRecord = await queryCruxRecord({
179
+ apiKey,
180
+ formFactor: settings.crux.formFactor,
181
+ countryCode: settings.crux.countryCode || undefined,
182
+ origin,
183
+ });
184
+ if (originRecord) {
185
+ hasOriginData = true;
186
+ records.push(originRecord);
187
+ }
188
+ }
189
+ if (includeUrls) {
190
+ for (const url of sampleUrls) {
191
+ urlChecks += 1;
192
+ const record = await queryCruxRecord({
193
+ apiKey,
194
+ formFactor: settings.crux.formFactor,
195
+ countryCode: settings.crux.countryCode || undefined,
196
+ url,
197
+ });
198
+ if (record) {
199
+ urlHits += 1;
200
+ records.push(record);
201
+ }
202
+ }
203
+ }
204
+ if (!records.length) {
205
+ return {
206
+ enabled: true,
207
+ available: false,
208
+ skipped: 'no-data',
209
+ summary: {
210
+ ...defaultSummary(),
211
+ cruxSampleUrlsChecked: urlChecks,
212
+ cruxSampleUrlsWithData: urlHits,
213
+ },
214
+ };
215
+ }
216
+ const summary = {
217
+ cruxAvailable: true,
218
+ cruxSource: hasOriginData && urlHits > 0 ? 'mixed' : hasOriginData ? 'origin' : 'url',
219
+ cruxP75LCPMs: averageMetric(records.map((record) => record.lcp.p75)),
220
+ cruxP75INPMs: averageMetric(records.map((record) => record.inp.p75)),
221
+ cruxP75CLS: averageMetric(records.map((record) => record.cls.p75)),
222
+ cruxGoodLCPPct: averageMetric(records.map((record) => record.lcp.goodPct)),
223
+ cruxGoodINPPct: averageMetric(records.map((record) => record.inp.goodPct)),
224
+ cruxGoodCLSPct: averageMetric(records.map((record) => record.cls.goodPct)),
225
+ cruxSampleUrlsChecked: urlChecks,
226
+ cruxSampleUrlsWithData: urlHits,
227
+ };
228
+ return {
229
+ enabled: true,
230
+ available: true,
231
+ summary,
232
+ };
233
+ }
234
+ catch (error) {
235
+ return {
236
+ enabled: true,
237
+ available: false,
238
+ skipped: 'request-failed',
239
+ error: error instanceof Error ? error.message : String(error),
240
+ summary: defaultSummary(),
241
+ };
242
+ }
243
+ };
244
+ exports.getCruxSummary = getCruxSummary;
@@ -0,0 +1,12 @@
1
+ import type { Payload } from 'payload';
2
+ import type { AuthoritySyncResult, IntegrationSettings, SEOSiteRecord } from '../utilities/types';
3
+ export declare const syncDataForSEOAuthoritySnapshot: ({ payload, site, settings, }: {
4
+ payload: Payload;
5
+ site: SEOSiteRecord;
6
+ settings: IntegrationSettings;
7
+ }) => Promise<AuthoritySyncResult>;
8
+ export declare const shouldRunAuthoritySync: ({ runType, settings, }: {
9
+ runType: "manual" | "scheduled" | "publish-triggered";
10
+ settings: IntegrationSettings;
11
+ }) => boolean;
12
+ //# sourceMappingURL=dataforseo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataforseo.d.ts","sourceRoot":"","sources":["../../src/utilities/dataforseo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAkEjG,eAAO,MAAM,+BAA+B,GAAU,8BAInD;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,EAAE,aAAa,CAAA;IACnB,QAAQ,EAAE,mBAAmB,CAAA;CAC9B,KAAG,OAAO,CAAC,mBAAmB,CAqI9B,CAAA;AAED,eAAO,MAAM,sBAAsB,GAAI,wBAGpC;IACD,OAAO,EAAE,QAAQ,GAAG,WAAW,GAAG,mBAAmB,CAAA;IACrD,QAAQ,EAAE,mBAAmB,CAAA;CAC9B,YAMA,CAAA"}