@adobe/spacecat-shared-utils 1.60.0 → 1.62.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 CHANGED
@@ -1,3 +1,17 @@
1
+ # [@adobe/spacecat-shared-utils-v1.62.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.61.0...@adobe/spacecat-shared-utils-v1.62.0) (2025-10-23)
2
+
3
+
4
+ ### Features
5
+
6
+ * new origin for category ([#1039](https://github.com/adobe/spacecat-shared/issues/1039)) ([f33c0cf](https://github.com/adobe/spacecat-shared/commit/f33c0cfabc6856955dcf9cea33158e04df4ab82a))
7
+
8
+ # [@adobe/spacecat-shared-utils-v1.61.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.60.0...@adobe/spacecat-shared-utils-v1.61.0) (2025-10-23)
9
+
10
+
11
+ ### Features
12
+
13
+ * SITES-36623 added url extraction for opportunities and suggestions ([#1038](https://github.com/adobe/spacecat-shared/issues/1038)) ([1e16802](https://github.com/adobe/spacecat-shared/commit/1e16802b8189f64cc71c9ecc8d0c100c350408f7))
14
+
1
15
  # [@adobe/spacecat-shared-utils-v1.60.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.59.4...@adobe/spacecat-shared-utils-v1.60.0) (2025-10-21)
2
16
 
3
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-utils",
3
- "version": "1.60.0",
3
+ "version": "1.62.0",
4
4
  "description": "Shared modules of the Spacecat Services - utils",
5
5
  "type": "module",
6
6
  "exports": {
@@ -0,0 +1,71 @@
1
+ /*
2
+ * Copyright 2025 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+ const OPPORTUNITY_TYPES = {
14
+ // Core Audit Types
15
+ ACCESSIBILITY: 'accessibility',
16
+ ALT_TEXT: 'alt-text',
17
+ BROKEN_BACKLINKS: 'broken-backlinks',
18
+ BROKEN_INTERNAL_LINKS: 'broken-internal-links',
19
+ CANONICAL: 'canonical',
20
+ CWV: 'cwv',
21
+ HEADINGS: 'headings',
22
+ HREFLANG: 'hreflang',
23
+ INVALID_OR_MISSING_METADATA: 'meta-tags',
24
+ NOTFOUND: '404',
25
+ PRERENDER: 'prerender',
26
+ SECURITY_CSP: 'security-csp',
27
+ SECURITY_VULNERABILITIES: 'security-vulnerabilities',
28
+ SITEMAP: 'sitemap',
29
+ STRUCTURED_DATA: 'structured-data',
30
+
31
+ // Custom Audit Types (not in shared AUDIT_TYPES)
32
+ LLM_BLOCKED: 'llm-blocked',
33
+ REDIRECT_CHAINS: 'redirect-chains',
34
+ SECURITY_PERMISSIONS: 'security-permissions',
35
+ SECURITY_PERMISSIONS_REDUNDANT: 'security-permissions-redundant',
36
+ SITEMAP_PRODUCT_COVERAGE: 'sitemap-product-coverage',
37
+
38
+ // Experimentation Opportunities
39
+ HIGH_ORGANIC_LOW_CTR: 'high-organic-low-ctr',
40
+ RAGECLICK: 'rageclick',
41
+ HIGH_INORGANIC_HIGH_BOUNCE_RATE: 'high-inorganic-high-bounce-rate',
42
+
43
+ // Forms Opportunities
44
+ HIGH_FORM_VIEWS_LOW_CONVERSIONS: 'high-form-views-low-conversions',
45
+ HIGH_PAGE_VIEWS_LOW_FORM_NAV: 'high-page-views-low-form-nav',
46
+ HIGH_PAGE_VIEWS_LOW_FORM_VIEWS: 'high-page-views-low-form-views',
47
+ FORM_ACCESSIBILITY: 'form-accessibility',
48
+
49
+ // Geo Brand Presence
50
+ DETECT_GEO_BRAND_PRESENCE: 'detect:geo-brand-presence',
51
+ DETECT_GEO_BRAND_PRESENCE_DAILY: 'detect:geo-brand-presence-daily',
52
+ GEO_BRAND_PRESENCE_TRIGGER_REFRESH: 'geo-brand-presence-trigger-refresh',
53
+ GUIDANCE_GEO_FAQ: 'guidance:geo-faq',
54
+
55
+ // Accessibility Sub-types
56
+ A11Y_ASSISTIVE: 'a11y-assistive',
57
+ COLOR_CONTRAST: 'a11y-color-contrast',
58
+
59
+ // Security
60
+ SECURITY_XSS: 'security-xss',
61
+
62
+ // Generic Opportunity
63
+ GENERIC_OPPORTUNITY: 'generic-opportunity',
64
+
65
+ // Paid Cookie Consent
66
+ PAID_COOKIE_CONSENT: 'paid-cookie-consent',
67
+ };
68
+
69
+ export {
70
+ OPPORTUNITY_TYPES,
71
+ };
package/src/index.d.ts CHANGED
@@ -272,6 +272,28 @@ export function isoCalendarWeekSunday(date: Date): Date;
272
272
 
273
273
  export function isoCalendarWeekMonday(date: Date): Date;
274
274
 
275
+ /**
276
+ * Extracts URLs from a suggestion based on the opportunity type.
277
+ * @param opts - Options object
278
+ * @param opts.opportunity - The opportunity object
279
+ * @param opts.suggestion - The suggestion object
280
+ * @returns An array of extracted URLs
281
+ */
282
+ export function extractUrlsFromSuggestion(opts: {
283
+ opportunity: any;
284
+ suggestion: any;
285
+ }): string[];
286
+
287
+ /**
288
+ * Extracts URLs from an opportunity based on the opportunity type.
289
+ * @param opts - Options object
290
+ * @param opts.opportunity - The opportunity object
291
+ * @returns An array of extracted URLs
292
+ */
293
+ export function extractUrlsFromOpportunity(opts: {
294
+ opportunity: any;
295
+ }): string[];
296
+
275
297
  export * as llmoConfig from './llmo-config.js';
276
298
  export * as schemas from './schemas.js';
277
299
 
package/src/index.js CHANGED
@@ -68,6 +68,11 @@ export {
68
68
  urlMatchesFilter,
69
69
  } from './url-helpers.js';
70
70
 
71
+ export {
72
+ extractUrlsFromOpportunity,
73
+ extractUrlsFromSuggestion,
74
+ } from './url-extractors.js';
75
+
71
76
  export { getStoredMetrics, storeMetrics } from './metrics-store.js';
72
77
 
73
78
  export { s3Wrapper } from './s3.js';
package/src/schemas.js CHANGED
@@ -53,6 +53,7 @@ const entity = z.object({
53
53
  const category = z.object({
54
54
  name: nonEmptyString,
55
55
  region: z.union([region, z.array(region)]),
56
+ origin: z.union([z.literal('human'), z.literal('ai'), z.string()]).optional(),
56
57
  });
57
58
 
58
59
  const topic = z.object({
@@ -0,0 +1,183 @@
1
+ /*
2
+ * Copyright 2025 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+ import { OPPORTUNITY_TYPES } from './constants.js';
14
+
15
+ /**
16
+ * Function to extract the URL from a suggestion based on a particular type
17
+ * @param {*} suggestion
18
+ */
19
+ function extractUrlsFromSuggestion(opts) {
20
+ const {
21
+ opportunity,
22
+ suggestion,
23
+ } = opts;
24
+
25
+ const urls = [];
26
+
27
+ try {
28
+ const opportunityType = opportunity.getType();
29
+ const data = suggestion.getData ? suggestion.getData() : suggestion.data;
30
+
31
+ switch (opportunityType) {
32
+ case OPPORTUNITY_TYPES.ALT_TEXT:
33
+ {
34
+ const recommendations = data?.recommendations;
35
+ if (Array.isArray(recommendations)) {
36
+ recommendations.forEach((rec) => {
37
+ if (rec.pageUrl && typeof rec.pageUrl === 'string') {
38
+ urls.push(rec.pageUrl);
39
+ }
40
+ });
41
+ }
42
+ }
43
+ break;
44
+ case OPPORTUNITY_TYPES.ACCESSIBILITY:
45
+ case OPPORTUNITY_TYPES.COLOR_CONTRAST:
46
+ case OPPORTUNITY_TYPES.STRUCTURED_DATA:
47
+ case OPPORTUNITY_TYPES.CANONICAL:
48
+ case OPPORTUNITY_TYPES.HREFLANG:
49
+ case OPPORTUNITY_TYPES.HEADINGS:
50
+ case OPPORTUNITY_TYPES.INVALID_OR_MISSING_METADATA:
51
+ case OPPORTUNITY_TYPES.SITEMAP_PRODUCT_COVERAGE:
52
+ {
53
+ const url = data?.url;
54
+ if (url && typeof url === 'string') {
55
+ urls.push(url);
56
+ }
57
+ }
58
+ break;
59
+ case OPPORTUNITY_TYPES.CWV:
60
+ {
61
+ const { type } = data;
62
+ const url = data?.url;
63
+ if (type === 'url' && url && typeof url === 'string') {
64
+ urls.push(url);
65
+ }
66
+ }
67
+ break;
68
+ case OPPORTUNITY_TYPES.REDIRECT_CHAINS:
69
+ {
70
+ const sourceUrl = data?.sourceUrl;
71
+ if (sourceUrl && typeof sourceUrl === 'string') {
72
+ urls.push(sourceUrl);
73
+ }
74
+ }
75
+ break;
76
+ case OPPORTUNITY_TYPES.SECURITY_XSS:
77
+ {
78
+ const url = data?.link;
79
+ if (url && typeof url === 'string') {
80
+ urls.push(url);
81
+ }
82
+ }
83
+ break;
84
+ case OPPORTUNITY_TYPES.SECURITY_CSP:
85
+ {
86
+ const findings = data?.findings;
87
+ if (Array.isArray(findings)) {
88
+ findings.forEach((finding) => {
89
+ if (finding.url && typeof finding.url === 'string') {
90
+ urls.push(finding.url);
91
+ }
92
+ });
93
+ }
94
+ }
95
+ break;
96
+ case OPPORTUNITY_TYPES.SECURITY_PERMISSIONS:
97
+ {
98
+ const url = data?.path;
99
+ if (url && typeof url === 'string') {
100
+ urls.push(url);
101
+ }
102
+ }
103
+ break;
104
+ case OPPORTUNITY_TYPES.BROKEN_BACKLINKS:
105
+ case OPPORTUNITY_TYPES.BROKEN_INTERNAL_LINKS:
106
+ {
107
+ const url = data?.url_to;
108
+ if (url && typeof url === 'string') {
109
+ urls.push(url);
110
+ }
111
+ }
112
+ break;
113
+ case OPPORTUNITY_TYPES.SITEMAP:
114
+ {
115
+ const url = data?.pageUrl;
116
+ if (url && typeof url === 'string') {
117
+ urls.push(url);
118
+ }
119
+ }
120
+ break;
121
+ default:
122
+ break;
123
+ }
124
+ } catch (error) {
125
+ // Silently handle errors and return empty array
126
+ }
127
+ return urls;
128
+ }
129
+
130
+ /**
131
+ * Function to extract the URL from an opportunity based on a particular type
132
+ * @param {*} opportunity
133
+ */
134
+ function extractUrlsFromOpportunity(opts) {
135
+ const {
136
+ opportunity,
137
+ } = opts;
138
+ const urls = [];
139
+
140
+ try {
141
+ const opportunityType = opportunity.getType();
142
+ const data = opportunity.getData ? opportunity.getData() : opportunity.data;
143
+
144
+ switch (opportunityType) {
145
+ case OPPORTUNITY_TYPES.HIGH_ORGANIC_LOW_CTR:
146
+ {
147
+ const url = data?.page;
148
+ if (url && typeof url === 'string') {
149
+ urls.push(url);
150
+ }
151
+ }
152
+ break;
153
+ case OPPORTUNITY_TYPES.HIGH_FORM_VIEWS_LOW_CONVERSIONS:
154
+ case OPPORTUNITY_TYPES.HIGH_PAGE_VIEWS_LOW_FORM_NAV:
155
+ case OPPORTUNITY_TYPES.HIGH_PAGE_VIEWS_LOW_FORM_VIEWS:
156
+ {
157
+ const url = data?.form;
158
+ if (url && typeof url === 'string') {
159
+ urls.push(url);
160
+ }
161
+ }
162
+ break;
163
+ case OPPORTUNITY_TYPES.FORM_ACCESSIBILITY:
164
+ {
165
+ const url = data?.url;
166
+ if (url && typeof url === 'string') {
167
+ urls.push(url);
168
+ }
169
+ }
170
+ break;
171
+ default:
172
+ break;
173
+ }
174
+ } catch (error) {
175
+ // Silently handle errors and return empty array
176
+ }
177
+ return urls;
178
+ }
179
+
180
+ export {
181
+ extractUrlsFromSuggestion,
182
+ extractUrlsFromOpportunity,
183
+ };