@adobe/spacecat-shared-utils 1.94.0 → 1.96.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.96.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.95.0...@adobe/spacecat-shared-utils-v1.96.0) (2026-02-12)
2
+
3
+
4
+ ### Features
5
+
6
+ * get llmo config v2 ([#1340](https://github.com/adobe/spacecat-shared/issues/1340)) ([6fcbd51](https://github.com/adobe/spacecat-shared/commit/6fcbd51864daca13736d3813097c485e2189cd68))
7
+
8
+ # [@adobe/spacecat-shared-utils-v1.95.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.94.0...@adobe/spacecat-shared-utils-v1.95.0) (2026-02-12)
9
+
10
+
11
+ ### Features
12
+
13
+ * add canonicalizeUrl utility for consistent URL comparison ([#1334](https://github.com/adobe/spacecat-shared/issues/1334)) ([eb15132](https://github.com/adobe/spacecat-shared/commit/eb1513228e5d2e1701211b6aaf36108bd1642d68))
14
+
1
15
  # [@adobe/spacecat-shared-utils-v1.94.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.93.0...@adobe/spacecat-shared-utils-v1.94.0) (2026-02-11)
2
16
 
3
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-utils",
3
- "version": "1.94.0",
3
+ "version": "1.96.0",
4
4
  "description": "Shared modules of the Spacecat Services - utils",
5
5
  "type": "module",
6
6
  "exports": {
package/src/index.d.ts CHANGED
@@ -133,6 +133,20 @@ export declare function stripTrailingSlash(url: string): string;
133
133
  */
134
134
  export declare function stripWWW(url: string): string;
135
135
 
136
+ /**
137
+ * Canonicalizes a URL by removing protocol, www prefix, and trailing slash
138
+ * for comparison and matching purposes.
139
+ * Optionally strips query parameters and fragments.
140
+ * @param url - URL to canonicalize
141
+ * @param options - Canonicalization options
142
+ * @param options.stripQuery - Whether to strip query parameters and fragments
143
+ * @returns Canonicalized URL
144
+ */
145
+ export declare function canonicalizeUrl(
146
+ url: string,
147
+ options?: { stripQuery?: boolean }
148
+ ): string;
149
+
136
150
  /**
137
151
  * Composes a base URL by applying a series of transformations to the given domain.
138
152
  * @param domain - The domain to compose the base URL from.
package/src/index.js CHANGED
@@ -55,6 +55,7 @@ export { logWrapper } from './log-wrapper.js';
55
55
  export { instrumentAWSClient, getTraceId, addTraceIdHeader } from './xray.js';
56
56
 
57
57
  export {
58
+ canonicalizeUrl,
58
59
  composeBaseURL,
59
60
  composeAuditURL,
60
61
  prependSchema,
@@ -125,3 +125,67 @@ export async function writeConfig(siteId, config, s3Client, options) {
125
125
  }
126
126
  return { version: res.VersionId };
127
127
  }
128
+
129
+ /**
130
+ * Gets the S3 path for a V2 customer configuration.
131
+ * @param {string} organizationId The SpaceCat organization ID.
132
+ * @returns {string} The S3 key path for the V2 customer config.
133
+ */
134
+ export function customerConfigV2Path(organizationId) {
135
+ return `customer-config-v2/${organizationId}/config.json`;
136
+ }
137
+
138
+ /**
139
+ * Reads the V2 customer configuration for an organization from S3.
140
+ * @param {string} organizationId The SpaceCat organization ID.
141
+ * @param {S3Client} s3Client The S3 client to use for reading the configuration.
142
+ * @param {object} [options]
143
+ * @param {string} [options.s3Bucket] Optional S3 bucket name.
144
+ * @returns {Promise<object|null>} The configuration object or null if not found.
145
+ * @throws {Error} If reading the configuration fails for reasons other than it not existing.
146
+ */
147
+ export async function readCustomerConfigV2(organizationId, s3Client, options) {
148
+ const s3Bucket = options?.s3Bucket || process.env.S3_BUCKET_NAME;
149
+
150
+ const getObjectCommand = new GetObjectCommand({
151
+ Bucket: s3Bucket,
152
+ Key: customerConfigV2Path(organizationId),
153
+ });
154
+
155
+ try {
156
+ const res = await s3Client.send(getObjectCommand);
157
+ const body = res.Body;
158
+ if (!body) {
159
+ throw new Error('Customer config V2 body is empty');
160
+ }
161
+ const text = await body.transformToString();
162
+ return JSON.parse(text);
163
+ } catch (e) {
164
+ if (e.name === 'NoSuchKey' || e.name === 'NotFound') {
165
+ return null;
166
+ }
167
+ throw e;
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Writes the V2 customer configuration for an organization to S3.
173
+ * @param {string} organizationId The SpaceCat organization ID.
174
+ * @param {object} config The customer configuration object to write.
175
+ * @param {S3Client} s3Client The S3 client to use for writing the configuration.
176
+ * @param {object} [options]
177
+ * @param {string} [options.s3Bucket] Optional S3 bucket name.
178
+ * @returns {Promise<void>}
179
+ */
180
+ export async function writeCustomerConfigV2(organizationId, config, s3Client, options) {
181
+ const s3Bucket = options?.s3Bucket || process.env.S3_BUCKET_NAME;
182
+
183
+ const putObjectCommand = new PutObjectCommand({
184
+ Bucket: s3Bucket,
185
+ Key: customerConfigV2Path(organizationId),
186
+ Body: JSON.stringify(config, null, 2),
187
+ ContentType: 'application/json',
188
+ });
189
+
190
+ await s3Client.send(putObjectCommand);
191
+ }
@@ -318,6 +318,40 @@ async function wwwUrlResolver(site, rumApiClient, log) {
318
318
  return fallback;
319
319
  }
320
320
 
321
+ /**
322
+ * Canonicalizes a URL by removing protocol, www prefix, and trailing slash
323
+ * for comparison and matching purposes.
324
+ * Optionally strips query parameters and fragments.
325
+ * @param {string} url - URL to canonicalize
326
+ * @param {object} options - Canonicalization options
327
+ * @param {boolean} options.stripQuery - Whether to strip query parameters and fragments
328
+ * @returns {string} Canonicalized URL
329
+ */
330
+ export function canonicalizeUrl(url, { stripQuery = false } = {}) {
331
+ if (!url || typeof url !== 'string') {
332
+ return '';
333
+ }
334
+
335
+ let canonicalized = url
336
+ .toLowerCase() // Case insensitive
337
+ .trim()
338
+ .replace(/^https?:\/\//, '') // Remove protocol
339
+ .replace(/^www\d*\./, '') // Remove www, www2, www3, etc.
340
+ .replace(/\/$/, ''); // Remove trailing slash
341
+
342
+ // Optionally strip query parameters and fragments
343
+ if (stripQuery) {
344
+ const queryIndex = canonicalized.search(/[?#]/);
345
+ if (queryIndex !== -1) {
346
+ canonicalized = canonicalized.substring(0, queryIndex);
347
+ }
348
+ // Remove any trailing slash that may have been revealed
349
+ canonicalized = canonicalized.replace(/\/$/, '');
350
+ }
351
+
352
+ return canonicalized;
353
+ }
354
+
321
355
  export {
322
356
  ensureHttps,
323
357
  getSpacecatRequestHeaders,