@adobe/spacecat-shared-utils 1.115.0 → 1.115.2

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,15 @@
1
+ ## [@adobe/spacecat-shared-utils-v1.115.2](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.115.1...@adobe/spacecat-shared-utils-v1.115.2) (2026-05-07)
2
+
3
+ ### Bug Fixes
4
+
5
+ * **utils:** update calculateCPCValue S3 path from ahrefs to seo ([#1591](https://github.com/adobe/spacecat-shared/issues/1591)) ([3bf1fe5](https://github.com/adobe/spacecat-shared/commit/3bf1fe5ce9d257de5b30de4d7082f8af30035147)), closes [spacecat-shared#1499](https://github.com/adobe/spacecat-shared/issues/1499)
6
+
7
+ ## [@adobe/spacecat-shared-utils-v1.115.1](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.115.0...@adobe/spacecat-shared-utils-v1.115.1) (2026-05-06)
8
+
9
+ ### Bug Fixes
10
+
11
+ * **utils:** include subpath in resolveCustomerSecretsName to prevent credential collisions ([#1577](https://github.com/adobe/spacecat-shared/issues/1577)) ([db95707](https://github.com/adobe/spacecat-shared/commit/db95707796c53d0fbe22c4fc40fed9ab271dd59c))
12
+
1
13
  ## [@adobe/spacecat-shared-utils-v1.115.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.114.0...@adobe/spacecat-shared-utils-v1.115.0) (2026-05-04)
2
14
 
3
15
  ### Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-utils",
3
- "version": "1.115.0",
3
+ "version": "1.115.2",
4
4
  "description": "Shared modules of the Spacecat Services - utils",
5
5
  "type": "module",
6
6
  "exports": {
package/src/helpers.js CHANGED
@@ -39,18 +39,60 @@ export function resolveSecretsName(opts, ctx, defaultPath) {
39
39
 
40
40
  /**
41
41
  * Resolves the name of the customer secrets based on the baseURL.
42
- * @param {string} baseURL - The base URL to resolve the customer secrets name from.
42
+ *
43
+ * The hostname (per RFC 1035, case-insensitive) and each URL path segment are
44
+ * percent-decoded and individually sanitized: runs of non-alphanumeric characters
45
+ * are replaced with a single `_`, leading/trailing `_` are trimmed, and the
46
+ * result is lowercased. Segments that reduce to empty after sanitization are
47
+ * dropped. Sanitized parts are joined with `__` as the path-segment delimiter.
48
+ *
49
+ * The `__` delimiter cannot appear inside a sanitized segment (any run of
50
+ * non-alphanumeric characters, including `__`, collapses to a single `_`),
51
+ * so URLs that differ in path structure (different number of segments, or
52
+ * segments with different alphanumeric content) produce distinct keys. Note
53
+ * that path segments differing only in punctuation (e.g. `us-kings` vs
54
+ * `us_kings`) produce the same sanitized segment and therefore the same key;
55
+ * this is an inherent limitation of lossy sanitization.
56
+ *
57
+ * Key format: /helix-deploy/spacecat-services/customer-secrets/<host>[__<seg>...]/<version>
58
+ *
59
+ * Examples:
60
+ * https://nba.com -> .../nba_com/<version>
61
+ * https://nba.com/kings -> .../nba_com__kings/<version>
62
+ * https://nba.com/us/kings -> .../nba_com__us__kings/<version>
63
+ *
64
+ * @param {string} baseURL - The base URL (must be http(s) with a hostname).
43
65
  * @param {Object} ctx - The context object containing the function version.
44
66
  * @returns {string} - The resolved secret name.
67
+ * @since next - key now includes URL path segments; prior versions used hostname
68
+ * only, causing subpath sites on the same domain to share a secret. LLMO-4186.
45
69
  */
46
70
  export function resolveCustomerSecretsName(baseURL, ctx) {
47
71
  const basePath = '/helix-deploy/spacecat-services/customer-secrets';
48
- let customer;
72
+ let url;
49
73
  try {
50
- customer = new URL(baseURL).host.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase();
74
+ url = new URL(baseURL);
51
75
  } catch {
52
76
  throw new Error('Invalid baseURL: must be a valid URL');
53
77
  }
78
+ if (!url.hostname || !['http:', 'https:'].includes(url.protocol)) {
79
+ throw new Error('Invalid baseURL: must be an http(s) URL with a hostname');
80
+ }
81
+ const sanitize = (s) => s.replace(/[^a-zA-Z0-9]+/g, '_').replace(/^_+|_+$/g, '').toLowerCase();
82
+ const host = sanitize(url.hostname);
83
+ if (!host) {
84
+ throw new Error('Invalid baseURL: hostname reduces to empty after sanitization');
85
+ }
86
+ const segments = url.pathname.split('/').filter(Boolean)
87
+ .map((seg) => {
88
+ let decoded = seg;
89
+ try {
90
+ decoded = decodeURIComponent(seg);
91
+ } catch { /* keep raw on percent-encoded sequences that are not valid UTF-8 */ }
92
+ return sanitize(decoded);
93
+ })
94
+ .filter(Boolean);
95
+ const customer = segments.length > 0 ? `${host}__${segments.join('__')}` : host;
54
96
  return resolveSecretsName({}, ctx, `${basePath}/${customer}`);
55
97
  }
56
98
 
@@ -106,7 +106,7 @@ export async function calculateCPCValue(context, siteId) {
106
106
  }
107
107
  const { s3Client, log } = context;
108
108
  const bucketName = context.env.S3_IMPORTER_BUCKET_NAME;
109
- const key = `metrics/${siteId}/ahrefs/organic-traffic.json`;
109
+ const key = createFilePath({ siteId, source: 'seo', metric: 'organic-traffic' });
110
110
  try {
111
111
  const organicTrafficData = await getObjectFromKey(s3Client, bucketName, key, log);
112
112
  if (!Array.isArray(organicTrafficData) || organicTrafficData.length === 0) {