@adobe/spacecat-shared-utils 1.48.0 → 1.50.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.50.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.49.0...@adobe/spacecat-shared-utils-v1.50.0) (2025-08-28)
2
+
3
+
4
+ ### Features
5
+
6
+ * add email validation function using validator package ([#938](https://github.com/adobe/spacecat-shared/issues/938)) ([5ec8aec](https://github.com/adobe/spacecat-shared/commit/5ec8aec460abbf19fc0756a05da5f50ff7fed79a))
7
+
8
+ # [@adobe/spacecat-shared-utils-v1.49.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.48.0...@adobe/spacecat-shared-utils-v1.49.0) (2025-08-26)
9
+
10
+
11
+ ### Features
12
+
13
+ * AEM utils ([#927](https://github.com/adobe/spacecat-shared/issues/927)) ([57fc3df](https://github.com/adobe/spacecat-shared/commit/57fc3df80cbacfdc66443090e073367856ec2f7c))
14
+
1
15
  # [@adobe/spacecat-shared-utils-v1.48.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.47.0...@adobe/spacecat-shared-utils-v1.48.0) (2025-08-18)
2
16
 
3
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-utils",
3
- "version": "1.48.0",
3
+ "version": "1.50.0",
4
4
  "description": "Shared modules of the Spacecat Services - utils",
5
5
  "type": "module",
6
6
  "engines": {
@@ -35,6 +35,7 @@
35
35
  },
36
36
  "devDependencies": {
37
37
  "@adobe/helix-shared-wrap": "2.0.2",
38
+ "@types/validator": "^13.15.2",
38
39
  "chai": "5.2.1",
39
40
  "chai-as-promised": "8.0.1",
40
41
  "esmock": "2.7.1",
@@ -52,7 +53,8 @@
52
53
  "@aws-sdk/client-sqs": "3.864.0",
53
54
  "@json2csv/plainjs": "7.0.6",
54
55
  "aws-xray-sdk": "3.10.3",
56
+ "date-fns": "2.30.0",
55
57
  "uuid": "11.1.0",
56
- "date-fns": "2.30.0"
58
+ "validator": "^13.15.15"
57
59
  }
58
60
  }
package/src/aem.js ADDED
@@ -0,0 +1,222 @@
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 { fetch } from './adobe-fetch.js';
14
+
15
+ /**
16
+ * Delivery types for AEM deployment
17
+ */
18
+ export const DELIVERY_TYPES = {
19
+ AEM_CS: 'aem_cs',
20
+ AEM_EDGE: 'aem_edge',
21
+ AEM_AMS: 'aem_ams',
22
+ AEM_HEADLESS: 'aem_headless',
23
+ OTHER: 'other',
24
+ };
25
+
26
+ /**
27
+ * Detects the AEM delivery type from HTML source code
28
+ * @param {string} htmlSource - The HTML source code of the page
29
+ * @return {string|null} - 'aem_edge', 'aem_cs', 'aem_ams', 'aem_headless'
30
+ * or 'other' if undetermined, null if no HTML source is provided
31
+ */
32
+ export function detectAEMVersion(htmlSource, headers = {}) {
33
+ if (!htmlSource || typeof htmlSource !== 'string') {
34
+ return null;
35
+ }
36
+
37
+ // Create a normalized version of the HTML for simpler pattern matching
38
+ const normalizedHtml = htmlSource.toLowerCase();
39
+
40
+ // EDS Indicators
41
+ const edsPatterns = [
42
+ // Core library references
43
+ /lib-franklin\.js/i,
44
+ /aem\.js/i,
45
+ // Block structure
46
+ /data-block-status/i,
47
+ // Franklin-specific markup patterns
48
+ /scripts\.js/i,
49
+ // Block HTML patterns
50
+ /<div class="[^"]*block[^"]*"[^>]*>/i,
51
+ // RUM data-routing for EDS
52
+ /data-routing="[^"]*eds=([^,"]*)/i,
53
+ ];
54
+
55
+ // CS Indicators (Cloud Service)
56
+ const csPatterns = [
57
+ // Core Components patterns
58
+ /<div class="[^"]*cmp-[^"]*"[^>]*>/i,
59
+ // CS-specific clientlib pattern with lc- prefix/suffix
60
+ // (more specific than general etc.clientlibs)
61
+ /\/etc\.clientlibs\/[^"']+\.lc-[a-f0-9]+-lc\.min\.(js|css)/i,
62
+ // Modern libs clientlib paths
63
+ /\/libs\.clientlibs\//i,
64
+ // Core components comments or data attributes
65
+ /data-cmp-/i,
66
+ /data-sly-/i,
67
+ // Cloud Manager references
68
+ /content\/experience-fragments\//i,
69
+ // SPA editor references
70
+ /data-cq-/i,
71
+ // RUM data-routing for CS
72
+ /data-routing="[^"]*cs=([^,"]*)/i,
73
+ ];
74
+
75
+ // AMS Indicators (Managed Services) - typically older AEM patterns
76
+ const amsPatterns = [
77
+ // Legacy clientlib paths
78
+ /\/etc\/clientlibs\//i,
79
+ /\/etc\/designs\//i,
80
+ // AMS-specific clientlib pattern with fingerprinted hashes (both JS and CSS)
81
+ /\/etc\.clientlibs\/[^"']+\.min\.[a-f0-9]{32}\.(js|css)/i,
82
+ // Classic UI patterns
83
+ /foundation-/i,
84
+ /cq:template/i,
85
+ /cq-commons/i,
86
+ // Legacy component patterns
87
+ /parsys/i,
88
+ // Legacy CQ references
89
+ /\/CQ\//i,
90
+ /\/apps\//i,
91
+ // RUM data-routing for AMS
92
+ /data-routing="[^"]*ams=([^,"]*)/i,
93
+ ];
94
+
95
+ const amsHeaderPatterns = [
96
+ /^dispatcher[0-9].*$/,
97
+ ];
98
+
99
+ const aemHeadlessPatterns = [
100
+ /aem-headless/i,
101
+ /\/content\/dam\//i,
102
+ ];
103
+
104
+ // Count matches for each type
105
+ let edsMatches = 0;
106
+ let csMatches = 0;
107
+ let amsMatches = 0;
108
+ let aemHeadlessMatches = 0;
109
+
110
+ // Check EDS patterns
111
+ for (const pattern of edsPatterns) {
112
+ if (pattern.test(normalizedHtml)) {
113
+ edsMatches += 1;
114
+ }
115
+ }
116
+
117
+ // Check CS patterns
118
+ for (const pattern of csPatterns) {
119
+ if (pattern.test(normalizedHtml)) {
120
+ csMatches += 1;
121
+ }
122
+ }
123
+
124
+ // Check AMS patterns
125
+ for (const pattern of amsPatterns) {
126
+ if (pattern.test(normalizedHtml)) {
127
+ amsMatches += 1;
128
+ }
129
+ }
130
+
131
+ // Check AMS header patterns
132
+ for (const pattern of amsHeaderPatterns) {
133
+ if (pattern.test(headers['x-dispatcher'])) {
134
+ amsMatches += 1;
135
+ }
136
+ }
137
+
138
+ for (const pattern of aemHeadlessPatterns) {
139
+ if (pattern.test(normalizedHtml)) {
140
+ aemHeadlessMatches += 1;
141
+ }
142
+ }
143
+
144
+ // Check for decisive indicators with higher weight
145
+ if (normalizedHtml.includes('lib-franklin.js') || normalizedHtml.includes('aem.js')) {
146
+ edsMatches += 3;
147
+ }
148
+
149
+ // Only give CS weight for core components, but reduced since they can exist in AMS too
150
+ if (normalizedHtml.match(/class="[^"]*cmp-[^"]*"/)) {
151
+ csMatches += 1; // Reduced weight since core components can exist in both AMS and CS
152
+ }
153
+
154
+ // Check for decisive indicators with higher weight
155
+ if (normalizedHtml.includes('/etc/designs/') || normalizedHtml.includes('foundation-')) {
156
+ amsMatches += 2;
157
+ }
158
+
159
+ // Check for decisive indicators with higher weight
160
+ // Give extra weight to AMS clientlib format pattern as it's very distinctive
161
+ if (/\/etc\.clientlibs\/[^"']+\.min\.[a-f0-9]{32}\.(js|css)/i.test(normalizedHtml)) {
162
+ amsMatches += 5; // Increased weight since this is a very reliable AMS indicator
163
+ }
164
+
165
+ // Give extra weight to CS clientlib format pattern as it's very distinctive
166
+ if (/\/etc\.clientlibs\/[^"']+\.lc-[a-f0-9]+-lc\.min\.(js|css)/i.test(normalizedHtml)) {
167
+ csMatches += 3;
168
+ }
169
+
170
+ // Give significant weight to explicit RUM data-routing indicators
171
+ if (/data-routing="[^"]*ams=([^,"]*)/i.test(normalizedHtml)) {
172
+ amsMatches += 5;
173
+ }
174
+
175
+ if (/data-routing="[^"]*eds=([^,"]*)/i.test(normalizedHtml)) {
176
+ edsMatches += 5;
177
+ }
178
+
179
+ if (/data-routing="[^"]*cs=([^,"]*)/i.test(normalizedHtml)) {
180
+ csMatches += 5;
181
+ }
182
+
183
+ // Determine the most likely version based on match counts
184
+ const maxMatches = Math.max(edsMatches, csMatches, amsMatches, aemHeadlessMatches);
185
+
186
+ // Require a minimum threshold of matches to make a determination
187
+ const MIN_THRESHOLD = 2;
188
+
189
+ if (maxMatches < MIN_THRESHOLD) {
190
+ return DELIVERY_TYPES.OTHER;
191
+ }
192
+ // Create an array of [type, matches] and find the first with maxMatches, or 'other'
193
+ const types = [
194
+ [DELIVERY_TYPES.AEM_EDGE, edsMatches],
195
+ [DELIVERY_TYPES.AEM_CS, csMatches],
196
+ [DELIVERY_TYPES.AEM_AMS, amsMatches],
197
+ [DELIVERY_TYPES.AEM_HEADLESS, aemHeadlessMatches],
198
+ ];
199
+ const found = types.find(([, count]) => count === maxMatches);
200
+ return found[0];
201
+ }
202
+
203
+ /**
204
+ * Determines the AEM CS page ID for Content API, from the page URL
205
+ * @param {string} pageURL - The URL of the page
206
+ * @return {string|null} - The AEM CS page ID
207
+ */
208
+ export async function determineAEMCSPageId(pageURL) {
209
+ const htmlResponse = await fetch(pageURL);
210
+ if (!htmlResponse.ok) {
211
+ return null;
212
+ }
213
+ const html = await htmlResponse.text();
214
+ const metaTagRegex = /<meta\s+name=['"]content-page-id['"]\s+content=['"]([^'"]*)['"]\s*\/?>/i;
215
+ const match = html.match(metaTagRegex);
216
+
217
+ let pageId = null;
218
+ if (match && match[1] && match[1].trim() !== '') {
219
+ pageId = match[1].trim();
220
+ }
221
+ return pageId;
222
+ }
package/src/functions.js CHANGED
@@ -11,6 +11,7 @@
11
11
  */
12
12
 
13
13
  import { validate as uuidValidate } from 'uuid';
14
+ import isEmail from 'validator/lib/isEmail.js';
14
15
 
15
16
  // Precompile regular expressions
16
17
  const REGEX_ISO_DATE = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/;
@@ -261,6 +262,15 @@ function isValidIMSOrgId(imsOrgId) {
261
262
  return IMS_ORG_ID_REGEX.test(imsOrgId);
262
263
  }
263
264
 
265
+ /**
266
+ * Validates whether the given string is a valid email address.
267
+ * @param {string} email - The string to validate.
268
+ * @returns {boolean} True if the given string is a valid email address, false otherwise.
269
+ */
270
+ function isValidEmail(email) {
271
+ return typeof email === 'string' && isEmail(email);
272
+ }
273
+
264
274
  /**
265
275
  * Validates whether the given string is a valid Helix preview URL.
266
276
  * Preview URLs have the format: https://ref--site--owner.domain
@@ -323,6 +333,7 @@ export {
323
333
  isObject,
324
334
  isString,
325
335
  isValidDate,
336
+ isValidEmail,
326
337
  isValidUrl,
327
338
  isValidUUID,
328
339
  isValidIMSOrgId,
package/src/index.d.ts CHANGED
@@ -23,6 +23,8 @@ export function isInteger(value: unknown): boolean;
23
23
 
24
24
  export function isValidDate(value: unknown): boolean;
25
25
 
26
+ export function isValidEmail(email: string): boolean;
27
+
26
28
  export function isIsoDate(str: string): boolean;
27
29
 
28
30
  export function isIsoTimeOffsetsDate(str: string): boolean;
package/src/index.js CHANGED
@@ -26,6 +26,7 @@ export {
26
26
  isObject,
27
27
  isString,
28
28
  isValidDate,
29
+ isValidEmail,
29
30
  isValidUrl,
30
31
  isValidUUID,
31
32
  isValidIMSOrgId,
@@ -89,3 +90,5 @@ export {
89
90
  getMonthInfo,
90
91
  getTemporalCondition,
91
92
  } from './calendar-week-helper.js';
93
+
94
+ export { detectAEMVersion, determineAEMCSPageId, DELIVERY_TYPES } from './aem.js';