@adobe/spacecat-shared-utils 1.50.2 → 1.50.4

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.4](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.50.3...@adobe/spacecat-shared-utils-v1.50.4) (2025-09-10)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * getPageEditUrl util ([#952](https://github.com/adobe/spacecat-shared/issues/952)) ([aef026d](https://github.com/adobe/spacecat-shared/commit/aef026db3452606936c271e87f3d376fc8a38642))
7
+
8
+ # [@adobe/spacecat-shared-utils-v1.50.3](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.50.2...@adobe/spacecat-shared-utils-v1.50.3) (2025-09-09)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **deps:** update external major (major) ([#795](https://github.com/adobe/spacecat-shared/issues/795)) ([b020e88](https://github.com/adobe/spacecat-shared/commit/b020e884bfcad48667da87ad9caee7a3669e43d0))
14
+
1
15
  # [@adobe/spacecat-shared-utils-v1.50.2](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.50.1...@adobe/spacecat-shared-utils-v1.50.2) (2025-09-06)
2
16
 
3
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-utils",
3
- "version": "1.50.2",
3
+ "version": "1.50.4",
4
4
  "description": "Shared modules of the Spacecat Services - utils",
5
5
  "type": "module",
6
6
  "engines": {
@@ -36,12 +36,12 @@
36
36
  "devDependencies": {
37
37
  "@adobe/helix-shared-wrap": "2.0.2",
38
38
  "@types/validator": "^13.15.2",
39
- "chai": "5.3.3",
39
+ "chai": "6.0.1",
40
40
  "chai-as-promised": "8.0.2",
41
41
  "esmock": "2.7.2",
42
42
  "husky": "9.1.7",
43
43
  "nock": "14.0.10",
44
- "sinon": "20.0.0",
44
+ "sinon": "21.0.0",
45
45
  "sinon-chai": "4.0.1"
46
46
  },
47
47
  "dependencies": {
@@ -53,8 +53,7 @@
53
53
  "@aws-sdk/client-sqs": "3.883.0",
54
54
  "@json2csv/plainjs": "7.0.6",
55
55
  "aws-xray-sdk": "3.10.3",
56
- "date-fns": "2.30.0",
57
- "uuid": "11.1.0",
56
+ "date-fns": "4.1.0",
58
57
  "validator": "^13.15.15"
59
58
  }
60
59
  }
@@ -0,0 +1,124 @@
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
+ const CONTENT_API_PREFIX = '/adobe/experimental/aspm-expires-20251231';
16
+
17
+ /**
18
+ * Determines the AEM CS/AMS page ID for Content API, from the page URL
19
+ * @param {string} pageURL - The URL of the page
20
+ * @param {string} authorURL - The URL of the author instance
21
+ * @param {string} bearerToken - The access token for the author instance
22
+ * @param {boolean} preferContentApi - Whether to prefer the Content API over the PSS, default is
23
+ * false
24
+ * @param {Object} log - The logger object, default is console
25
+ * @return {string|null} - The AEM page ID
26
+ */
27
+ export async function determineAEMCSPageId(
28
+ pageURL,
29
+ authorURL,
30
+ bearerToken,
31
+ preferContentApi = false,
32
+ log = console,
33
+ ) {
34
+ log.info(`Fetching HTML from ${pageURL} to retrieve content-page-id for AEM CS Content API mode.`);
35
+ const htmlResponse = await fetch(pageURL);
36
+
37
+ if (!htmlResponse.ok) {
38
+ return null;
39
+ }
40
+
41
+ const html = await htmlResponse.text();
42
+
43
+ // First try to find a content-page-ref meta tag
44
+ const contentPageRefRegex = /<meta\s+name=['"]content-page-ref['"]\s+content=['"]([^'"]*)['"]\s*\/?>/i;
45
+ const refMatch = html.match(contentPageRefRegex);
46
+
47
+ if (refMatch?.[1]?.trim()) {
48
+ if (!authorURL || !bearerToken) {
49
+ // If ref was present but resolution failed, return null per spec
50
+ log.warn('Content-page-ref found but authorURL or bearerToken is missing, skipping resolution.');
51
+ return null;
52
+ }
53
+ const contentPageRef = refMatch[1].trim();
54
+ try {
55
+ const base = preferContentApi
56
+ ? `${authorURL}${CONTENT_API_PREFIX}`
57
+ : `${authorURL}/adobe/experimental/pss`;
58
+ const resolveUrl = `${base}/pages/resolve?pageRef=${contentPageRef}`;
59
+ log.info(`Resolving content-page-ref via ${resolveUrl} (preferContentApi=${preferContentApi})`);
60
+ const resp = await fetch(resolveUrl, {
61
+ method: 'GET',
62
+ headers: { Authorization: bearerToken },
63
+ redirect: 'follow',
64
+ });
65
+ if (resp.status === 200) {
66
+ let pageId = null;
67
+ if (preferContentApi) {
68
+ const data = await resp.json();
69
+ pageId = data?.id || null;
70
+ } else {
71
+ const data = await resp.text();
72
+ pageId = data || null;
73
+ }
74
+
75
+ if (pageId) {
76
+ log.info(`Resolved pageId: "${pageId}" from JSON directly for ref "${contentPageRef}"`);
77
+ return pageId;
78
+ }
79
+ log.error('resolve response did not contain an "id" property.');
80
+ return null;
81
+ } else {
82
+ log.warn(`Unexpected status ${resp.status} when resolving content-page-ref.`);
83
+ }
84
+ } catch (e) {
85
+ log.error(`Error while resolving content-page-ref: ${e.message}`);
86
+ }
87
+ // If ref was present but resolution failed, return null per spec
88
+ return null;
89
+ }
90
+
91
+ // Fallback to content-page-id meta tag
92
+ const contentPageIdRegex = /<meta\s+name=['"]content-page-id['"]\s+content=['"]([^'"]*)['"]\s*\/?>/i;
93
+ const idMatch = html.match(contentPageIdRegex);
94
+
95
+ let pageId = null;
96
+ if (idMatch?.[1]?.trim()) {
97
+ pageId = idMatch[1].trim();
98
+ if (pageId) {
99
+ log.info(`Extracted pageId: "${pageId}" from "content-page-id" meta tag at ${pageURL}`);
100
+ }
101
+ }
102
+ return pageId;
103
+ }
104
+
105
+ /**
106
+ * Fetch the edit URL for a given page ID using the Content API
107
+ * @param {string} authorURL - The author URL
108
+ * @param {string} bearerToken - The bearer token
109
+ * @param {string} pageId - The page ID
110
+ * @returns {string} The edit URL or null if the page ID is not found
111
+ */
112
+ export const getPageEditUrl = async (authorURL, bearerToken, pageId) => {
113
+ const PAGE_ID_API = `${authorURL}${CONTENT_API_PREFIX}/pages/${pageId}`;
114
+ const response = await fetch(PAGE_ID_API, {
115
+ method: 'GET',
116
+ headers: { Authorization: bearerToken },
117
+ });
118
+ if (response.ok) {
119
+ const responseData = await response.json();
120
+ // eslint-disable-next-line no-underscore-dangle
121
+ return responseData?._links?.edit;
122
+ }
123
+ return null;
124
+ };
package/src/aem.js CHANGED
@@ -10,8 +10,6 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import { fetch } from './adobe-fetch.js';
14
-
15
13
  /**
16
14
  * Delivery types for AEM deployment
17
15
  */
@@ -199,91 +197,3 @@ export function detectAEMVersion(htmlSource, headers = {}) {
199
197
  const found = types.find(([, count]) => count === maxMatches);
200
198
  return found[0];
201
199
  }
202
-
203
- /**
204
- * Determines the AEM CS/AMS page ID for Content API, from the page URL
205
- * @param {string} pageURL - The URL of the page
206
- * @param {string} authorURL - The URL of the author instance
207
- * @param {string} bearerToken - The access token for the author instance
208
- * @param {boolean} preferContentApi - Whether to prefer the Content API over the PSS, default is
209
- * false
210
- * @param {Object} log - The logger object, default is console
211
- * @return {string|null} - The AEM page ID
212
- */
213
- export async function determineAEMCSPageId(
214
- pageURL,
215
- authorURL,
216
- bearerToken,
217
- preferContentApi = false,
218
- log = console,
219
- ) {
220
- log.info(`Fetching HTML from ${pageURL} to retrieve content-page-id for AEM CS Content API mode.`);
221
- const htmlResponse = await fetch(pageURL);
222
-
223
- if (!htmlResponse.ok) {
224
- return null;
225
- }
226
-
227
- const html = await htmlResponse.text();
228
-
229
- // First try to find a content-page-ref meta tag
230
- const contentPageRefRegex = /<meta\s+name=['"]content-page-ref['"]\s+content=['"]([^'"]*)['"]\s*\/?>/i;
231
- const refMatch = html.match(contentPageRefRegex);
232
-
233
- if (refMatch?.[1]?.trim()) {
234
- if (!authorURL || !bearerToken) {
235
- // If ref was present but resolution failed, return null per spec
236
- log.warn('Content-page-ref found but authorURL or bearerToken is missing, skipping resolution.');
237
- return null;
238
- }
239
- const contentPageRef = refMatch[1].trim();
240
- try {
241
- const base = preferContentApi
242
- ? `${authorURL}/adobe/experimental/aspm-expires-20251231`
243
- : `${authorURL}/adobe/experimental/pss`;
244
- const resolveUrl = `${base}/pages/resolve?pageRef=${contentPageRef}`;
245
- log.info(`Resolving content-page-ref via ${resolveUrl} (preferContentApi=${preferContentApi})`);
246
- const resp = await fetch(resolveUrl, {
247
- method: 'GET',
248
- headers: { Authorization: bearerToken },
249
- redirect: 'follow',
250
- });
251
- if (resp.status === 200) {
252
- let pageId = null;
253
- if (preferContentApi) {
254
- const data = await resp.json();
255
- pageId = data?.id || null;
256
- } else {
257
- const data = await resp.text();
258
- pageId = data || null;
259
- }
260
-
261
- if (pageId) {
262
- log.info(`Resolved pageId: "${pageId}" from JSON directly for ref "${contentPageRef}"`);
263
- return pageId;
264
- }
265
- log.error('resolve response did not contain an "id" property.');
266
- return null;
267
- } else {
268
- log.warn(`Unexpected status ${resp.status} when resolving content-page-ref.`);
269
- }
270
- } catch (e) {
271
- log.error(`Error while resolving content-page-ref: ${e.message}`);
272
- }
273
- // If ref was present but resolution failed, return null per spec
274
- return null;
275
- }
276
-
277
- // Fallback to content-page-id meta tag
278
- const contentPageIdRegex = /<meta\s+name=['"]content-page-id['"]\s+content=['"]([^'"]*)['"]\s*\/?>/i;
279
- const idMatch = html.match(contentPageIdRegex);
280
-
281
- let pageId = null;
282
- if (idMatch?.[1]?.trim()) {
283
- pageId = idMatch[1].trim();
284
- if (pageId) {
285
- log.info(`Extracted pageId: "${pageId}" from "content-page-id" meta tag at ${pageURL}`);
286
- }
287
- }
288
- return pageId;
289
- }
package/src/functions.js CHANGED
@@ -10,13 +10,13 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import { validate as uuidValidate } from 'uuid';
14
13
  import isEmail from 'validator/lib/isEmail.js';
15
14
 
16
15
  // Precompile regular expressions
17
16
  const REGEX_ISO_DATE = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/;
18
17
  const REGEX_TIME_OFFSET_DATE = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}(Z|[+-]\d{2}:\d{2})/;
19
18
  const IMS_ORG_ID_REGEX = /[a-z0-9]{24}@AdobeOrg/i;
19
+ const UUID_V4_REGEX = /^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$/;
20
20
 
21
21
  /**
22
22
  * Determines if the given parameter is an array.
@@ -204,7 +204,7 @@ function isValidUrl(urlString) {
204
204
  * @return {boolean} True if the given string is a valid UUID.
205
205
  */
206
206
  function isValidUUID(uuid) {
207
- return uuidValidate(uuid);
207
+ return UUID_V4_REGEX.test(uuid);
208
208
  }
209
209
 
210
210
  /**
package/src/index.js CHANGED
@@ -91,4 +91,6 @@ export {
91
91
  getTemporalCondition,
92
92
  } from './calendar-week-helper.js';
93
93
 
94
- export { detectAEMVersion, determineAEMCSPageId, DELIVERY_TYPES } from './aem.js';
94
+ export { detectAEMVersion, DELIVERY_TYPES } from './aem.js';
95
+
96
+ export { determineAEMCSPageId, getPageEditUrl } from './aem-content-api-utils.js';