@adobe/spacecat-shared-utils 1.60.0 → 1.61.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 +7 -0
- package/package.json +1 -1
- package/src/constants.js +71 -0
- package/src/index.d.ts +22 -0
- package/src/index.js +5 -0
- package/src/url-extractors.js +183 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [@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)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* 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))
|
|
7
|
+
|
|
1
8
|
# [@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
9
|
|
|
3
10
|
|
package/package.json
CHANGED
package/src/constants.js
ADDED
|
@@ -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';
|
|
@@ -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
|
+
};
|