@adobe/spacecat-shared-data-access 3.44.0 → 3.45.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,10 @@
|
|
|
1
|
+
## [@adobe/spacecat-shared-data-access-v3.45.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.44.0...@adobe/spacecat-shared-data-access-v3.45.0) (2026-04-04)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* Manage a custom list of audit target URLs ([#1484](https://github.com/adobe/spacecat-shared/issues/1484)) ([06c54d8](https://github.com/adobe/spacecat-shared/commit/06c54d89ea2488f6951cc96b9458521ac9d26706))
|
|
6
|
+
* update suggestion data schemas for some of the opportunities ([#1466](https://github.com/adobe/spacecat-shared/issues/1466)) ([a09e968](https://github.com/adobe/spacecat-shared/commit/a09e968bba9235813451b65defe7d133a8956711))
|
|
7
|
+
|
|
1
8
|
## [@adobe/spacecat-shared-data-access-v3.44.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.43.0...@adobe/spacecat-shared-data-access-v3.44.0) (2026-04-02)
|
|
2
9
|
|
|
3
10
|
### Features
|
package/package.json
CHANGED
|
@@ -400,6 +400,11 @@ export const configSchema = Joi.object({
|
|
|
400
400
|
contentAiConfig: Joi.object({
|
|
401
401
|
index: Joi.string().optional(),
|
|
402
402
|
}).optional(),
|
|
403
|
+
auditTargetURLs: Joi.object({
|
|
404
|
+
manual: Joi.array().items(Joi.object({
|
|
405
|
+
url: Joi.string().uri().required(),
|
|
406
|
+
})).optional().default([]),
|
|
407
|
+
}).options({ stripUnknown: true }).optional(),
|
|
403
408
|
handlers: Joi.object().pattern(Joi.string(), Joi.object({
|
|
404
409
|
mentions: Joi.object().pattern(Joi.string(), Joi.array().items(Joi.string())),
|
|
405
410
|
excludedURLs: Joi.array().items(Joi.string()),
|
|
@@ -510,6 +515,60 @@ export const Config = (data = {}) => {
|
|
|
510
515
|
self.getEdgeOptimizeConfig = () => state?.edgeOptimizeConfig;
|
|
511
516
|
self.getOnboardConfig = () => state?.onboardConfig;
|
|
512
517
|
self.getCommerceLlmoConfig = () => state?.commerceLlmoConfig;
|
|
518
|
+
const AUDIT_TARGET_SOURCES = ['manual'];
|
|
519
|
+
const auditTargetEntrySchema = Joi.object({
|
|
520
|
+
url: Joi.string().uri().required(),
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
const validateAuditTargetSource = (source) => {
|
|
524
|
+
if (!AUDIT_TARGET_SOURCES.includes(source)) {
|
|
525
|
+
throw new Error(`Invalid audit target source: "${source}". Must be one of: ${AUDIT_TARGET_SOURCES.join(', ')}`);
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
self.getAuditTargetURLsConfig = () => state?.auditTargetURLs;
|
|
530
|
+
|
|
531
|
+
self.getAuditTargetURLs = () => {
|
|
532
|
+
const targets = state?.auditTargetURLs;
|
|
533
|
+
if (!targets) return [];
|
|
534
|
+
return AUDIT_TARGET_SOURCES.flatMap(
|
|
535
|
+
(source) => (targets[source] || []).map((entry) => ({ ...entry, source })),
|
|
536
|
+
);
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
self.getAuditTargetURLsBySource = (source) => {
|
|
540
|
+
validateAuditTargetSource(source);
|
|
541
|
+
return state?.auditTargetURLs?.[source] || [];
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
self.updateAuditTargetURLs = (source, urls) => {
|
|
545
|
+
validateAuditTargetSource(source);
|
|
546
|
+
Joi.assert(urls, Joi.array().items(auditTargetEntrySchema), 'Invalid audit target URLs');
|
|
547
|
+
state.auditTargetURLs = state.auditTargetURLs || {};
|
|
548
|
+
state.auditTargetURLs[source] = urls;
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
self.addAuditTargetURL = (source, urlObj) => {
|
|
552
|
+
validateAuditTargetSource(source);
|
|
553
|
+
Joi.assert(urlObj, auditTargetEntrySchema, 'Invalid audit target URL');
|
|
554
|
+
|
|
555
|
+
state.auditTargetURLs = state.auditTargetURLs || {};
|
|
556
|
+
state.auditTargetURLs[source] = state.auditTargetURLs[source] || [];
|
|
557
|
+
const allUrls = AUDIT_TARGET_SOURCES.flatMap(
|
|
558
|
+
(s) => (state.auditTargetURLs[s] || []).map((e) => e.url),
|
|
559
|
+
);
|
|
560
|
+
if (!allUrls.includes(urlObj.url)) {
|
|
561
|
+
state.auditTargetURLs[source].push(urlObj);
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
self.removeAuditTargetURL = (source, url) => {
|
|
566
|
+
validateAuditTargetSource(source);
|
|
567
|
+
if (!state.auditTargetURLs?.[source]) return;
|
|
568
|
+
state.auditTargetURLs[source] = state.auditTargetURLs[source]
|
|
569
|
+
.filter((t) => t.url !== url);
|
|
570
|
+
};
|
|
571
|
+
|
|
513
572
|
self.updateSlackConfig = (channel, workspace, invitedUserCount) => {
|
|
514
573
|
state.slack = {
|
|
515
574
|
channel,
|
|
@@ -864,4 +923,5 @@ Config.toDynamoItem = (config) => ({
|
|
|
864
923
|
edgeOptimizeConfig: config.getEdgeOptimizeConfig(),
|
|
865
924
|
onboardConfig: config.getOnboardConfig?.(),
|
|
866
925
|
commerceLlmoConfig: config.getCommerceLlmoConfig?.(),
|
|
926
|
+
auditTargetURLs: config.getAuditTargetURLsConfig?.(),
|
|
867
927
|
});
|
|
@@ -99,6 +99,20 @@ export interface LlmoCustomerIntent {
|
|
|
99
99
|
value: string;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
export type AuditTargetSource = 'manual';
|
|
103
|
+
|
|
104
|
+
export interface AuditTargetEntry {
|
|
105
|
+
url: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface AuditTargetEntryWithSource extends AuditTargetEntry {
|
|
109
|
+
source: AuditTargetSource;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface AuditTargetURLs {
|
|
113
|
+
manual?: AuditTargetEntry[];
|
|
114
|
+
}
|
|
115
|
+
|
|
102
116
|
export interface SiteConfig {
|
|
103
117
|
state: {
|
|
104
118
|
slack?: {
|
|
@@ -107,6 +121,7 @@ export interface SiteConfig {
|
|
|
107
121
|
invitedUserCount?: number;
|
|
108
122
|
};
|
|
109
123
|
imports?: ImportConfig[];
|
|
124
|
+
auditTargetURLs?: AuditTargetURLs;
|
|
110
125
|
handlers?: Record<string, {
|
|
111
126
|
mentions?: Record<string, string[]>;
|
|
112
127
|
excludedURLs?: string[];
|
|
@@ -205,6 +220,11 @@ export interface SiteConfig {
|
|
|
205
220
|
removeLlmoTag(tag: string): void;
|
|
206
221
|
getOnboardConfig(): { lastProfile?: string; lastStartTime?: number; forcedOverride?: boolean; history?: Array<{ profile?: string; startTime?: number }> } | undefined;
|
|
207
222
|
updateOnboardConfig(onboardConfig: { lastProfile?: string; lastStartTime?: number; forcedOverride?: boolean }, options?: { maxHistory?: number }): void;
|
|
223
|
+
getAuditTargetURLs(): AuditTargetEntryWithSource[];
|
|
224
|
+
getAuditTargetURLsBySource(source: AuditTargetSource): AuditTargetEntry[];
|
|
225
|
+
updateAuditTargetURLs(source: AuditTargetSource, urls: AuditTargetEntry[]): void;
|
|
226
|
+
addAuditTargetURL(source: AuditTargetSource, urlObj: AuditTargetEntry): void;
|
|
227
|
+
removeAuditTargetURL(source: AuditTargetSource, url: string): void;
|
|
208
228
|
}
|
|
209
229
|
|
|
210
230
|
export interface Site extends BaseModel {
|
|
@@ -26,6 +26,17 @@
|
|
|
26
26
|
import Joi from 'joi';
|
|
27
27
|
import { OPPORTUNITY_TYPES } from '@adobe/spacecat-shared-utils';
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Custom Joi validator that accepts malformed HTTP/HTTPS URLs and relative paths
|
|
31
|
+
* while rejecting dangerous URI schemes (javascript:, data:, blob:, etc.).
|
|
32
|
+
* Used for BROKEN_INTERNAL_LINKS where crawled content may contain malformed URLs.
|
|
33
|
+
*/
|
|
34
|
+
const relaxedUrl = Joi.string().min(1).custom((value, helpers) => (
|
|
35
|
+
/^(https?:\/\/|\/)/i.test(value) ? value : helpers.error('string.uriScheme')
|
|
36
|
+
), 'relaxed URL').messages({
|
|
37
|
+
'string.uriScheme': '{{#label}} must start with http://, https://, or /',
|
|
38
|
+
});
|
|
39
|
+
|
|
29
40
|
/**
|
|
30
41
|
* Data schemas configuration per opportunity type.
|
|
31
42
|
*
|
|
@@ -85,6 +96,8 @@ export const DATA_SCHEMAS = {
|
|
|
85
96
|
).required(),
|
|
86
97
|
jiraLink: Joi.string().uri().allow(null).optional(),
|
|
87
98
|
aggregationKey: Joi.string().optional(),
|
|
99
|
+
patchContent: Joi.string().optional(),
|
|
100
|
+
isCodeChangeAvailable: Joi.boolean().optional(),
|
|
88
101
|
}).unknown(true),
|
|
89
102
|
projections: {
|
|
90
103
|
minimal: {
|
|
@@ -113,6 +126,8 @@ export const DATA_SCHEMAS = {
|
|
|
113
126
|
).required(),
|
|
114
127
|
jiraLink: Joi.string().uri().allow(null).optional(),
|
|
115
128
|
aggregationKey: Joi.string().optional(),
|
|
129
|
+
patchContent: Joi.string().optional(),
|
|
130
|
+
isCodeChangeAvailable: Joi.boolean().optional(),
|
|
116
131
|
}).unknown(true),
|
|
117
132
|
projections: {
|
|
118
133
|
minimal: {
|
|
@@ -123,10 +138,14 @@ export const DATA_SCHEMAS = {
|
|
|
123
138
|
},
|
|
124
139
|
},
|
|
125
140
|
},
|
|
141
|
+
// CWV has two implicit data shapes:
|
|
142
|
+
// 1. Page-level (type='url'): url and issues are present
|
|
143
|
+
// 2. Group-type (type='group'): url is absent, issues may be populated later via update
|
|
144
|
+
// Both shapes share the same schema; url and issues are optional to support both.
|
|
126
145
|
[OPPORTUNITY_TYPES.CWV]: {
|
|
127
146
|
schema: Joi.object({
|
|
128
147
|
type: Joi.string().required(),
|
|
129
|
-
url: Joi.string().uri().
|
|
148
|
+
url: Joi.string().uri().optional(),
|
|
130
149
|
pageviews: Joi.number().optional(),
|
|
131
150
|
organic: Joi.number().optional(),
|
|
132
151
|
metrics: Joi.array().items(
|
|
@@ -144,7 +163,7 @@ export const DATA_SCHEMAS = {
|
|
|
144
163
|
organic: Joi.number().optional(),
|
|
145
164
|
}).unknown(true),
|
|
146
165
|
).required(),
|
|
147
|
-
issues: Joi.array().items(Joi.object()).
|
|
166
|
+
issues: Joi.array().items(Joi.object()).optional().default([]),
|
|
148
167
|
jiraLink: Joi.string().uri().allow(null).optional(),
|
|
149
168
|
aggregationKey: Joi.string().allow(null).optional(),
|
|
150
169
|
}).unknown(true),
|
|
@@ -163,6 +182,7 @@ export const DATA_SCHEMAS = {
|
|
|
163
182
|
Joi.object({
|
|
164
183
|
isAppropriate: Joi.boolean().optional(),
|
|
165
184
|
isDecorative: Joi.boolean().optional(),
|
|
185
|
+
hasAltAttribute: Joi.boolean().optional(),
|
|
166
186
|
xpath: Joi.string().optional(),
|
|
167
187
|
altText: Joi.string().optional(),
|
|
168
188
|
imageUrl: Joi.string().uri().optional(),
|
|
@@ -376,12 +396,16 @@ export const DATA_SCHEMAS = {
|
|
|
376
396
|
[OPPORTUNITY_TYPES.BROKEN_INTERNAL_LINKS]: {
|
|
377
397
|
schema: Joi.object({
|
|
378
398
|
// Support both naming conventions (snake_case and camelCase)
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
399
|
+
// URL fields use relaxedUrl instead of Joi.string().uri() because
|
|
400
|
+
// internal-links sometimes keeps URL values as-is, including malformed URLs.
|
|
401
|
+
// Unlike BROKEN_BACKLINKS, these accept malformed http/https/relative URLs
|
|
402
|
+
// but reject dangerous schemes (javascript:, data:, blob:, etc.).
|
|
403
|
+
url_from: relaxedUrl.optional(),
|
|
404
|
+
urlFrom: relaxedUrl.optional(),
|
|
405
|
+
url_to: relaxedUrl.optional(),
|
|
406
|
+
urlTo: relaxedUrl.optional(),
|
|
383
407
|
title: Joi.string().optional(),
|
|
384
|
-
urlsSuggested: Joi.array().items(
|
|
408
|
+
urlsSuggested: Joi.array().items(relaxedUrl).optional(),
|
|
385
409
|
aiRationale: Joi.string().optional(),
|
|
386
410
|
trafficDomain: Joi.number().optional(),
|
|
387
411
|
priority: Joi.string().optional(),
|