From fa96952e475f5228ea8df083110dd2096192631d Mon Sep 17 00:00:00 2001 From: Divyansh Pratap Date: Wed, 15 Oct 2025 03:31:19 +0530 Subject: [PATCH 01/20] fix: add tokowaka client --- .../.mocha-multi.json | 7 + .../LICENSE.txt | 203 +++++++++ .../spacecat-shared-tokowaka-client/README.md | 372 ++++++++++++++++ .../package.json | 44 ++ .../src/index.d.ts | 136 ++++++ .../src/index.js | 238 ++++++++++ .../src/mappers/base-mapper.js | 79 ++++ .../src/mappers/headings-mapper.js | 79 ++++ .../src/mappers/mapper-registry.js | 85 ++++ .../test/index.test.js | 420 ++++++++++++++++++ .../test/setup-env.js | 13 + 11 files changed, 1676 insertions(+) create mode 100644 packages/spacecat-shared-tokowaka-client/.mocha-multi.json create mode 100644 packages/spacecat-shared-tokowaka-client/LICENSE.txt create mode 100644 packages/spacecat-shared-tokowaka-client/README.md create mode 100644 packages/spacecat-shared-tokowaka-client/package.json create mode 100644 packages/spacecat-shared-tokowaka-client/src/index.d.ts create mode 100644 packages/spacecat-shared-tokowaka-client/src/index.js create mode 100644 packages/spacecat-shared-tokowaka-client/src/mappers/base-mapper.js create mode 100644 packages/spacecat-shared-tokowaka-client/src/mappers/headings-mapper.js create mode 100644 packages/spacecat-shared-tokowaka-client/src/mappers/mapper-registry.js create mode 100644 packages/spacecat-shared-tokowaka-client/test/index.test.js create mode 100644 packages/spacecat-shared-tokowaka-client/test/setup-env.js diff --git a/packages/spacecat-shared-tokowaka-client/.mocha-multi.json b/packages/spacecat-shared-tokowaka-client/.mocha-multi.json new file mode 100644 index 000000000..bcca6e574 --- /dev/null +++ b/packages/spacecat-shared-tokowaka-client/.mocha-multi.json @@ -0,0 +1,7 @@ +{ + "reporterEnabled": "spec,xunit", + "xunitReporterOptions": { + "output": "junit/test-results.xml" + } +} + diff --git a/packages/spacecat-shared-tokowaka-client/LICENSE.txt b/packages/spacecat-shared-tokowaka-client/LICENSE.txt new file mode 100644 index 000000000..6b0b1270f --- /dev/null +++ b/packages/spacecat-shared-tokowaka-client/LICENSE.txt @@ -0,0 +1,203 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/packages/spacecat-shared-tokowaka-client/README.md b/packages/spacecat-shared-tokowaka-client/README.md new file mode 100644 index 000000000..b9f33e316 --- /dev/null +++ b/packages/spacecat-shared-tokowaka-client/README.md @@ -0,0 +1,372 @@ +# @adobe/spacecat-shared-tokowaka-client + +Tokowaka Client for SpaceCat - Manages edge optimization configurations for LLM/AI agent traffic. + +## Installation + +```bash +npm install @adobe/spacecat-shared-tokowaka-client +``` + +## Usage + +### Basic Usage + +```javascript +import TokowakaClient from '@adobe/spacecat-shared-tokowaka-client'; + +// Create client from context +const tokowakaClient = TokowakaClient.createFrom(context); + +// Deploy suggestions to Tokowaka +const result = await tokowakaClient.deploySuggestions(site, opportunity, suggestions); + +console.log('Deployed to S3:', result.s3Key); +``` + +### Manual Configuration Generation + +```javascript +// Generate configuration without uploading +const config = tokowakaClient.generateConfig(site, opportunity, suggestions); + +console.log('Generated config:', config); +/* +{ + siteId: '9ae8877a-bbf3-407d-9adb-d6a72ce3c5e3', + baseURL: 'https://example.com', + version: '1.0', + tokowakaForceFail: false, + tokowakaOptimizations: { + '/page1.html': { + prerender: true, + patches: [ + { + op: 'replace', + selector: 'h1.title', + value: 'Optimized Heading', + opportunityId: '...', + suggestionId: '...', + prerenderRequired: true, + lastUpdated: 1234567890 + } + ] + } + } +} +*/ + +// Upload config separately +const s3Key = await tokowakaClient.uploadConfig(tokowakaApiKey, config); +``` + +## API Reference + +### TokowakaClient + +#### Constructor + +```javascript +new TokowakaClient(config, log) +``` + +**Parameters:** +- `config.bucketName` (string): S3 bucket name for storing configurations +- `config.s3Client` (S3Client): AWS S3 client instance +- `log` (Object): Logger instance (console-compatible) + +#### Static Methods + +##### `TokowakaClient.createFrom(context)` + +Creates a client instance from a context object. Reuses existing client if available. + +**Parameters:** +- `context.env.TOKOWAKA_CONFIG_BUCKET` (string): S3 bucket name +- `context.s3Client` (S3Client): AWS S3 client +- `context.log` (Object, optional): Logger instance + +**Returns:** `TokowakaClient` + +#### Instance Methods + +##### `generateConfig(site, opportunity, suggestions)` + +Generates Tokowaka configuration from opportunity suggestions. + +**Parameters:** +- `site` (Object): Site entity with `getId()`, `getBaseURL()`, `getConfig()` methods +- `opportunity` (Object): Opportunity entity with `getId()`, `getType()` methods +- `suggestions` (Array): Array of suggestion entities with `getId()`, `getData()` methods + +**Returns:** `TokowakaConfig` object + +##### `uploadConfig(apiKey, config)` + +Uploads configuration to S3. + +**Parameters:** +- `apiKey` (string): Tokowaka API key (used as S3 key prefix) +- `config` (Object): Tokowaka configuration object + +**Returns:** `Promise` - S3 key of uploaded configuration + +##### `deploySuggestions(site, opportunity, suggestions)` + +Complete deployment flow: generates config and uploads to S3. + +**Parameters:** +- `site` (Object): Site entity +- `opportunity` (Object): Opportunity entity +- `suggestions` (Array): Array of suggestion entities + +**Returns:** `Promise` + +```typescript +interface DeploymentResult { + tokowakaApiKey: string; + s3Key: string; + config: TokowakaConfig; +} +``` + +## Supported Opportunity Types + +### 1. Headings + +Optimizes heading elements (h1, h2, h3, etc.). + +**Required suggestion data:** +- `recommendedAction` (or fallbacks: `value`, `suggestedText`, `text`, `heading`): New heading text +- `headingTag` (or `selector`, `cssSelector`, `xpath`): Heading tag or CSS selector (e.g., "h1", "h2") + +**Example suggestion data:** +```json +{ + "url": "https://www.example.com/page.html", + "headingTag": "h1", + "recommendedAction": "Optimized Heading for SEO", + "checkType": "heading-empty" +} +``` + +**Generated patch:** +```json +{ + "op": "replace", + "selector": "h1", + "value": "Optimized Heading for SEO", + "opportunityId": "...", + "suggestionId": "...", + "prerenderRequired": true, + "lastUpdated": 1234567890 +} +``` + +### 2. Meta Tags + +Updates meta tag content attributes. + +**Required suggestion data:** +- `metaName` or `name`: Meta tag name attribute +- `content` or `value`: New content value + +**Generated patch:** +```json +{ + "op": "replace", + "selector": "meta[name=\"description\"]", + "attribute": "content", + "value": "New meta description", + "prerenderRequired": false +} +``` + +### 3. Prerender + +Enables pre-rendering for client-side rendered pages. + +**Generated patch:** +```json +{ + "op": "prerender", + "prerenderRequired": true +} +``` + +### 4. Structured Data + +Adds JSON-LD structured data to pages. + +**Required suggestion data:** +- `script` or `structuredData`: JSON-LD script content + +**Generated patch:** +```json +{ + "op": "add", + "selector": "head", + "element": "script", + "attributes": { + "type": "application/ld+json" + }, + "value": "{ \"@context\": \"https://schema.org\", ... }", + "prerenderRequired": true +} +``` + +## Extending with Custom Mappers + +The Tokowaka client uses a **Strategy Pattern** with opportunity-specific mappers. You can easily add support for new opportunity types by creating custom mappers. + +### Creating a Custom Mapper + +```javascript +import { BaseOpportunityMapper } from '@adobe/spacecat-shared-tokowaka-client'; + +class CustomOpportunityMapper extends BaseOpportunityMapper { + getOpportunityType() { + return 'custom-opportunity'; + } + + requiresPrerender() { + return true; // or false, depending on your needs + } + + suggestionToPatch(suggestion, opportunityId) { + const data = suggestion.getData(); + + // Validate data + if (!this.validateSuggestionData(data)) { + this.log.warn(`Invalid suggestion data for ${suggestion.getId()}`); + return null; + } + + // Convert suggestion data to patch format + return { + ...this.createBasePatch(suggestion.getId(), opportunityId), + op: 'replace', + selector: data.targetElement, + value: data.newValue, + // Add any other custom fields + }; + } + + validateSuggestionData(data) { + return !!(data?.targetElement && data?.newValue); + } +} + +// Register the custom mapper +const client = TokowakaClient.createFrom(context); +client.registerMapper(new CustomOpportunityMapper(context.log)); + +// Now the client can handle 'custom-opportunity' type +const result = await client.deploySuggestions(site, customOpportunity, suggestions); +``` + +### Architecture Benefits + +**Common for all opportunities:** +- ✅ Authentication & Authorization +- ✅ Validation logic +- ✅ S3 upload handling +- ✅ Site config generation structure + +**Opportunity-specific (via mappers):** +- 🔧 Suggestion data → Tokowaka patch conversion +- 🔧 Data field mapping +- 🔧 Prerender requirements +- 🔧 Custom validation rules + +### Getting Supported Types + +```javascript +const supportedTypes = client.getSupportedOpportunityTypes(); +console.log(supportedTypes); +// ['headings', 'meta-tags', 'prerender', 'structured-data', 'custom-opportunity'] +``` + +## Configuration Format + +### Tokowaka Site Config + +```typescript +interface TokowakaConfig { + siteId: string; // Site UUID + baseURL: string; // Site base URL + version: string; // Config version (currently "1.0") + tokowakaForceFail: boolean; // Force fail flag (for testing) + tokowakaOptimizations: { // Optimizations by URL path + [urlPath: string]: { + prerender: boolean; // Whether to pre-render this URL + patches: TokawakaPatch[]; // Array of patches to apply + } + } +} +``` + +### Patch Format + +```typescript +interface TokawakaPatch { + op: 'replace' | 'add' | 'prerender'; // Operation type + selector?: string; // CSS selector (for replace/add) + value?: string; // New value + attribute?: string; // Attribute to modify (optional) + element?: string; // Element type to add (for 'add') + attributes?: Record; // Element attributes (for 'add') + opportunityId: string; // Opportunity UUID + suggestionId: string; // Suggestion UUID + prerenderRequired: boolean; // Whether prerender is needed + lastUpdated: number; // Timestamp (milliseconds) +} +``` + +## S3 Storage Structure + +Configurations are stored at: + +``` +s3://{TOKOWAKA_CONFIG_BUCKET}/{tokowakaApiKey}/v1/tokowaka-site-config.json +``` + +**Example:** +``` +s3://spacecat-tokowaka-configs/OCtrOiKqOxhg4Er3lzYDJS8FAeEUSriK/v1/tokowaka-site-config.json +``` + +**Cache Control:** 24 hours (86400 seconds) + +## Environment Variables + +```bash +TOKOWAKA_CONFIG_BUCKET=spacecat-tokowaka-configs +``` + +## Error Handling + +The client throws errors with `status` property for HTTP-compatible error handling: + +```javascript +try { + await tokowakaClient.deploySuggestions(site, opportunity, suggestions); +} catch (error) { + if (error.status === 400) { + console.error('Bad request:', error.message); + } else if (error.status === 500) { + console.error('Server error:', error.message); + } +} +``` + +## Testing + +```bash +npm test +``` + +## License + +Apache-2.0 + diff --git a/packages/spacecat-shared-tokowaka-client/package.json b/packages/spacecat-shared-tokowaka-client/package.json new file mode 100644 index 000000000..8e54efc35 --- /dev/null +++ b/packages/spacecat-shared-tokowaka-client/package.json @@ -0,0 +1,44 @@ +{ + "name": "@adobe/spacecat-shared-tokowaka-client", + "version": "1.0.0", + "description": "Tokowaka Client for SpaceCat - Edge optimization config management", + "type": "module", + "main": "src/index.js", + "types": "src/index.d.ts", + "scripts": { + "test": "c8 mocha", + "lint": "eslint .", + "clean": "rm -rf package-lock.json node_modules" + }, + "repository": { + "type": "git", + "url": "https://github.com/adobe/spacecat-shared.git", + "directory": "packages/spacecat-shared-tokowaka-client" + }, + "author": "", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/adobe/spacecat-shared/issues" + }, + "homepage": "https://github.com/adobe/spacecat-shared#readme", + "mocha": { + "require": "test/setup-env.js", + "reporter": "mocha-multi-reporters", + "reporter-options": "configFile=.mocha-multi.json", + "spec": "test/**/*.test.js" + }, + "dependencies": { + "@adobe/spacecat-shared-utils": "*", + "@aws-sdk/client-s3": "^3.893.0" + }, + "devDependencies": { + "c8": "^10.1.3", + "chai": "^6.0.1", + "eslint": "^9.36.0", + "mocha": "^11.7.2", + "nock": "^14.0.10", + "sinon": "^21.0.0", + "sinon-chai": "^4.0.1" + } +} + diff --git a/packages/spacecat-shared-tokowaka-client/src/index.d.ts b/packages/spacecat-shared-tokowaka-client/src/index.d.ts new file mode 100644 index 000000000..1f0b10134 --- /dev/null +++ b/packages/spacecat-shared-tokowaka-client/src/index.d.ts @@ -0,0 +1,136 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import { S3Client } from '@aws-sdk/client-s3'; + +export interface TokawakaPatch { + op: 'replace' | 'add' | 'prerender'; + selector?: string; + value?: string; + attribute?: string; + element?: string; + attributes?: Record; + opportunityId: string; + suggestionId: string; + prerenderRequired: boolean; + lastUpdated: number; +} + +export interface TokowakaUrlOptimization { + prerender: boolean; + patches: TokawakaPatch[]; +} + +export interface TokowakaConfig { + siteId: string; + baseURL: string; + version: string; + tokowakaForceFail: boolean; + tokowakaOptimizations: Record; +} + +export interface DeploymentResult { + tokowakaApiKey: string; + s3Key: string; + config: TokowakaConfig; +} + +export interface Site { + getId(): string; + getBaseURL(): string; + getConfig(): Record; +} + +export interface Opportunity { + getId(): string; + getType(): string; +} + +export interface Suggestion { + getId(): string; + getData(): Record; +} + +/** + * Base class for opportunity mappers + * Extend this class to create custom mappers for new opportunity types + */ +export abstract class BaseOpportunityMapper { + constructor(log: any); + + /** + * Returns the opportunity type this mapper handles + */ + abstract getOpportunityType(): string; + + /** + * Determines if prerendering is required for this opportunity type + */ + abstract requiresPrerender(): boolean; + + /** + * Converts a suggestion to a Tokowaka patch + */ + abstract suggestionToPatch( + suggestion: Suggestion, + opportunityId: string + ): TokawakaPatch | null; + + /** + * Validates suggestion data before conversion + */ + validateSuggestionData(data: Record): boolean; + + /** + * Helper method to create base patch structure + */ + protected createBasePatch( + suggestionId: string, + opportunityId: string + ): Partial; +} + +export default class TokowakaClient { + constructor(config: { bucketName: string; s3Client: S3Client }, log: any); + + static createFrom(context: { + env: { TOKOWAKA_CONFIG_BUCKET: string }; + log?: any; + s3Client: S3Client; + tokowakaClient?: TokowakaClient; + }): TokowakaClient; + + generateConfig( + site: Site, + opportunity: Opportunity, + suggestions: Suggestion[] + ): TokowakaConfig; + + uploadConfig(apiKey: string, config: TokowakaConfig): Promise; + + deploySuggestions( + site: Site, + opportunity: Opportunity, + suggestions: Suggestion[] + ): Promise; + + /** + * Registers a custom mapper for an opportunity type + */ + registerMapper(mapper: BaseOpportunityMapper): void; + + /** + * Gets list of supported opportunity types + */ + getSupportedOpportunityTypes(): string[]; +} + diff --git a/packages/spacecat-shared-tokowaka-client/src/index.js b/packages/spacecat-shared-tokowaka-client/src/index.js new file mode 100644 index 000000000..57700ffa5 --- /dev/null +++ b/packages/spacecat-shared-tokowaka-client/src/index.js @@ -0,0 +1,238 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import { PutObjectCommand } from '@aws-sdk/client-s3'; +import { hasText, isNonEmptyObject } from '@adobe/spacecat-shared-utils'; +import MapperRegistry from './mappers/mapper-registry.js'; +import BaseOpportunityMapper from './mappers/base-mapper.js'; + +const HTTP_BAD_REQUEST = 400; +const HTTP_INTERNAL_SERVER_ERROR = 500; +const HTTP_NOT_IMPLEMENTED = 501; + +/** + * Tokowaka Client - Manages edge optimization configurations + */ +class TokowakaClient { + /** + * Creates a TokowakaClient from context + * @param {Object} context - The context object + * @returns {TokowakaClient} - The client instance + */ + static createFrom(context) { + const { env, log = console, s3Client } = context; + const { TOKOWAKA_CONFIG_BUCKET: bucketName } = env; + + if (context.tokowakaClient) { + return context.tokowakaClient; + } + + const client = new TokowakaClient({ bucketName, s3Client }, log); + context.tokowakaClient = client; + return client; + } + + /** + * Constructor + * @param {Object} config - Configuration object + * @param {string} config.bucketName - S3 bucket name for configs + * @param {Object} config.s3Client - AWS S3 client + * @param {Object} log - Logger instance + */ + constructor({ bucketName, s3Client }, log) { + this.log = log; + + if (!hasText(bucketName)) { + throw this.#createError('TOKOWAKA_CONFIG_BUCKET is required', HTTP_BAD_REQUEST); + } + + if (!isNonEmptyObject(s3Client)) { + throw this.#createError('S3 client is required', HTTP_BAD_REQUEST); + } + + this.bucketName = bucketName; + this.s3Client = s3Client; + + // Initialize mapper registry + this.mapperRegistry = new MapperRegistry(log); + } + + #createError(message, status) { + const error = Object.assign(new Error(message), { status }); + this.log.error(error.message); + return error; + } + + /** + * Generates Tokowaka site configuration from suggestions + * @param {Object} site - Site entity + * @param {Object} opportunity - Opportunity entity + * @param {Array} suggestions - Array of suggestion entities + * @returns {Object} - Tokowaka configuration object + */ + generateConfig(site, opportunity, suggestions) { + const opportunityType = opportunity.getType(); + const siteId = site.getId(); + const baseURL = site.getBaseURL(); + + // Get mapper for this opportunity type + const mapper = this.mapperRegistry.getMapper(opportunityType); + if (!mapper) { + throw this.#createError( + `No mapper found for opportunity type: ${opportunityType}. ` + + `Supported types: ${this.mapperRegistry.getSupportedOpportunityTypes().join(', ')}`, + HTTP_NOT_IMPLEMENTED, + ); + } + + // Group suggestions by URL + const suggestionsByUrl = suggestions.reduce((acc, suggestion) => { + const data = suggestion.getData(); + const url = data?.url || data?.pageUrl || data?.recommendations?.[0]?.pageUrl; + + if (!url) { + this.log.warn(`Suggestion ${suggestion.getId()} does not have a URL, skipping`); + return acc; + } + + // Extract path from URL + let urlPath; + try { + urlPath = new URL(url).pathname; + } catch (e) { + this.log.warn(`Invalid URL for suggestion ${suggestion.getId()}: ${url}`); + return acc; + } + + if (!acc[urlPath]) { + acc[urlPath] = []; + } + acc[urlPath].push(suggestion); + return acc; + }, {}); + + // Generate patches for each URL using the mapper + const tokowakaOptimizations = {}; + + Object.entries(suggestionsByUrl).forEach(([urlPath, urlSuggestions]) => { + const patches = urlSuggestions.map((suggestion) => { + const patch = mapper.suggestionToPatch(suggestion, opportunity.getId()); + return patch; + }).filter((patch) => patch !== null); + + if (patches.length > 0) { + tokowakaOptimizations[urlPath] = { + prerender: mapper.requiresPrerender(), + patches, + }; + } + }); + + return { + siteId, + baseURL, + version: '1.0', + tokowakaForceFail: false, + tokowakaOptimizations, + }; + } + + /** + * Registers a custom mapper for an opportunity type + * This allows extending the client with new opportunity types + * @param {BaseOpportunityMapper} mapper - Mapper instance + */ + registerMapper(mapper) { + this.mapperRegistry.registerMapper(mapper); + } + + /** + * Gets list of supported opportunity types + * @returns {string[]} - Array of supported opportunity types + */ + getSupportedOpportunityTypes() { + return this.mapperRegistry.getSupportedOpportunityTypes(); + } + + /** + * Uploads Tokowaka configuration to S3 + * @param {string} apiKey - Tokowaka API key (used as S3 key prefix) + * @param {Object} config - Tokowaka configuration object + * @returns {Promise} - S3 key of uploaded config + */ + async uploadConfig(apiKey, config) { + if (!hasText(apiKey)) { + throw this.#createError('Tokowaka API key is required', HTTP_BAD_REQUEST); + } + + if (!isNonEmptyObject(config)) { + throw this.#createError('Config object is required', HTTP_BAD_REQUEST); + } + + const s3Key = `${apiKey}/v1/tokowaka-site-config.json`; + + try { + const command = new PutObjectCommand({ + Bucket: this.bucketName, + Key: s3Key, + Body: JSON.stringify(config, null, 2), + ContentType: 'application/json', + CacheControl: 'max-age=86400', // 24 hours + }); + + await this.s3Client.send(command); + this.log.info(`Successfully uploaded Tokowaka config to s3://${this.bucketName}/${s3Key}`); + + return s3Key; + } catch (error) { + this.log.error(`Failed to upload Tokowaka config to S3: ${error.message}`, error); + throw this.#createError(`S3 upload failed: ${error.message}`, HTTP_INTERNAL_SERVER_ERROR); + } + } + + /** + * Deploys suggestions to Tokowaka by generating config and uploading to S3 + * @param {Object} site - Site entity + * @param {Object} opportunity - Opportunity entity + * @param {Array} suggestions - Array of suggestion entities + * @returns {Promise} - Deployment result with s3Key + */ + async deploySuggestions(site, opportunity, suggestions) { + // Get site's Tokowaka API key + const { tokowakaApiKey } = site.getConfig() || {}; + + if (!hasText(tokowakaApiKey)) { + throw this.#createError( + 'Site does not have a Tokowaka API key configured. Please onboard the site to Tokowaka first.', + HTTP_BAD_REQUEST, + ); + } + + // Generate configuration + this.log.info(`Generating Tokowaka config for site ${site.getId()}, opportunity ${opportunity.getId()}`); + const config = this.generateConfig(site, opportunity, suggestions); + + // Upload to S3 + this.log.info(`Uploading Tokowaka config for ${suggestions.length} suggestions`); + const s3Key = await this.uploadConfig(tokowakaApiKey, config); + + return { + tokowakaApiKey, + s3Key, + config, + }; + } +} + +// Export the client as default and base mapper for custom implementations +export default TokowakaClient; +export { BaseOpportunityMapper }; diff --git a/packages/spacecat-shared-tokowaka-client/src/mappers/base-mapper.js b/packages/spacecat-shared-tokowaka-client/src/mappers/base-mapper.js new file mode 100644 index 000000000..e4968c144 --- /dev/null +++ b/packages/spacecat-shared-tokowaka-client/src/mappers/base-mapper.js @@ -0,0 +1,79 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +/** + * Base class for opportunity mappers + * Each opportunity type should extend this class and implement the abstract methods + */ +export default class BaseOpportunityMapper { + constructor(log) { + this.log = log; + } + + /** + * Returns the opportunity type this mapper handles + * @abstract + * @returns {string} - Opportunity type + */ + // eslint-disable-next-line class-methods-use-this + getOpportunityType() { + throw new Error('getOpportunityType() must be implemented by subclass'); + } + + /** + * Determines if prerendering is required for this opportunity type + * @abstract + * @returns {boolean} - True if prerendering is required + */ + // eslint-disable-next-line class-methods-use-this + requiresPrerender() { + throw new Error('requiresPrerender() must be implemented by subclass'); + } + + /** + * Converts a suggestion to a Tokowaka patch + * @abstract + * @param {Object} _ - Suggestion entity with getId() and getData() methods + * @param {string} _ - Opportunity ID + * @returns {Object|null} - Patch object or null if conversion fails + */ + // eslint-disable-next-line class-methods-use-this, no-unused-vars + suggestionToPatch(_, __) { + throw new Error('suggestionToPatch() must be implemented by subclass'); + } + + /** + * Validates suggestion data before conversion + * @param {Object} _ - Suggestion data + * @returns {boolean} - True if valid + */ + // eslint-disable-next-line class-methods-use-this, no-unused-vars + validateSuggestionData(_) { + return true; // Override in subclass if needed + } + + /** + * Helper method to create base patch structure + * @protected + * @param {string} suggestionId - Suggestion ID + * @param {string} opportunityId - Opportunity ID + * @returns {Object} - Base patch object + */ + createBasePatch(suggestionId, opportunityId) { + return { + opportunityId, + suggestionId, + prerenderRequired: this.requiresPrerender(), + lastUpdated: Date.now(), + }; + } +} diff --git a/packages/spacecat-shared-tokowaka-client/src/mappers/headings-mapper.js b/packages/spacecat-shared-tokowaka-client/src/mappers/headings-mapper.js new file mode 100644 index 000000000..907beba36 --- /dev/null +++ b/packages/spacecat-shared-tokowaka-client/src/mappers/headings-mapper.js @@ -0,0 +1,79 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import BaseOpportunityMapper from './base-mapper.js'; + +/** + * Mapper for headings opportunity + * Handles conversion of heading suggestions to Tokowaka patches + */ +export default class HeadingsMapper extends BaseOpportunityMapper { + // eslint-disable-next-line class-methods-use-this + getOpportunityType() { + return 'headings'; + } + + // eslint-disable-next-line class-methods-use-this + requiresPrerender() { + return true; + } + + suggestionToPatch(suggestion, opportunityId) { + const data = suggestion.getData(); + + if (!this.validateSuggestionData(data)) { + this.log.warn(`Headings suggestion ${suggestion.getId()} has invalid data`); + return null; + } + + // Extract heading text - try multiple field names for backward compatibility + const value = data?.recommendedAction + || data?.value + || data?.suggestedText + || data?.text + || data?.heading; + + // Extract selector - try headingTag first, then explicit selectors + const headingTag = data?.headingTag; + let selector = data?.selector || data?.cssSelector || data?.xpath; + + // If no explicit selector, construct from headingTag + if (!selector && headingTag) { + selector = headingTag; + } + + if (!selector || !value) { + this.log.warn( + `Headings suggestion ${suggestion.getId()} missing required fields: ` + + `selector/headingTag=${selector}, value/recommendedAction=${value}`, + ); + return null; + } + + return { + ...this.createBasePatch(suggestion.getId(), opportunityId), + op: 'replace', + selector, + value, + }; + } + + // eslint-disable-next-line class-methods-use-this + validateSuggestionData(data) { + // At minimum, need either headingTag/selector and recommendedAction/value + const hasSelector = data?.headingTag || data?.selector + || data?.cssSelector || data?.xpath; + const hasValue = data?.recommendedAction || data?.value + || data?.suggestedText || data?.text || data?.heading; + return !!(hasSelector && hasValue); + } +} diff --git a/packages/spacecat-shared-tokowaka-client/src/mappers/mapper-registry.js b/packages/spacecat-shared-tokowaka-client/src/mappers/mapper-registry.js new file mode 100644 index 000000000..dddfd0043 --- /dev/null +++ b/packages/spacecat-shared-tokowaka-client/src/mappers/mapper-registry.js @@ -0,0 +1,85 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import HeadingsMapper from './headings-mapper.js'; + +/** + * Registry for opportunity mappers + * Implements Factory Pattern to get the appropriate mapper for an opportunity type + */ +export default class MapperRegistry { + constructor(log) { + this.log = log; + this.mappers = new Map(); + this.#registerDefaultMappers(); + } + + /** + * Registers default mappers for built-in opportunity types + * @private + */ + #registerDefaultMappers() { + const defaultMappers = [ + HeadingsMapper, + // more mappers here + ]; + + defaultMappers.forEach((MapperClass) => { + const mapper = new MapperClass(this.log); + this.registerMapper(mapper); + }); + } + + /** + * Registers a mapper for an opportunity type + * @param {BaseOpportunityMapper} mapper - Mapper instance + */ + registerMapper(mapper) { + const opportunityType = mapper.getOpportunityType(); + if (this.mappers.has(opportunityType)) { + this.log.warn(`Mapper for opportunity type "${opportunityType}" is being overridden`); + } + this.mappers.set(opportunityType, mapper); + this.log.info(`Registered mapper for opportunity type: ${opportunityType}`); + } + + /** + * Gets mapper for an opportunity type + * @param {string} opportunityType - Type of opportunity + * @returns {BaseOpportunityMapper|null} - Mapper instance or null if not found + */ + getMapper(opportunityType) { + const mapper = this.mappers.get(opportunityType); + if (!mapper) { + this.log.warn(`No mapper found for opportunity type: ${opportunityType}`); + return null; + } + return mapper; + } + + /** + * Checks if a mapper exists for an opportunity type + * @param {string} opportunityType - Type of opportunity + * @returns {boolean} - True if mapper exists + */ + hasMapper(opportunityType) { + return this.mappers.has(opportunityType); + } + + /** + * Gets all registered opportunity types + * @returns {string[]} - Array of opportunity types + */ + getSupportedOpportunityTypes() { + return Array.from(this.mappers.keys()); + } +} diff --git a/packages/spacecat-shared-tokowaka-client/test/index.test.js b/packages/spacecat-shared-tokowaka-client/test/index.test.js new file mode 100644 index 000000000..29afb0497 --- /dev/null +++ b/packages/spacecat-shared-tokowaka-client/test/index.test.js @@ -0,0 +1,420 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +/* eslint-env mocha */ + +import { expect, use } from 'chai'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import TokowakaClient from '../src/index.js'; + +use(sinonChai); + +describe('TokowakaClient', () => { + let client; + let s3Client; + let log; + let mockSite; + let mockOpportunity; + let mockSuggestions; + + beforeEach(() => { + s3Client = { + send: sinon.stub().resolves(), + }; + + log = { + info: sinon.stub(), + warn: sinon.stub(), + error: sinon.stub(), + }; + + client = new TokowakaClient( + { bucketName: 'test-bucket', s3Client }, + log, + ); + + mockSite = { + getId: () => 'site-123', + getBaseURL: () => 'https://example.com', + getConfig: () => ({ tokowakaApiKey: 'test-api-key-123' }), + }; + + mockOpportunity = { + getId: () => 'opp-123', + getType: () => 'headings', + }; + + mockSuggestions = [ + { + getId: () => 'sugg-1', + getData: () => ({ + url: 'https://example.com/page1', + headingTag: 'h1', + recommendedAction: 'New Heading', + checkType: 'heading-empty', + }), + }, + { + getId: () => 'sugg-2', + getData: () => ({ + url: 'https://example.com/page1', + headingTag: 'h2', + recommendedAction: 'New Subtitle', + checkType: 'heading-empty', + }), + }, + ]; + }); + + afterEach(() => { + sinon.restore(); + }); + + describe('constructor', () => { + it('should create an instance with valid config', () => { + expect(client).to.be.instanceOf(TokowakaClient); + expect(client.bucketName).to.equal('test-bucket'); + expect(client.s3Client).to.equal(s3Client); + }); + + it('should throw error if bucketName is missing', () => { + expect(() => new TokowakaClient({ s3Client }, log)) + .to.throw('TOKOWAKA_CONFIG_BUCKET is required'); + }); + + it('should throw error if s3Client is missing', () => { + expect(() => new TokowakaClient({ bucketName: 'test-bucket' }, log)) + .to.throw('S3 client is required'); + }); + }); + + describe('createFrom', () => { + it('should create client from context', () => { + const context = { + env: { TOKOWAKA_CONFIG_BUCKET: 'test-bucket' }, + s3Client, + log, + }; + + const createdClient = TokowakaClient.createFrom(context); + + expect(createdClient).to.be.instanceOf(TokowakaClient); + expect(context.tokowakaClient).to.equal(createdClient); + }); + + it('should reuse existing client from context', () => { + const existingClient = new TokowakaClient( + { bucketName: 'test-bucket', s3Client }, + log, + ); + const context = { + env: { TOKOWAKA_CONFIG_BUCKET: 'test-bucket' }, + s3Client, + log, + tokowakaClient: existingClient, + }; + + const createdClient = TokowakaClient.createFrom(context); + + expect(createdClient).to.equal(existingClient); + }); + }); + + describe('generateConfig', () => { + it('should generate config for headings opportunity', () => { + const config = client.generateConfig(mockSite, mockOpportunity, mockSuggestions); + + expect(config).to.deep.include({ + siteId: 'site-123', + baseURL: 'https://example.com', + version: '1.0', + tokowakaForceFail: false, + }); + + expect(config.tokowakaOptimizations).to.have.property('/page1'); + expect(config.tokowakaOptimizations['/page1'].prerender).to.be.true; + expect(config.tokowakaOptimizations['/page1'].patches).to.have.length(2); + + const patch = config.tokowakaOptimizations['/page1'].patches[0]; + expect(patch).to.include({ + op: 'replace', + selector: 'h1', + value: 'New Heading', + opportunityId: 'opp-123', + suggestionId: 'sugg-1', + prerenderRequired: true, + }); + expect(patch).to.have.property('lastUpdated'); + }); + + it('should generate config for meta-tags opportunity', () => { + mockOpportunity.getType = () => 'meta-tags'; + mockSuggestions = [ + { + getId: () => 'sugg-1', + getData: () => ({ + url: 'https://example.com/page1', + metaName: 'description', + content: 'New meta description', + }), + }, + ]; + + const config = client.generateConfig(mockSite, mockOpportunity, mockSuggestions); + + const patch = config.tokowakaOptimizations['/page1'].patches[0]; + expect(patch).to.include({ + op: 'replace', + selector: 'meta[name="description"]', + attribute: 'content', + value: 'New meta description', + prerenderRequired: false, + }); + }); + + it('should generate config for prerender opportunity', () => { + mockOpportunity.getType = () => 'prerender'; + mockSuggestions = [ + { + getId: () => 'sugg-1', + getData: () => ({ + url: 'https://example.com/page1', + }), + }, + ]; + + const config = client.generateConfig(mockSite, mockOpportunity, mockSuggestions); + + const patch = config.tokowakaOptimizations['/page1'].patches[0]; + expect(patch).to.include({ + op: 'prerender', + prerenderRequired: true, + }); + }); + + it('should generate config for structured-data opportunity', () => { + mockOpportunity.getType = () => 'structured-data'; + mockSuggestions = [ + { + getId: () => 'sugg-1', + getData: () => ({ + url: 'https://example.com/page1', + script: '{"@context": "https://schema.org"}', + }), + }, + ]; + + const config = client.generateConfig(mockSite, mockOpportunity, mockSuggestions); + + const patch = config.tokowakaOptimizations['/page1'].patches[0]; + expect(patch).to.include({ + op: 'add', + selector: 'head', + element: 'script', + value: '{"@context": "https://schema.org"}', + prerenderRequired: true, + }); + expect(patch.attributes).to.deep.equal({ type: 'application/ld+json' }); + }); + + it('should group suggestions by URL path', () => { + mockSuggestions = [ + { + getId: () => 'sugg-1', + getData: () => ({ + url: 'https://example.com/page1', + headingTag: 'h1', + recommendedAction: 'Page 1 Heading', + }), + }, + { + getId: () => 'sugg-2', + getData: () => ({ + url: 'https://example.com/page2', + headingTag: 'h1', + recommendedAction: 'Page 2 Heading', + }), + }, + ]; + + const config = client.generateConfig(mockSite, mockOpportunity, mockSuggestions); + + expect(Object.keys(config.tokowakaOptimizations)).to.have.length(2); + expect(config.tokowakaOptimizations).to.have.property('/page1'); + expect(config.tokowakaOptimizations).to.have.property('/page2'); + }); + + it('should skip suggestions without URL', () => { + mockSuggestions = [ + { + getId: () => 'sugg-1', + getData: () => ({ + selector: 'h1', + value: 'Heading without URL', + }), + }, + ]; + + const config = client.generateConfig(mockSite, mockOpportunity, mockSuggestions); + + expect(Object.keys(config.tokowakaOptimizations)).to.have.length(0); + expect(log.warn).to.have.been.calledWith(sinon.match(/does not have a URL/)); + }); + + it('should skip suggestions with invalid URL', () => { + mockSuggestions = [ + { + getId: () => 'sugg-1', + getData: () => ({ + url: 'not-a-valid-url', + selector: 'h1', + value: 'Heading', + }), + }, + ]; + + const config = client.generateConfig(mockSite, mockOpportunity, mockSuggestions); + + expect(Object.keys(config.tokowakaOptimizations)).to.have.length(0); + expect(log.warn).to.have.been.calledWith(sinon.match(/Invalid URL/)); + }); + + it('should skip suggestions with missing required fields', () => { + mockSuggestions = [ + { + getId: () => 'sugg-1', + getData: () => ({ + url: 'https://example.com/page1', + // Missing headingTag and recommendedAction + }), + }, + ]; + + const config = client.generateConfig(mockSite, mockOpportunity, mockSuggestions); + + expect(Object.keys(config.tokowakaOptimizations)).to.have.length(0); + expect(log.warn).to.have.been.calledWith(sinon.match(/has invalid data/)); + }); + + it('should handle unsupported opportunity types', () => { + mockOpportunity.getType = () => 'unsupported-type'; + mockSuggestions = [ + { + getId: () => 'sugg-1', + getData: () => ({ + url: 'https://example.com/page1', + }), + }, + ]; + + expect(() => client.generateConfig(mockSite, mockOpportunity, mockSuggestions)) + .to.throw(/No mapper found for opportunity type: unsupported-type/) + .with.property('status', 501); + }); + }); + + describe('uploadConfig', () => { + it('should upload config to S3', async () => { + const config = { + siteId: 'site-123', + baseURL: 'https://example.com', + version: '1.0', + tokowakaForceFail: false, + tokowakaOptimizations: {}, + }; + + const s3Key = await client.uploadConfig('test-api-key', config); + + expect(s3Key).to.equal('test-api-key/v1/tokowaka-site-config.json'); + expect(s3Client.send).to.have.been.calledOnce; + + const command = s3Client.send.firstCall.args[0]; + expect(command.input.Bucket).to.equal('test-bucket'); + expect(command.input.Key).to.equal('test-api-key/v1/tokowaka-site-config.json'); + expect(command.input.ContentType).to.equal('application/json'); + expect(command.input.CacheControl).to.equal('max-age=86400'); + expect(JSON.parse(command.input.Body)).to.deep.equal(config); + }); + + it('should throw error if apiKey is missing', async () => { + const config = { siteId: 'site-123' }; + + try { + await client.uploadConfig('', config); + expect.fail('Should have thrown error'); + } catch (error) { + expect(error.message).to.include('Tokowaka API key is required'); + expect(error.status).to.equal(400); + } + }); + + it('should throw error if config is empty', async () => { + try { + await client.uploadConfig('test-api-key', {}); + expect.fail('Should have thrown error'); + } catch (error) { + expect(error.message).to.include('Config object is required'); + expect(error.status).to.equal(400); + } + }); + + it('should handle S3 upload failure', async () => { + s3Client.send.rejects(new Error('Network error')); + const config = { siteId: 'site-123', tokowakaOptimizations: {} }; + + try { + await client.uploadConfig('test-api-key', config); + expect.fail('Should have thrown error'); + } catch (error) { + expect(error.message).to.include('S3 upload failed'); + expect(error.status).to.equal(500); + } + }); + }); + + describe('deploySuggestions', () => { + it('should deploy suggestions successfully', async () => { + const result = await client.deploySuggestions( + mockSite, + mockOpportunity, + mockSuggestions, + ); + + expect(result).to.have.property('tokowakaApiKey', 'test-api-key-123'); + expect(result).to.have.property('s3Key', 'test-api-key-123/v1/tokowaka-site-config.json'); + expect(result).to.have.property('config'); + expect(result.config.siteId).to.equal('site-123'); + expect(s3Client.send).to.have.been.calledOnce; + }); + + it('should throw error if site does not have Tokowaka API key', async () => { + mockSite.getConfig = () => ({}); + + try { + await client.deploySuggestions(mockSite, mockOpportunity, mockSuggestions); + expect.fail('Should have thrown error'); + } catch (error) { + expect(error.message).to.include('Tokowaka API key configured'); + expect(error.status).to.equal(400); + } + }); + + it('should log progress during deployment', async () => { + await client.deploySuggestions(mockSite, mockOpportunity, mockSuggestions); + + expect(log.info).to.have.been.calledWith(sinon.match(/Generating Tokowaka config/)); + expect(log.info).to.have.been.calledWith(sinon.match(/Uploading Tokowaka config/)); + expect(log.info).to.have.been.calledWith(sinon.match(/Successfully uploaded/)); + }); + }); +}); diff --git a/packages/spacecat-shared-tokowaka-client/test/setup-env.js b/packages/spacecat-shared-tokowaka-client/test/setup-env.js new file mode 100644 index 000000000..f9c07f88c --- /dev/null +++ b/packages/spacecat-shared-tokowaka-client/test/setup-env.js @@ -0,0 +1,13 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +process.env.TOKOWAKA_CONFIG_BUCKET = 'test-bucket'; From 5051619aad3daa56efd8a6dee476e20e7ef94b64 Mon Sep 17 00:00:00 2001 From: Divyansh Pratap Date: Wed, 15 Oct 2025 23:52:26 +0530 Subject: [PATCH 02/20] fix: add tokowaka client --- .../CHANGELOG.md | 12 ++ .../spacecat-shared-tokowaka-client/README.md | 147 ++++++++++++++ .../package.json | 7 +- .../src/cdn/akamai-cdn-client.js | 180 ++++++++++++++++++ .../src/cdn/base-cdn-client.js | 61 ++++++ .../src/cdn/cdn-client-registry.js | 86 +++++++++ .../src/index.d.ts | 69 +++++++ .../src/index.js | 69 ++++++- .../src/mappers/headings-mapper.js | 35 +--- .../src/mappers/mapper-registry.js | 2 +- 10 files changed, 631 insertions(+), 37 deletions(-) create mode 100644 packages/spacecat-shared-tokowaka-client/CHANGELOG.md create mode 100644 packages/spacecat-shared-tokowaka-client/src/cdn/akamai-cdn-client.js create mode 100644 packages/spacecat-shared-tokowaka-client/src/cdn/base-cdn-client.js create mode 100644 packages/spacecat-shared-tokowaka-client/src/cdn/cdn-client-registry.js diff --git a/packages/spacecat-shared-tokowaka-client/CHANGELOG.md b/packages/spacecat-shared-tokowaka-client/CHANGELOG.md new file mode 100644 index 000000000..462ccf5a9 --- /dev/null +++ b/packages/spacecat-shared-tokowaka-client/CHANGELOG.md @@ -0,0 +1,12 @@ +# @adobe/spacecat-shared-tokowaka-client + +## 1.0.0 (2025-10-15) + +### Features + +* Initial release of Tokowaka Client +* Support for edge-based optimization deployment +* S3 configuration management +* Extensible mapper pattern for opportunity-specific transformations +* Built-in support for headings opportunity + diff --git a/packages/spacecat-shared-tokowaka-client/README.md b/packages/spacecat-shared-tokowaka-client/README.md index b9f33e316..f6721049f 100644 --- a/packages/spacecat-shared-tokowaka-client/README.md +++ b/packages/spacecat-shared-tokowaka-client/README.md @@ -127,7 +127,154 @@ interface DeploymentResult { tokowakaApiKey: string; s3Key: string; config: TokowakaConfig; + cdnInvalidation: CdnInvalidationResult | null; } + +interface CdnInvalidationResult { + status: string; + provider?: string; + purgeId?: string; + estimatedSeconds?: number; + paths?: number; + message?: string; +} +``` + +##### `invalidateCdnCache(site, s3Key)` + +Invalidates CDN cache for the Tokowaka configuration. + +**Parameters:** +- `site` (Object): Site entity with CDN configuration +- `s3Key` (string): S3 key of the uploaded configuration + +**Returns:** `Promise` + +This method is called automatically after S3 upload in `deploySuggestions()`. Failures are logged but don't block deployment. + +## CDN Cache Invalidation + +The Tokowaka client automatically invalidates CDN caches after uploading configurations to ensure fresh content is served immediately. This feature is: +- ✅ **Automatic**: Triggered after every successful S3 upload +- ✅ **Non-blocking**: Failures are logged but don't prevent deployment +- ✅ **Extensible**: Support for multiple CDN providers + +### Site Configuration + +Configure CDN invalidation in your site config: + +```javascript +{ + "tokowakaApiKey": "OCtrOiKqOxhg4Er3lzYDJS8FAeEUSriK", + "cdn": { + "provider": "akamai", + "config": { + "clientToken": "akab-xxxxx", + "clientSecret": "xxxxxx", + "accessToken": "akab-xxxxx", + "baseUrl": "https://akaa-baseurl-xxx.luna.akamaiapis.net" + } + } +} +``` + +### Supported CDN Providers + +| Provider | Status | Authentication Method | +|----------|--------|----------------------| +| **Akamai** | ✅ Supported | EdgeGrid (HMAC-SHA256) | +| Cloudflare | 🔜 Coming soon | API Token | +| Fastly | 🔜 Coming soon | API Key | +| AWS CloudFront | 🔜 Coming soon | AWS IAM | + +### Akamai CDN Configuration + +```typescript +{ + provider: 'akamai', + config: { + clientToken: string; // Akamai {OPEN} API client token + clientSecret: string; // Akamai client secret + accessToken: string; // Akamai access token + baseUrl?: string; // Optional: Akamai API base URL + } +} +``` + +**Getting Akamai credentials:** +1. Log in to Akamai Control Center +2. Navigate to Identity & Access Management +3. Create API client with CCU (Cache Control Utility) permissions +4. Copy credentials to site config + +### Custom CDN Providers + +You can add support for additional CDN providers by creating custom CDN clients: + +```javascript +import { BaseCdnClient } from '@adobe/spacecat-shared-tokowaka-client'; + +class CustomCdnClient extends BaseCdnClient { + getProviderName() { + return 'custom-cdn'; + } + + validateConfig() { + return !!(this.config.apiKey && this.config.apiSecret); + } + + async invalidateCache(paths) { + // Implement CDN-specific cache invalidation + const response = await fetch('https://api.custom-cdn.com/purge', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${this.config.apiKey}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ urls: paths }), + }); + + if (!response.ok) { + throw new Error(`Purge failed: ${response.status}`); + } + + return { + status: 'success', + provider: 'custom-cdn', + paths: paths.length, + }; + } +} + +// Register the custom CDN client +const client = TokowakaClient.createFrom(context); +client.cdnClientRegistry.registerClient('custom-cdn', CustomCdnClient); +``` + +### Testing CDN Invalidation + +```javascript +const result = await tokowakaClient.deploySuggestions(site, opportunity, suggestions); + +if (result.cdnInvalidation) { + console.log('CDN cache invalidated:', result.cdnInvalidation); + console.log('Provider:', result.cdnInvalidation.provider); + console.log('Purge ID:', result.cdnInvalidation.purgeId); +} else { + console.log('CDN invalidation skipped (no CDN configured)'); +} +``` + +### Checking Invalidation Status (Akamai) + +```javascript +import { AkamaiCdnClient } from '@adobe/spacecat-shared-tokowaka-client'; + +const cdnClient = new AkamaiCdnClient(cdnConfig, log); +const status = await cdnClient.getInvalidationStatus(purgeId); + +console.log('Purge status:', status.status); +// Status values: 'In-Progress', 'Done', 'Unknown' ``` ## Supported Opportunity Types diff --git a/packages/spacecat-shared-tokowaka-client/package.json b/packages/spacecat-shared-tokowaka-client/package.json index 8e54efc35..5c9847fbb 100644 --- a/packages/spacecat-shared-tokowaka-client/package.json +++ b/packages/spacecat-shared-tokowaka-client/package.json @@ -3,6 +3,10 @@ "version": "1.0.0", "description": "Tokowaka Client for SpaceCat - Edge optimization config management", "type": "module", + "engines": { + "node": ">=22.0.0 <23.0.0", + "npm": ">=10.9.0 <12.0.0" + }, "main": "src/index.js", "types": "src/index.d.ts", "scripts": { @@ -12,8 +16,7 @@ }, "repository": { "type": "git", - "url": "https://github.com/adobe/spacecat-shared.git", - "directory": "packages/spacecat-shared-tokowaka-client" + "url": "https://github.com/adobe/spacecat-shared.git" }, "author": "", "license": "Apache-2.0", diff --git a/packages/spacecat-shared-tokowaka-client/src/cdn/akamai-cdn-client.js b/packages/spacecat-shared-tokowaka-client/src/cdn/akamai-cdn-client.js new file mode 100644 index 000000000..1845a7031 --- /dev/null +++ b/packages/spacecat-shared-tokowaka-client/src/cdn/akamai-cdn-client.js @@ -0,0 +1,180 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import crypto from 'crypto'; +import BaseCdnClient from './base-cdn-client.js'; + +/** + * Akamai CDN client implementation + * Handles cache invalidation (purge) for Akamai CDN + */ +export default class AkamaiCdnClient extends BaseCdnClient { + constructor(config, log) { + super(config, log); + this.baseUrl = config.baseUrl || 'https://akaa-baseurl-xxx-xxx.luna.akamaiapis.net'; + } + + // eslint-disable-next-line class-methods-use-this + getProviderName() { + return 'akamai'; + } + + validateConfig() { + const required = ['client_token', 'client_secret', 'access_token', 'host']; + const missing = required.filter((key) => !this.config[key]); + + if (missing.length > 0) { + this.log.error(`Akamai CDN config missing required fields: ${missing.join(', ')}`); + return false; + } + + return true; + } + + /** + * Generates Akamai EdgeGrid authentication header + * @param {string} method - HTTP method + * @param {string} path - API path + * @param {string} body - Request body + * @returns {string} Authorization header value + */ + generateAuthHeader(method, path, body = '') { + const timestamp = new Date().toISOString().replace(/\.\d{3}Z$/, '+0000'); + const nonce = crypto.randomBytes(16).toString('hex'); + + const authData = [ + `client_token=${this.config.clientToken}`, + `access_token=${this.config.accessToken}`, + `timestamp=${timestamp}`, + `nonce=${nonce}`, + ].join(';'); + + const bodyHash = body + ? crypto.createHash('sha256').update(body).digest('base64') + : ''; + + const dataToSign = [ + method.toUpperCase(), + 'https', + this.baseUrl.replace(/^https?:\/\//, ''), + path, + '', + bodyHash, + authData, + ].join('\t'); + + const signingKey = crypto + .createHmac('sha256', this.config.clientSecret) + .update(timestamp) + .digest(); + + const signature = crypto + .createHmac('sha256', signingKey) + .update(dataToSign) + .digest('base64'); + + return `EG1-HMAC-SHA256 ${authData};signature=${signature}`; + } + + /** + * Invalidates Akamai CDN cache for given paths + * @param {Array} paths - Array of URL paths to invalidate + * @returns {Promise} Result of the purge request + */ + async invalidateCache(paths) { + if (!this.validateConfig()) { + throw new Error('Invalid Akamai CDN configuration'); + } + + if (!Array.isArray(paths) || paths.length === 0) { + this.log.warn('No paths provided for cache invalidation'); + return { status: 'skipped', message: 'No paths to invalidate' }; + } + + const endpoint = '/ccu/v3/invalidate/url'; + const body = JSON.stringify({ + objects: paths, + }); + + try { + const authHeader = this.generateAuthHeader('POST', endpoint, body); + + const response = await fetch(`${this.baseUrl}${endpoint}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: authHeader, + }, + body, + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Akamai purge failed: ${response.status} - ${errorText}`); + } + + const result = await response.json(); + this.log.info(`Akamai cache invalidation initiated: ${result.purgeId || result.estimatedSeconds || 'success'}`); + + return { + status: 'success', + provider: 'akamai', + purgeId: result.purgeId, + estimatedSeconds: result.estimatedSeconds, + paths: paths.length, + }; + } catch (error) { + this.log.error(`Failed to invalidate Akamai cache: ${error.message}`, error); + throw error; + } + } + + /** + * Checks the status of an Akamai purge request + * @param {string} purgeId - The purge request ID + * @returns {Promise} Status of the purge request + */ + async getInvalidationStatus(purgeId) { + if (!this.validateConfig()) { + throw new Error('Invalid Akamai CDN configuration'); + } + + const endpoint = `/ccu/v3/purges/${purgeId}`; + + try { + const authHeader = this.generateAuthHeader('GET', endpoint); + + const response = await fetch(`${this.baseUrl}${endpoint}`, { + method: 'GET', + headers: { + Authorization: authHeader, + }, + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Failed to get purge status: ${response.status} - ${errorText}`); + } + + const result = await response.json(); + return { + status: result.purgeStatus || 'unknown', + provider: 'akamai', + purgeId, + ...result, + }; + } catch (error) { + this.log.error(`Failed to get Akamai purge status: ${error.message}`, error); + throw error; + } + } +} diff --git a/packages/spacecat-shared-tokowaka-client/src/cdn/base-cdn-client.js b/packages/spacecat-shared-tokowaka-client/src/cdn/base-cdn-client.js new file mode 100644 index 000000000..3724b194f --- /dev/null +++ b/packages/spacecat-shared-tokowaka-client/src/cdn/base-cdn-client.js @@ -0,0 +1,61 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +/** + * Base class for CDN clients + * Defines the interface that all CDN-specific clients must implement + */ +export default class BaseCdnClient { + constructor(config, log) { + this.config = config; + this.log = log; + } + + /** + * Returns the CDN provider name (e.g., 'akamai', 'cloudflare', 'fastly') + * @returns {string} The CDN provider name + */ + // eslint-disable-next-line class-methods-use-this + getProviderName() { + throw new Error('getProviderName() must be implemented by subclass'); + } + + /** + * Validates the CDN configuration + * @returns {boolean} True if configuration is valid + */ + // eslint-disable-next-line class-methods-use-this, no-unused-vars + validateConfig() { + return true; // Override in subclass if needed + } + + /** + * Invalidates the CDN cache for the given paths + * @param {Array} paths - Array of URL paths to invalidate + * @returns {Promise} Result of the invalidation request + */ + // eslint-disable-next-line class-methods-use-this, no-unused-vars + async invalidateCache(_paths) { + throw new Error('invalidateCache() must be implemented by subclass'); + } + + /** + * Checks the status of an invalidation request + * @param {string} requestId - The invalidation request ID + * @returns {Promise} Status of the invalidation request + */ + // eslint-disable-next-line class-methods-use-this, no-unused-vars + async getInvalidationStatus(_requestId) { + // Optional: Override in subclass if supported + return { status: 'unknown', message: 'Status check not supported' }; + } +} diff --git a/packages/spacecat-shared-tokowaka-client/src/cdn/cdn-client-registry.js b/packages/spacecat-shared-tokowaka-client/src/cdn/cdn-client-registry.js new file mode 100644 index 000000000..e85b8e467 --- /dev/null +++ b/packages/spacecat-shared-tokowaka-client/src/cdn/cdn-client-registry.js @@ -0,0 +1,86 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import AkamaiCdnClient from './akamai-cdn-client.js'; + +/** + * Registry for CDN clients + * Manages different CDN provider implementations + */ +export default class CdnClientRegistry { + constructor(log) { + this.log = log; + this.clients = new Map(); + this.#registerDefaultClients(); + } + + /** + * Registers default CDN clients + * @private + */ + #registerDefaultClients() { + this.registerClient('akamai', AkamaiCdnClient); + } + + /** + * Registers a CDN client class + * @param {string} provider - CDN provider name + * @param {Class} ClientClass - CDN client class + */ + registerClient(provider, ClientClass) { + this.clients.set(provider.toLowerCase(), ClientClass); + } + + /** + * Gets a CDN client instance for the specified provider + * @param {string} provider - CDN provider name + * @param {Object} config - CDN configuration + * @returns {BaseCdnClient|null} CDN client instance or null if not found + */ + getClient(provider, config) { + if (!provider) { + this.log.warn('No CDN provider specified'); + return null; + } + + const ClientClass = this.clients.get(provider.toLowerCase()); + + if (!ClientClass) { + this.log.warn(`No CDN client found for provider: ${provider}`); + return null; + } + + try { + return new ClientClass(config, this.log); + } catch (error) { + this.log.error(`Failed to create CDN client for ${provider}: ${error.message}`, error); + return null; + } + } + + /** + * Gets list of supported CDN providers + * @returns {Array} List of provider names + */ + getSupportedProviders() { + return Array.from(this.clients.keys()); + } + + /** + * Checks if a provider is supported + * @param {string} provider - CDN provider name + * @returns {boolean} True if provider is supported + */ + isProviderSupported(provider) { + return this.clients.has(provider?.toLowerCase()); + } +} diff --git a/packages/spacecat-shared-tokowaka-client/src/index.d.ts b/packages/spacecat-shared-tokowaka-client/src/index.d.ts index 1f0b10134..30d74291b 100644 --- a/packages/spacecat-shared-tokowaka-client/src/index.d.ts +++ b/packages/spacecat-shared-tokowaka-client/src/index.d.ts @@ -38,10 +38,20 @@ export interface TokowakaConfig { tokowakaOptimizations: Record; } +export interface CdnInvalidationResult { + status: string; + provider?: string; + purgeId?: string; + estimatedSeconds?: number; + paths?: number; + message?: string; +} + export interface DeploymentResult { tokowakaApiKey: string; s3Key: string; config: TokowakaConfig; + cdnInvalidation: CdnInvalidationResult | null; } export interface Site { @@ -99,6 +109,63 @@ export abstract class BaseOpportunityMapper { ): Partial; } +/** + * Base class for CDN clients + * Extend this class to create custom CDN clients for different providers + */ +export abstract class BaseCdnClient { + constructor(config: Record, log: any); + + /** + * Returns the CDN provider name + */ + abstract getProviderName(): string; + + /** + * Validates the CDN configuration + */ + validateConfig(): boolean; + + /** + * Invalidates the CDN cache for the given paths + */ + abstract invalidateCache(paths: string[]): Promise; + + /** + * Checks the status of an invalidation request + */ + getInvalidationStatus(requestId: string): Promise; +} + +/** + * Akamai CDN client implementation + */ +export class AkamaiCdnClient extends BaseCdnClient { + constructor(config: { + clientToken: string; + clientSecret: string; + accessToken: string; + baseUrl?: string; + }, log: any); + + getProviderName(): string; + validateConfig(): boolean; + invalidateCache(paths: string[]): Promise; + getInvalidationStatus(purgeId: string): Promise; +} + +/** + * Registry for CDN clients + */ +export class CdnClientRegistry { + constructor(log: any); + + registerClient(provider: string, ClientClass: typeof BaseCdnClient): void; + getClient(provider: string, config: Record): BaseCdnClient | null; + getSupportedProviders(): string[]; + isProviderSupported(provider: string): boolean; +} + export default class TokowakaClient { constructor(config: { bucketName: string; s3Client: S3Client }, log: any); @@ -116,6 +183,8 @@ export default class TokowakaClient { ): TokowakaConfig; uploadConfig(apiKey: string, config: TokowakaConfig): Promise; + + invalidateCdnCache(site: Site, s3Key: string): Promise; deploySuggestions( site: Site, diff --git a/packages/spacecat-shared-tokowaka-client/src/index.js b/packages/spacecat-shared-tokowaka-client/src/index.js index 57700ffa5..b158d98a2 100644 --- a/packages/spacecat-shared-tokowaka-client/src/index.js +++ b/packages/spacecat-shared-tokowaka-client/src/index.js @@ -14,6 +14,7 @@ import { PutObjectCommand } from '@aws-sdk/client-s3'; import { hasText, isNonEmptyObject } from '@adobe/spacecat-shared-utils'; import MapperRegistry from './mappers/mapper-registry.js'; import BaseOpportunityMapper from './mappers/base-mapper.js'; +import CdnClientRegistry from './cdn/cdn-client-registry.js'; const HTTP_BAD_REQUEST = 400; const HTTP_INTERNAL_SERVER_ERROR = 500; @@ -62,8 +63,8 @@ class TokowakaClient { this.bucketName = bucketName; this.s3Client = s3Client; - // Initialize mapper registry this.mapperRegistry = new MapperRegistry(log); + this.cdnClientRegistry = new CdnClientRegistry(log); } #createError(message, status) { @@ -97,14 +98,13 @@ class TokowakaClient { // Group suggestions by URL const suggestionsByUrl = suggestions.reduce((acc, suggestion) => { const data = suggestion.getData(); - const url = data?.url || data?.pageUrl || data?.recommendations?.[0]?.pageUrl; + const url = data?.url; if (!url) { this.log.warn(`Suggestion ${suggestion.getId()} does not have a URL, skipping`); return acc; } - // Extract path from URL let urlPath; try { urlPath = new URL(url).pathname; @@ -186,7 +186,6 @@ class TokowakaClient { Key: s3Key, Body: JSON.stringify(config, null, 2), ContentType: 'application/json', - CacheControl: 'max-age=86400', // 24 hours }); await this.s3Client.send(command); @@ -199,6 +198,59 @@ class TokowakaClient { } } + /** + * Invalidates CDN cache for the Tokowaka config + * @param {Object} site - Site entity + * @param {string} s3Key - S3 key of the uploaded config + * @returns {Promise} - CDN invalidation result or null if skipped + */ + async invalidateCdnCache(site, s3Key) { + const siteConfig = site.getConfig() || {}; + const { cdn } = siteConfig; + + if (!isNonEmptyObject(cdn)) { + this.log.info('No CDN configuration found for site, skipping cache invalidation'); + return null; + } + + const { provider, config: cdnConfig } = cdn; + + if (!hasText(provider) || !cdnConfig) { + this.log.warn('CDN provider or config not specified in site config, skipping cache invalidation'); + return null; + } + + try { + const cdnClient = this.cdnClientRegistry.getClient(provider, cdnConfig); + + if (!cdnClient) { + this.log.warn(`No CDN client available for provider: ${provider}, skipping cache invalidation`); + return null; + } + + // Build CDN paths to invalidate + // The config is accessed via the Tokowaka API key path + const baseURL = site.getBaseURL(); + const pathsToInvalidate = [ + `${baseURL}/${s3Key}`, + ]; + + this.log.debug(`Invalidating CDN cache for ${pathsToInvalidate.length} paths via ${provider}`); + const result = await cdnClient.invalidateCache(pathsToInvalidate); + + this.log.info(`CDN cache invalidation completed: ${JSON.stringify(result)}`); + return result; + } catch (error) { + // Log error but don't fail the deployment + this.log.error(`CDN cache invalidation failed (non-fatal): ${error.message}`, error); + return { + status: 'error', + provider, + message: error.message, + }; + } + } + /** * Deploys suggestions to Tokowaka by generating config and uploading to S3 * @param {Object} site - Site entity @@ -225,14 +277,21 @@ class TokowakaClient { this.log.info(`Uploading Tokowaka config for ${suggestions.length} suggestions`); const s3Key = await this.uploadConfig(tokowakaApiKey, config); + // // Invalidate CDN cache (non-blocking, failures are logged but don't fail deployment) + // const cdnInvalidationResult = await this.invalidateCdnCache(site, s3Key); + return { tokowakaApiKey, s3Key, config, + // cdnInvalidation: cdnInvalidationResult, }; } } -// Export the client as default and base mapper for custom implementations +// Export the client as default and base classes for custom implementations export default TokowakaClient; export { BaseOpportunityMapper }; +export { default as BaseCdnClient } from './cdn/base-cdn-client.js'; +export { default as AkamaiCdnClient } from './cdn/akamai-cdn-client.js'; +export { default as CdnClientRegistry } from './cdn/cdn-client-registry.js'; diff --git a/packages/spacecat-shared-tokowaka-client/src/mappers/headings-mapper.js b/packages/spacecat-shared-tokowaka-client/src/mappers/headings-mapper.js index 907beba36..fa0fd7b55 100644 --- a/packages/spacecat-shared-tokowaka-client/src/mappers/headings-mapper.js +++ b/packages/spacecat-shared-tokowaka-client/src/mappers/headings-mapper.js @@ -35,45 +35,22 @@ export default class HeadingsMapper extends BaseOpportunityMapper { return null; } - // Extract heading text - try multiple field names for backward compatibility - const value = data?.recommendedAction - || data?.value - || data?.suggestedText - || data?.text - || data?.heading; - - // Extract selector - try headingTag first, then explicit selectors - const headingTag = data?.headingTag; - let selector = data?.selector || data?.cssSelector || data?.xpath; - - // If no explicit selector, construct from headingTag - if (!selector && headingTag) { - selector = headingTag; - } - - if (!selector || !value) { - this.log.warn( - `Headings suggestion ${suggestion.getId()} missing required fields: ` - + `selector/headingTag=${selector}, value/recommendedAction=${value}`, - ); - return null; - } + // Use path if available, otherwise construct from headingTag + const selector = data.path || data.headingTag; return { ...this.createBasePatch(suggestion.getId(), opportunityId), op: 'replace', selector, - value, + value: data.recommendedAction, }; } // eslint-disable-next-line class-methods-use-this validateSuggestionData(data) { - // At minimum, need either headingTag/selector and recommendedAction/value - const hasSelector = data?.headingTag || data?.selector - || data?.cssSelector || data?.xpath; - const hasValue = data?.recommendedAction || data?.value - || data?.suggestedText || data?.text || data?.heading; + // At minimum, need heading selector (path or headingTag) and recommendedAction/value + const hasSelector = data?.path || data?.headingTag; + const hasValue = data?.recommendedAction; return !!(hasSelector && hasValue); } } diff --git a/packages/spacecat-shared-tokowaka-client/src/mappers/mapper-registry.js b/packages/spacecat-shared-tokowaka-client/src/mappers/mapper-registry.js index dddfd0043..9aef91b45 100644 --- a/packages/spacecat-shared-tokowaka-client/src/mappers/mapper-registry.js +++ b/packages/spacecat-shared-tokowaka-client/src/mappers/mapper-registry.js @@ -46,7 +46,7 @@ export default class MapperRegistry { registerMapper(mapper) { const opportunityType = mapper.getOpportunityType(); if (this.mappers.has(opportunityType)) { - this.log.warn(`Mapper for opportunity type "${opportunityType}" is being overridden`); + this.log.debug(`Mapper for opportunity type "${opportunityType}" is being overridden`); } this.mappers.set(opportunityType, mapper); this.log.info(`Registered mapper for opportunity type: ${opportunityType}`); From 4469ea7dc095396f4b0570f6ef24fea3546311b5 Mon Sep 17 00:00:00 2001 From: Divyansh Pratap Date: Thu, 16 Oct 2025 00:49:32 +0530 Subject: [PATCH 03/20] fix: cdn changes --- .../src/models/site/config.js | 17 +++++ .../src/index.d.ts | 6 +- .../src/index.js | 64 +++++++++++++---- .../src/mappers/base-mapper.js | 11 +++ .../src/mappers/headings-mapper.js | 21 ++++++ .../test/index.test.js | 71 ------------------- 6 files changed, 103 insertions(+), 87 deletions(-) diff --git a/packages/spacecat-shared-data-access/src/models/site/config.js b/packages/spacecat-shared-data-access/src/models/site/config.js index 199ba24d6..e70330720 100644 --- a/packages/spacecat-shared-data-access/src/models/site/config.js +++ b/packages/spacecat-shared-data-access/src/models/site/config.js @@ -305,6 +305,18 @@ export const configSchema = Joi.object({ ).optional(), outputLocation: Joi.string().required(), }).optional(), + tokowakaConfig: Joi.object({ + apiKey: Joi.string().required(), + cdn: Joi.object({ + provider: Joi.string().required(), + config: Joi.object({ + client_token: Joi.string().required(), + client_secret: Joi.string().required(), + access_token: Joi.string().required(), + host: Joi.string().required(), + }).required(), + }).optional(), + }).optional(), contentAiConfig: Joi.object({ index: Joi.string().optional(), }).optional(), @@ -410,6 +422,7 @@ export const Config = (data = {}) => { }; self.getLlmoCdnlogsFilter = () => state?.llmo?.cdnlogsFilter; self.getLlmoCdnBucketConfig = () => state?.llmo?.cdnBucketConfig; + self.getTokowakaConfig = () => state?.tokowakaConfig; self.updateSlackConfig = (channel, workspace, invitedUserCount) => { state.slack = { @@ -657,6 +670,10 @@ export const Config = (data = {}) => { state.cdnLogsConfig = cdnLogsConfig; }; + self.updateTokowakaConfig = (tokowakaConfig) => { + state.tokowakaConfig = tokowakaConfig; + }; + return Object.freeze(self); }; diff --git a/packages/spacecat-shared-tokowaka-client/src/index.d.ts b/packages/spacecat-shared-tokowaka-client/src/index.d.ts index 30d74291b..0d97383d6 100644 --- a/packages/spacecat-shared-tokowaka-client/src/index.d.ts +++ b/packages/spacecat-shared-tokowaka-client/src/index.d.ts @@ -49,9 +49,11 @@ export interface CdnInvalidationResult { export interface DeploymentResult { tokowakaApiKey: string; - s3Key: string; - config: TokowakaConfig; + s3Key: string | null; + config: TokowakaConfig | null; cdnInvalidation: CdnInvalidationResult | null; + succeededSuggestions: Array; + failedSuggestions: Array<{ suggestion: any; reason: string }>; } export interface Site { diff --git a/packages/spacecat-shared-tokowaka-client/src/index.js b/packages/spacecat-shared-tokowaka-client/src/index.js index b158d98a2..691b4763e 100644 --- a/packages/spacecat-shared-tokowaka-client/src/index.js +++ b/packages/spacecat-shared-tokowaka-client/src/index.js @@ -13,7 +13,6 @@ import { PutObjectCommand } from '@aws-sdk/client-s3'; import { hasText, isNonEmptyObject } from '@adobe/spacecat-shared-utils'; import MapperRegistry from './mappers/mapper-registry.js'; -import BaseOpportunityMapper from './mappers/base-mapper.js'; import CdnClientRegistry from './cdn/cdn-client-registry.js'; const HTTP_BAD_REQUEST = 400; @@ -201,10 +200,10 @@ class TokowakaClient { /** * Invalidates CDN cache for the Tokowaka config * @param {Object} site - Site entity - * @param {string} s3Key - S3 key of the uploaded config + * @param {string} _ - S3 key of the uploaded config * @returns {Promise} - CDN invalidation result or null if skipped */ - async invalidateCdnCache(site, s3Key) { + async invalidateCdnCache(site, _) { const siteConfig = site.getConfig() || {}; const { cdn } = siteConfig; @@ -230,9 +229,8 @@ class TokowakaClient { // Build CDN paths to invalidate // The config is accessed via the Tokowaka API key path - const baseURL = site.getBaseURL(); const pathsToInvalidate = [ - `${baseURL}/${s3Key}`, + `/opportunities/${siteConfig.getTokowakaConfig().apiKey}`, ]; this.log.debug(`Invalidating CDN cache for ${pathsToInvalidate.length} paths via ${provider}`); @@ -256,7 +254,7 @@ class TokowakaClient { * @param {Object} site - Site entity * @param {Object} opportunity - Opportunity entity * @param {Array} suggestions - Array of suggestion entities - * @returns {Promise} - Deployment result with s3Key + * @returns {Promise} - Deployment result with succeeded/failed suggestions */ async deploySuggestions(site, opportunity, suggestions) { // Get site's Tokowaka API key @@ -269,15 +267,55 @@ class TokowakaClient { ); } - // Generate configuration + const opportunityType = opportunity.getType(); + const mapper = this.mapperRegistry.getMapper(opportunityType); + if (!mapper) { + throw this.#createError( + `No mapper found for opportunity type: ${opportunityType}. ` + + `Supported types: ${this.mapperRegistry.getSupportedOpportunityTypes().join(', ')}`, + HTTP_NOT_IMPLEMENTED, + ); + } + + // Validate which suggestions can be deployed using mapper's canDeploy method + const eligibleSuggestions = []; + const ineligibleSuggestions = []; + + suggestions.forEach((suggestion) => { + const eligibility = mapper.canDeploy(suggestion); + if (eligibility.eligible) { + eligibleSuggestions.push(suggestion); + } else { + ineligibleSuggestions.push({ + suggestion, + reason: eligibility.reason || 'Suggestion cannot be deployed', + }); + } + }); + + this.log.debug(`Deploying ${eligibleSuggestions.length} eligible suggestions (${ineligibleSuggestions.length} ineligible)`); + + if (eligibleSuggestions.length === 0) { + this.log.warn('No eligible suggestions to deploy'); + return { + tokowakaApiKey, + s3Key: null, + config: null, + cdnInvalidation: null, + succeededSuggestions: [], + failedSuggestions: ineligibleSuggestions, + }; + } + + // Generate configuration with eligible suggestions only this.log.info(`Generating Tokowaka config for site ${site.getId()}, opportunity ${opportunity.getId()}`); - const config = this.generateConfig(site, opportunity, suggestions); + const config = this.generateConfig(site, opportunity, eligibleSuggestions); // Upload to S3 - this.log.info(`Uploading Tokowaka config for ${suggestions.length} suggestions`); + this.log.info(`Uploading Tokowaka config for ${eligibleSuggestions.length} suggestions`); const s3Key = await this.uploadConfig(tokowakaApiKey, config); - // // Invalidate CDN cache (non-blocking, failures are logged but don't fail deployment) + // Invalidate CDN cache (non-blocking, failures are logged but don't fail deployment) // const cdnInvalidationResult = await this.invalidateCdnCache(site, s3Key); return { @@ -285,13 +323,11 @@ class TokowakaClient { s3Key, config, // cdnInvalidation: cdnInvalidationResult, + succeededSuggestions: eligibleSuggestions, + failedSuggestions: ineligibleSuggestions, }; } } // Export the client as default and base classes for custom implementations export default TokowakaClient; -export { BaseOpportunityMapper }; -export { default as BaseCdnClient } from './cdn/base-cdn-client.js'; -export { default as AkamaiCdnClient } from './cdn/akamai-cdn-client.js'; -export { default as CdnClientRegistry } from './cdn/cdn-client-registry.js'; diff --git a/packages/spacecat-shared-tokowaka-client/src/mappers/base-mapper.js b/packages/spacecat-shared-tokowaka-client/src/mappers/base-mapper.js index e4968c144..0fa98db82 100644 --- a/packages/spacecat-shared-tokowaka-client/src/mappers/base-mapper.js +++ b/packages/spacecat-shared-tokowaka-client/src/mappers/base-mapper.js @@ -61,6 +61,17 @@ export default class BaseOpportunityMapper { return true; // Override in subclass if needed } + /** + * Checks if a suggestion can be deployed for this opportunity type + * Override this method to add custom deployment eligibility checks + * @param {Object} _ - Suggestion object + * @returns {Object} - { eligible: boolean, reason?: string } + */ + // eslint-disable-next-line class-methods-use-this, no-unused-vars + canDeploy(_) { + return { eligible: true }; + } + /** * Helper method to create base patch structure * @protected diff --git a/packages/spacecat-shared-tokowaka-client/src/mappers/headings-mapper.js b/packages/spacecat-shared-tokowaka-client/src/mappers/headings-mapper.js index fa0fd7b55..04f13ff59 100644 --- a/packages/spacecat-shared-tokowaka-client/src/mappers/headings-mapper.js +++ b/packages/spacecat-shared-tokowaka-client/src/mappers/headings-mapper.js @@ -53,4 +53,25 @@ export default class HeadingsMapper extends BaseOpportunityMapper { const hasValue = data?.recommendedAction; return !!(hasSelector && hasValue); } + + /** + * Checks if a heading suggestion can be deployed + * Only empty headings are eligible for deployment + * @param {Object} suggestion - Suggestion object + * @returns {Object} { eligible: boolean, reason?: string } + */ + // eslint-disable-next-line class-methods-use-this + canDeploy(suggestion) { + const data = suggestion.getData(); + const checkType = data?.checkType; + + if (checkType !== 'heading-empty') { + return { + eligible: false, + reason: `Only empty headings can be deployed. This suggestion has checkType: ${checkType || 'unknown'}`, + }; + } + + return { eligible: true }; + } } diff --git a/packages/spacecat-shared-tokowaka-client/test/index.test.js b/packages/spacecat-shared-tokowaka-client/test/index.test.js index 29afb0497..5e8b277af 100644 --- a/packages/spacecat-shared-tokowaka-client/test/index.test.js +++ b/packages/spacecat-shared-tokowaka-client/test/index.test.js @@ -157,76 +157,6 @@ describe('TokowakaClient', () => { expect(patch).to.have.property('lastUpdated'); }); - it('should generate config for meta-tags opportunity', () => { - mockOpportunity.getType = () => 'meta-tags'; - mockSuggestions = [ - { - getId: () => 'sugg-1', - getData: () => ({ - url: 'https://example.com/page1', - metaName: 'description', - content: 'New meta description', - }), - }, - ]; - - const config = client.generateConfig(mockSite, mockOpportunity, mockSuggestions); - - const patch = config.tokowakaOptimizations['/page1'].patches[0]; - expect(patch).to.include({ - op: 'replace', - selector: 'meta[name="description"]', - attribute: 'content', - value: 'New meta description', - prerenderRequired: false, - }); - }); - - it('should generate config for prerender opportunity', () => { - mockOpportunity.getType = () => 'prerender'; - mockSuggestions = [ - { - getId: () => 'sugg-1', - getData: () => ({ - url: 'https://example.com/page1', - }), - }, - ]; - - const config = client.generateConfig(mockSite, mockOpportunity, mockSuggestions); - - const patch = config.tokowakaOptimizations['/page1'].patches[0]; - expect(patch).to.include({ - op: 'prerender', - prerenderRequired: true, - }); - }); - - it('should generate config for structured-data opportunity', () => { - mockOpportunity.getType = () => 'structured-data'; - mockSuggestions = [ - { - getId: () => 'sugg-1', - getData: () => ({ - url: 'https://example.com/page1', - script: '{"@context": "https://schema.org"}', - }), - }, - ]; - - const config = client.generateConfig(mockSite, mockOpportunity, mockSuggestions); - - const patch = config.tokowakaOptimizations['/page1'].patches[0]; - expect(patch).to.include({ - op: 'add', - selector: 'head', - element: 'script', - value: '{"@context": "https://schema.org"}', - prerenderRequired: true, - }); - expect(patch.attributes).to.deep.equal({ type: 'application/ld+json' }); - }); - it('should group suggestions by URL path', () => { mockSuggestions = [ { @@ -342,7 +272,6 @@ describe('TokowakaClient', () => { expect(command.input.Bucket).to.equal('test-bucket'); expect(command.input.Key).to.equal('test-api-key/v1/tokowaka-site-config.json'); expect(command.input.ContentType).to.equal('application/json'); - expect(command.input.CacheControl).to.equal('max-age=86400'); expect(JSON.parse(command.input.Body)).to.deep.equal(config); }); From a4cbec77482db2c0d7933fa4f7483d099f5e3891 Mon Sep 17 00:00:00 2001 From: Divyansh Pratap Date: Thu, 16 Oct 2025 01:05:57 +0530 Subject: [PATCH 04/20] fix: cdn changes --- packages/spacecat-shared-tokowaka-client/src/index.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/spacecat-shared-tokowaka-client/src/index.js b/packages/spacecat-shared-tokowaka-client/src/index.js index 691b4763e..8c53f09db 100644 --- a/packages/spacecat-shared-tokowaka-client/src/index.js +++ b/packages/spacecat-shared-tokowaka-client/src/index.js @@ -258,9 +258,10 @@ class TokowakaClient { */ async deploySuggestions(site, opportunity, suggestions) { // Get site's Tokowaka API key - const { tokowakaApiKey } = site.getConfig() || {}; + const { tokowakaConfig } = site.getConfig()?.getTokowakaConfig() || {}; + const { apiKey } = tokowakaConfig || {}; - if (!hasText(tokowakaApiKey)) { + if (!hasText(apiKey)) { throw this.#createError( 'Site does not have a Tokowaka API key configured. Please onboard the site to Tokowaka first.', HTTP_BAD_REQUEST, @@ -298,7 +299,7 @@ class TokowakaClient { if (eligibleSuggestions.length === 0) { this.log.warn('No eligible suggestions to deploy'); return { - tokowakaApiKey, + tokowakaApiKey: apiKey, s3Key: null, config: null, cdnInvalidation: null, @@ -313,13 +314,13 @@ class TokowakaClient { // Upload to S3 this.log.info(`Uploading Tokowaka config for ${eligibleSuggestions.length} suggestions`); - const s3Key = await this.uploadConfig(tokowakaApiKey, config); + const s3Key = await this.uploadConfig(apiKey, config); // Invalidate CDN cache (non-blocking, failures are logged but don't fail deployment) // const cdnInvalidationResult = await this.invalidateCdnCache(site, s3Key); return { - tokowakaApiKey, + tokowakaApiKey: apiKey, s3Key, config, // cdnInvalidation: cdnInvalidationResult, From 8e9b7e85a842bb31c77ff6f72ad0c7063ed7411d Mon Sep 17 00:00:00 2001 From: Divyansh Pratap Date: Thu, 16 Oct 2025 01:10:25 +0530 Subject: [PATCH 05/20] fix: cdn changes --- packages/spacecat-shared-tokowaka-client/src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/spacecat-shared-tokowaka-client/src/index.js b/packages/spacecat-shared-tokowaka-client/src/index.js index 8c53f09db..60680fe43 100644 --- a/packages/spacecat-shared-tokowaka-client/src/index.js +++ b/packages/spacecat-shared-tokowaka-client/src/index.js @@ -30,7 +30,7 @@ class TokowakaClient { */ static createFrom(context) { const { env, log = console, s3Client } = context; - const { TOKOWAKA_CONFIG_BUCKET: bucketName } = env; + const { TOKOWAKA_SITE_CONFIG_BUCKET: bucketName } = env || { TOKOWAKA_SITE_CONFIG_BUCKET: 'tokowaka-site-config' }; if (context.tokowakaClient) { return context.tokowakaClient; From 19b5641653b92404a187f43033bd2c13cae1f428 Mon Sep 17 00:00:00 2001 From: Divyansh Pratap Date: Thu, 16 Oct 2025 14:59:01 +0530 Subject: [PATCH 06/20] fix: cdn changes --- .../spacecat-shared-tokowaka-client/src/index.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/spacecat-shared-tokowaka-client/src/index.js b/packages/spacecat-shared-tokowaka-client/src/index.js index 60680fe43..8a3ac31ca 100644 --- a/packages/spacecat-shared-tokowaka-client/src/index.js +++ b/packages/spacecat-shared-tokowaka-client/src/index.js @@ -29,14 +29,15 @@ class TokowakaClient { * @returns {TokowakaClient} - The client instance */ static createFrom(context) { - const { env, log = console, s3Client } = context; - const { TOKOWAKA_SITE_CONFIG_BUCKET: bucketName } = env || { TOKOWAKA_SITE_CONFIG_BUCKET: 'tokowaka-site-config' }; + const { env, log = console, s3 } = context; + const { TOKOWAKA_SITE_CONFIG_BUCKET: bucketName } = env; if (context.tokowakaClient) { return context.tokowakaClient; } - const client = new TokowakaClient({ bucketName, s3Client }, log); + // s3ClientWrapper puts s3Client at context.s3.s3Client, so check both locations + const client = new TokowakaClient({ bucketName, s3Client: s3?.s3Client }, log); context.tokowakaClient = client; return client; } @@ -52,7 +53,7 @@ class TokowakaClient { this.log = log; if (!hasText(bucketName)) { - throw this.#createError('TOKOWAKA_CONFIG_BUCKET is required', HTTP_BAD_REQUEST); + throw this.#createError('TOKOWAKA_SITE_CONFIG_BUCKET is required', HTTP_BAD_REQUEST); } if (!isNonEmptyObject(s3Client)) { @@ -177,7 +178,7 @@ class TokowakaClient { throw this.#createError('Config object is required', HTTP_BAD_REQUEST); } - const s3Key = `${apiKey}/v1/tokowaka-site-config.json`; + const s3Key = `opportunities/${apiKey}-1`; try { const command = new PutObjectCommand({ @@ -258,7 +259,7 @@ class TokowakaClient { */ async deploySuggestions(site, opportunity, suggestions) { // Get site's Tokowaka API key - const { tokowakaConfig } = site.getConfig()?.getTokowakaConfig() || {}; + const tokowakaConfig = site.getConfig().getTokowakaConfig() || {}; const { apiKey } = tokowakaConfig || {}; if (!hasText(apiKey)) { From 82371f254c7702455f6a677ff21c2d57ebcb1eef Mon Sep 17 00:00:00 2001 From: Divyansh Pratap Date: Thu, 16 Oct 2025 16:12:02 +0530 Subject: [PATCH 07/20] fix: cdn changes --- packages/spacecat-shared-tokowaka-client/src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/spacecat-shared-tokowaka-client/src/index.js b/packages/spacecat-shared-tokowaka-client/src/index.js index 8a3ac31ca..2e5206876 100644 --- a/packages/spacecat-shared-tokowaka-client/src/index.js +++ b/packages/spacecat-shared-tokowaka-client/src/index.js @@ -178,7 +178,7 @@ class TokowakaClient { throw this.#createError('Config object is required', HTTP_BAD_REQUEST); } - const s3Key = `opportunities/${apiKey}-1`; + const s3Key = `opportunities/${apiKey}`; try { const command = new PutObjectCommand({ From 5b762d9780197328ae6edd5fa735e99c3e0b166e Mon Sep 17 00:00:00 2001 From: Divyansh Pratap Date: Fri, 17 Oct 2025 01:45:51 +0530 Subject: [PATCH 08/20] fix: finalize changes --- .../src/models/site/config.js | 10 +- .../.nycrc.json | 15 + .../package.json | 3 +- .../src/cdn/akamai-cdn-client.js | 180 ---------- .../src/cdn/base-cdn-client.js | 17 +- .../src/cdn/cdn-client-registry.js | 11 +- .../src/cdn/cloudfront-cdn-client.js | 129 +++++++ .../src/index.js | 67 ++-- .../src/mappers/base-mapper.js | 2 +- .../src/mappers/headings-mapper.js | 2 +- .../test/cdn/base-cdn-client.test.js | 51 +++ .../test/cdn/cdn-client-registry.test.js | 179 ++++++++++ .../test/cdn/cloudfront-cdn-client.test.js | 324 ++++++++++++++++++ .../test/index.test.js | 296 +++++++++++++++- .../test/mappers/base-mapper.test.js | 52 +++ .../test/mappers/headings-mapper.test.js | 189 ++++++++++ .../test/mappers/mapper-registry.test.js | 197 +++++++++++ .../test/setup-env.js | 9 +- 18 files changed, 1469 insertions(+), 264 deletions(-) create mode 100644 packages/spacecat-shared-tokowaka-client/.nycrc.json delete mode 100644 packages/spacecat-shared-tokowaka-client/src/cdn/akamai-cdn-client.js create mode 100644 packages/spacecat-shared-tokowaka-client/src/cdn/cloudfront-cdn-client.js create mode 100644 packages/spacecat-shared-tokowaka-client/test/cdn/base-cdn-client.test.js create mode 100644 packages/spacecat-shared-tokowaka-client/test/cdn/cdn-client-registry.test.js create mode 100644 packages/spacecat-shared-tokowaka-client/test/cdn/cloudfront-cdn-client.test.js create mode 100644 packages/spacecat-shared-tokowaka-client/test/mappers/base-mapper.test.js create mode 100644 packages/spacecat-shared-tokowaka-client/test/mappers/headings-mapper.test.js create mode 100644 packages/spacecat-shared-tokowaka-client/test/mappers/mapper-registry.test.js diff --git a/packages/spacecat-shared-data-access/src/models/site/config.js b/packages/spacecat-shared-data-access/src/models/site/config.js index e70330720..00638934b 100644 --- a/packages/spacecat-shared-data-access/src/models/site/config.js +++ b/packages/spacecat-shared-data-access/src/models/site/config.js @@ -307,15 +307,6 @@ export const configSchema = Joi.object({ }).optional(), tokowakaConfig: Joi.object({ apiKey: Joi.string().required(), - cdn: Joi.object({ - provider: Joi.string().required(), - config: Joi.object({ - client_token: Joi.string().required(), - client_secret: Joi.string().required(), - access_token: Joi.string().required(), - host: Joi.string().required(), - }).required(), - }).optional(), }).optional(), contentAiConfig: Joi.object({ index: Joi.string().optional(), @@ -688,4 +679,5 @@ Config.toDynamoItem = (config) => ({ brandConfig: config.getBrandConfig(), cdnLogsConfig: config.getCdnLogsConfig(), llmo: config.getLlmoConfig(), + tokowakaConfig: config.getTokowakaConfig(), }); diff --git a/packages/spacecat-shared-tokowaka-client/.nycrc.json b/packages/spacecat-shared-tokowaka-client/.nycrc.json new file mode 100644 index 000000000..4f95e2ebd --- /dev/null +++ b/packages/spacecat-shared-tokowaka-client/.nycrc.json @@ -0,0 +1,15 @@ +{ + "reporter": [ + "lcov", + "text" + ], + "check-coverage": true, + "lines": 100, + "branches": 100, + "statements": 100, + "all": true, + "include": [ + "src/**/*.js" + ] +} + diff --git a/packages/spacecat-shared-tokowaka-client/package.json b/packages/spacecat-shared-tokowaka-client/package.json index 5c9847fbb..0ddfda1a1 100644 --- a/packages/spacecat-shared-tokowaka-client/package.json +++ b/packages/spacecat-shared-tokowaka-client/package.json @@ -32,7 +32,8 @@ }, "dependencies": { "@adobe/spacecat-shared-utils": "*", - "@aws-sdk/client-s3": "^3.893.0" + "@aws-sdk/client-cloudfront": "3.893.0", + "@aws-sdk/client-s3": "3.893.0" }, "devDependencies": { "c8": "^10.1.3", diff --git a/packages/spacecat-shared-tokowaka-client/src/cdn/akamai-cdn-client.js b/packages/spacecat-shared-tokowaka-client/src/cdn/akamai-cdn-client.js deleted file mode 100644 index 1845a7031..000000000 --- a/packages/spacecat-shared-tokowaka-client/src/cdn/akamai-cdn-client.js +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright 2025 Adobe. All rights reserved. - * This file is licensed to you under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. You may obtain a copy - * of the License at http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under - * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - * OF ANY KIND, either express or implied. See the License for the specific language - * governing permissions and limitations under the License. - */ - -import crypto from 'crypto'; -import BaseCdnClient from './base-cdn-client.js'; - -/** - * Akamai CDN client implementation - * Handles cache invalidation (purge) for Akamai CDN - */ -export default class AkamaiCdnClient extends BaseCdnClient { - constructor(config, log) { - super(config, log); - this.baseUrl = config.baseUrl || 'https://akaa-baseurl-xxx-xxx.luna.akamaiapis.net'; - } - - // eslint-disable-next-line class-methods-use-this - getProviderName() { - return 'akamai'; - } - - validateConfig() { - const required = ['client_token', 'client_secret', 'access_token', 'host']; - const missing = required.filter((key) => !this.config[key]); - - if (missing.length > 0) { - this.log.error(`Akamai CDN config missing required fields: ${missing.join(', ')}`); - return false; - } - - return true; - } - - /** - * Generates Akamai EdgeGrid authentication header - * @param {string} method - HTTP method - * @param {string} path - API path - * @param {string} body - Request body - * @returns {string} Authorization header value - */ - generateAuthHeader(method, path, body = '') { - const timestamp = new Date().toISOString().replace(/\.\d{3}Z$/, '+0000'); - const nonce = crypto.randomBytes(16).toString('hex'); - - const authData = [ - `client_token=${this.config.clientToken}`, - `access_token=${this.config.accessToken}`, - `timestamp=${timestamp}`, - `nonce=${nonce}`, - ].join(';'); - - const bodyHash = body - ? crypto.createHash('sha256').update(body).digest('base64') - : ''; - - const dataToSign = [ - method.toUpperCase(), - 'https', - this.baseUrl.replace(/^https?:\/\//, ''), - path, - '', - bodyHash, - authData, - ].join('\t'); - - const signingKey = crypto - .createHmac('sha256', this.config.clientSecret) - .update(timestamp) - .digest(); - - const signature = crypto - .createHmac('sha256', signingKey) - .update(dataToSign) - .digest('base64'); - - return `EG1-HMAC-SHA256 ${authData};signature=${signature}`; - } - - /** - * Invalidates Akamai CDN cache for given paths - * @param {Array} paths - Array of URL paths to invalidate - * @returns {Promise} Result of the purge request - */ - async invalidateCache(paths) { - if (!this.validateConfig()) { - throw new Error('Invalid Akamai CDN configuration'); - } - - if (!Array.isArray(paths) || paths.length === 0) { - this.log.warn('No paths provided for cache invalidation'); - return { status: 'skipped', message: 'No paths to invalidate' }; - } - - const endpoint = '/ccu/v3/invalidate/url'; - const body = JSON.stringify({ - objects: paths, - }); - - try { - const authHeader = this.generateAuthHeader('POST', endpoint, body); - - const response = await fetch(`${this.baseUrl}${endpoint}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: authHeader, - }, - body, - }); - - if (!response.ok) { - const errorText = await response.text(); - throw new Error(`Akamai purge failed: ${response.status} - ${errorText}`); - } - - const result = await response.json(); - this.log.info(`Akamai cache invalidation initiated: ${result.purgeId || result.estimatedSeconds || 'success'}`); - - return { - status: 'success', - provider: 'akamai', - purgeId: result.purgeId, - estimatedSeconds: result.estimatedSeconds, - paths: paths.length, - }; - } catch (error) { - this.log.error(`Failed to invalidate Akamai cache: ${error.message}`, error); - throw error; - } - } - - /** - * Checks the status of an Akamai purge request - * @param {string} purgeId - The purge request ID - * @returns {Promise} Status of the purge request - */ - async getInvalidationStatus(purgeId) { - if (!this.validateConfig()) { - throw new Error('Invalid Akamai CDN configuration'); - } - - const endpoint = `/ccu/v3/purges/${purgeId}`; - - try { - const authHeader = this.generateAuthHeader('GET', endpoint); - - const response = await fetch(`${this.baseUrl}${endpoint}`, { - method: 'GET', - headers: { - Authorization: authHeader, - }, - }); - - if (!response.ok) { - const errorText = await response.text(); - throw new Error(`Failed to get purge status: ${response.status} - ${errorText}`); - } - - const result = await response.json(); - return { - status: result.purgeStatus || 'unknown', - provider: 'akamai', - purgeId, - ...result, - }; - } catch (error) { - this.log.error(`Failed to get Akamai purge status: ${error.message}`, error); - throw error; - } - } -} diff --git a/packages/spacecat-shared-tokowaka-client/src/cdn/base-cdn-client.js b/packages/spacecat-shared-tokowaka-client/src/cdn/base-cdn-client.js index 3724b194f..a8351b9bc 100644 --- a/packages/spacecat-shared-tokowaka-client/src/cdn/base-cdn-client.js +++ b/packages/spacecat-shared-tokowaka-client/src/cdn/base-cdn-client.js @@ -15,9 +15,9 @@ * Defines the interface that all CDN-specific clients must implement */ export default class BaseCdnClient { - constructor(config, log) { - this.config = config; - this.log = log; + constructor(env, log) { + this.env = env; + this.log = log || console; } /** @@ -47,15 +47,4 @@ export default class BaseCdnClient { async invalidateCache(_paths) { throw new Error('invalidateCache() must be implemented by subclass'); } - - /** - * Checks the status of an invalidation request - * @param {string} requestId - The invalidation request ID - * @returns {Promise} Status of the invalidation request - */ - // eslint-disable-next-line class-methods-use-this, no-unused-vars - async getInvalidationStatus(_requestId) { - // Optional: Override in subclass if supported - return { status: 'unknown', message: 'Status check not supported' }; - } } diff --git a/packages/spacecat-shared-tokowaka-client/src/cdn/cdn-client-registry.js b/packages/spacecat-shared-tokowaka-client/src/cdn/cdn-client-registry.js index e85b8e467..dc8bf81f9 100644 --- a/packages/spacecat-shared-tokowaka-client/src/cdn/cdn-client-registry.js +++ b/packages/spacecat-shared-tokowaka-client/src/cdn/cdn-client-registry.js @@ -10,14 +10,15 @@ * governing permissions and limitations under the License. */ -import AkamaiCdnClient from './akamai-cdn-client.js'; +import CloudFrontCdnClient from './cloudfront-cdn-client.js'; /** * Registry for CDN clients * Manages different CDN provider implementations */ export default class CdnClientRegistry { - constructor(log) { + constructor(env, log) { + this.env = env; this.log = log; this.clients = new Map(); this.#registerDefaultClients(); @@ -28,7 +29,7 @@ export default class CdnClientRegistry { * @private */ #registerDefaultClients() { - this.registerClient('akamai', AkamaiCdnClient); + this.registerClient('cloudfront', CloudFrontCdnClient); } /** @@ -46,7 +47,7 @@ export default class CdnClientRegistry { * @param {Object} config - CDN configuration * @returns {BaseCdnClient|null} CDN client instance or null if not found */ - getClient(provider, config) { + getClient(provider) { if (!provider) { this.log.warn('No CDN provider specified'); return null; @@ -60,7 +61,7 @@ export default class CdnClientRegistry { } try { - return new ClientClass(config, this.log); + return new ClientClass(this.env, this.log); } catch (error) { this.log.error(`Failed to create CDN client for ${provider}: ${error.message}`, error); return null; diff --git a/packages/spacecat-shared-tokowaka-client/src/cdn/cloudfront-cdn-client.js b/packages/spacecat-shared-tokowaka-client/src/cdn/cloudfront-cdn-client.js new file mode 100644 index 000000000..e0cb8e20d --- /dev/null +++ b/packages/spacecat-shared-tokowaka-client/src/cdn/cloudfront-cdn-client.js @@ -0,0 +1,129 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import { + CloudFrontClient, + CreateInvalidationCommand, +} from '@aws-sdk/client-cloudfront'; +import BaseCdnClient from './base-cdn-client.js'; + +/** + * CloudFront CDN client implementation + * Handles cache invalidation for AWS CloudFront + */ +export default class CloudFrontCdnClient extends BaseCdnClient { + constructor(env, log) { + super(env, log); + let parsedConfig = {}; + try { + parsedConfig = JSON.parse(env.TOKOWAKA_CDN_CONFIG); + } catch (e) { + throw new Error('Invalid TOKOWAKA_CDN_CONFIG: must be valid JSON'); + } + + if (!parsedConfig.cloudfront) { + throw new Error("Missing 'cloudfront' config in TOKOWAKA_CDN_CONFIG"); + } + + this.cdnConfig = parsedConfig.cloudfront; + this.client = null; + } + + // eslint-disable-next-line class-methods-use-this + getProviderName() { + return 'cloudfront'; + } + + validateConfig() { + // Only distributionId is required - credentials are optional when running on Lambda + if (!this.cdnConfig.distributionId || !this.cdnConfig.region) { + this.log.error('CloudFront CDN config missing required fields: distributionId and region'); + return false; + } + + return true; + } + + /** + * Initializes the CloudFront client + * @private + */ + #initializeClient() { + /* c8 ignore start */ + if (!this.client) { + this.client = new CloudFrontClient({ + region: this.cdnConfig.region, + }); + } + /* c8 ignore stop */ + } + + /** + * Invalidates CloudFront CDN cache for given paths + * @param {Array} paths - Array of URL paths to invalidate + * @returns {Promise} Result of the invalidation request + */ + async invalidateCache(paths) { + if (!this.validateConfig()) { + throw new Error('Invalid CloudFront CDN configuration'); + } + + if (!Array.isArray(paths) || paths.length === 0) { + this.log.warn('No paths provided for cache invalidation'); + return { status: 'skipped', message: 'No paths to invalidate' }; + } + + this.#initializeClient(); + + // CloudFront requires paths to start with '/' + const formattedPaths = paths.map((path) => { + if (!path.startsWith('/')) { + return `/${path}`; + } + return path; + }); + + const callerReference = `tokowaka-${Date.now()}`; + + const command = new CreateInvalidationCommand({ + DistributionId: this.cdnConfig.distributionId, + InvalidationBatch: { + CallerReference: callerReference, + Paths: { + Quantity: formattedPaths.length, + Items: formattedPaths, + }, + }, + }); + + try { + this.log.debug(`Initiating CloudFront cache invalidation for ${JSON.stringify(formattedPaths)} paths`); + + const response = await this.client.send(command); + const invalidation = response.Invalidation; + + this.log.info(`CloudFront cache invalidation initiated: ${invalidation.Id}`); + + return { + status: 'success', + provider: 'cloudfront', + invalidationId: invalidation.Id, + invalidationStatus: invalidation.Status, + createTime: invalidation.CreateTime, + paths: formattedPaths.length, + }; + } catch (error) { + this.log.error(`Failed to invalidate CloudFront cache: ${error.message}`, error); + throw error; + } + } +} diff --git a/packages/spacecat-shared-tokowaka-client/src/index.js b/packages/spacecat-shared-tokowaka-client/src/index.js index 2e5206876..0387aae6f 100644 --- a/packages/spacecat-shared-tokowaka-client/src/index.js +++ b/packages/spacecat-shared-tokowaka-client/src/index.js @@ -37,7 +37,11 @@ class TokowakaClient { } // s3ClientWrapper puts s3Client at context.s3.s3Client, so check both locations - const client = new TokowakaClient({ bucketName, s3Client: s3?.s3Client }, log); + const client = new TokowakaClient({ + bucketName, + s3Client: s3?.s3Client, + env, + }, log); context.tokowakaClient = client; return client; } @@ -47,9 +51,10 @@ class TokowakaClient { * @param {Object} config - Configuration object * @param {string} config.bucketName - S3 bucket name for configs * @param {Object} config.s3Client - AWS S3 client + * @param {Object} config.env - Environment variables (for CDN credentials) * @param {Object} log - Logger instance */ - constructor({ bucketName, s3Client }, log) { + constructor({ bucketName, s3Client, env = {} }, log) { this.log = log; if (!hasText(bucketName)) { @@ -62,9 +67,10 @@ class TokowakaClient { this.bucketName = bucketName; this.s3Client = s3Client; + this.env = env; this.mapperRegistry = new MapperRegistry(log); - this.cdnClientRegistry = new CdnClientRegistry(log); + this.cdnClientRegistry = new CdnClientRegistry(env, log); } #createError(message, status) { @@ -200,51 +206,30 @@ class TokowakaClient { /** * Invalidates CDN cache for the Tokowaka config - * @param {Object} site - Site entity - * @param {string} _ - S3 key of the uploaded config + * Currently supports CloudFront only + * @param {string} apiKey - Tokowaka API key + * @param {string} provider - CDN provider name (default: 'cloudfront') * @returns {Promise} - CDN invalidation result or null if skipped */ - async invalidateCdnCache(site, _) { - const siteConfig = site.getConfig() || {}; - const { cdn } = siteConfig; - - if (!isNonEmptyObject(cdn)) { - this.log.info('No CDN configuration found for site, skipping cache invalidation'); - return null; - } - - const { provider, config: cdnConfig } = cdn; - - if (!hasText(provider) || !cdnConfig) { - this.log.warn('CDN provider or config not specified in site config, skipping cache invalidation'); - return null; + async invalidateCdnCache(apiKey, provider) { + if (!hasText(apiKey) || !hasText(provider)) { + throw this.#createError('Tokowaka API key and provider are required', HTTP_BAD_REQUEST); } - try { - const cdnClient = this.cdnClientRegistry.getClient(provider, cdnConfig); - + const pathsToInvalidate = [`/opportunities/${apiKey}`]; + this.log.debug(`Invalidating CDN cache for ${pathsToInvalidate.length} paths via ${provider}`); + const cdnClient = this.cdnClientRegistry.getClient(provider); if (!cdnClient) { - this.log.warn(`No CDN client available for provider: ${provider}, skipping cache invalidation`); - return null; + throw this.#createError(`No CDN client available for provider: ${provider}`, HTTP_NOT_IMPLEMENTED); } - - // Build CDN paths to invalidate - // The config is accessed via the Tokowaka API key path - const pathsToInvalidate = [ - `/opportunities/${siteConfig.getTokowakaConfig().apiKey}`, - ]; - - this.log.debug(`Invalidating CDN cache for ${pathsToInvalidate.length} paths via ${provider}`); const result = await cdnClient.invalidateCache(pathsToInvalidate); - this.log.info(`CDN cache invalidation completed: ${JSON.stringify(result)}`); return result; } catch (error) { - // Log error but don't fail the deployment - this.log.error(`CDN cache invalidation failed (non-fatal): ${error.message}`, error); + this.log.error(`Failed to invalidate CDN cache: ${error.message}`, error); return { status: 'error', - provider, + provider: 'cloudfront', message: error.message, }; } @@ -259,8 +244,7 @@ class TokowakaClient { */ async deploySuggestions(site, opportunity, suggestions) { // Get site's Tokowaka API key - const tokowakaConfig = site.getConfig().getTokowakaConfig() || {}; - const { apiKey } = tokowakaConfig || {}; + const { apiKey } = site.getConfig().getTokowakaConfig() || {}; if (!hasText(apiKey)) { throw this.#createError( @@ -318,13 +302,16 @@ class TokowakaClient { const s3Key = await this.uploadConfig(apiKey, config); // Invalidate CDN cache (non-blocking, failures are logged but don't fail deployment) - // const cdnInvalidationResult = await this.invalidateCdnCache(site, s3Key); + const cdnInvalidationResult = await this.invalidateCdnCache( + apiKey, + this.env.TOKOWAKA_CDN_PROVIDER, + ); return { tokowakaApiKey: apiKey, s3Key, config, - // cdnInvalidation: cdnInvalidationResult, + cdnInvalidation: cdnInvalidationResult, succeededSuggestions: eligibleSuggestions, failedSuggestions: ineligibleSuggestions, }; diff --git a/packages/spacecat-shared-tokowaka-client/src/mappers/base-mapper.js b/packages/spacecat-shared-tokowaka-client/src/mappers/base-mapper.js index 0fa98db82..0a2df28cc 100644 --- a/packages/spacecat-shared-tokowaka-client/src/mappers/base-mapper.js +++ b/packages/spacecat-shared-tokowaka-client/src/mappers/base-mapper.js @@ -58,7 +58,7 @@ export default class BaseOpportunityMapper { */ // eslint-disable-next-line class-methods-use-this, no-unused-vars validateSuggestionData(_) { - return true; // Override in subclass if needed + return false; // Override in subclass if needed } /** diff --git a/packages/spacecat-shared-tokowaka-client/src/mappers/headings-mapper.js b/packages/spacecat-shared-tokowaka-client/src/mappers/headings-mapper.js index 04f13ff59..951b95619 100644 --- a/packages/spacecat-shared-tokowaka-client/src/mappers/headings-mapper.js +++ b/packages/spacecat-shared-tokowaka-client/src/mappers/headings-mapper.js @@ -68,7 +68,7 @@ export default class HeadingsMapper extends BaseOpportunityMapper { if (checkType !== 'heading-empty') { return { eligible: false, - reason: `Only empty headings can be deployed. This suggestion has checkType: ${checkType || 'unknown'}`, + reason: `Only empty headings can be deployed. This suggestion has checkType: ${checkType}`, }; } diff --git a/packages/spacecat-shared-tokowaka-client/test/cdn/base-cdn-client.test.js b/packages/spacecat-shared-tokowaka-client/test/cdn/base-cdn-client.test.js new file mode 100644 index 000000000..4c4a30709 --- /dev/null +++ b/packages/spacecat-shared-tokowaka-client/test/cdn/base-cdn-client.test.js @@ -0,0 +1,51 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +/* eslint-env mocha */ + +import { expect } from 'chai'; +import BaseCdnClient from '../../src/cdn/base-cdn-client.js'; + +describe('BaseCdnClient', () => { + let client; + + beforeEach(() => { + client = new BaseCdnClient({}, console); + }); + + describe('constructor', () => { + it('should use console as default logger if log is not provided', () => { + const clientWithoutLog = new BaseCdnClient({}); + expect(clientWithoutLog.log).to.equal(console); + }); + }); + + describe('abstract methods', () => { + it('getProviderName should throw error', () => { + expect(() => client.getProviderName()) + .to.throw('getProviderName() must be implemented by subclass'); + }); + + it('validateConfig should return true by default', () => { + expect(client.validateConfig()).to.be.true; + }); + + it('invalidateCache should throw error', async () => { + try { + await client.invalidateCache(['/test']); + expect.fail('Should have thrown error'); + } catch (error) { + expect(error.message).to.equal('invalidateCache() must be implemented by subclass'); + } + }); + }); +}); diff --git a/packages/spacecat-shared-tokowaka-client/test/cdn/cdn-client-registry.test.js b/packages/spacecat-shared-tokowaka-client/test/cdn/cdn-client-registry.test.js new file mode 100644 index 000000000..6b6d7aa43 --- /dev/null +++ b/packages/spacecat-shared-tokowaka-client/test/cdn/cdn-client-registry.test.js @@ -0,0 +1,179 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +/* eslint-env mocha */ +/* eslint-disable max-classes-per-file */ + +import { expect, use } from 'chai'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import CdnClientRegistry from '../../src/cdn/cdn-client-registry.js'; +import CloudFrontCdnClient from '../../src/cdn/cloudfront-cdn-client.js'; +import BaseCdnClient from '../../src/cdn/base-cdn-client.js'; + +use(sinonChai); + +describe('CdnClientRegistry', () => { + let registry; + let log; + let env; + + beforeEach(() => { + log = { + info: sinon.stub(), + warn: sinon.stub(), + error: sinon.stub(), + }; + + env = { + TOKOWAKA_CDN_CONFIG: JSON.stringify({ + cloudfront: { + distributionId: 'E123456', + region: 'us-east-1', + }, + }), + }; + + registry = new CdnClientRegistry(env, log); + }); + + afterEach(() => { + sinon.restore(); + }); + + describe('constructor', () => { + it('should create an instance and register default clients', () => { + expect(registry).to.be.instanceOf(CdnClientRegistry); + expect(registry.clients).to.be.instanceOf(Map); + expect(registry.clients.size).to.be.greaterThan(0); + }); + }); + + describe('registerClient', () => { + it('should register a custom CDN client', () => { + class CustomCdnClient extends BaseCdnClient {} + + registry.registerClient('custom', CustomCdnClient); + + expect(registry.clients.has('custom')).to.be.true; + expect(registry.getSupportedProviders()).to.include('custom'); + }); + + it('should register client with case-insensitive provider name', () => { + class CustomCdnClient extends BaseCdnClient {} + + registry.registerClient('CUSTOM', CustomCdnClient); + + expect(registry.clients.has('custom')).to.be.true; + }); + }); + + describe('getClient', () => { + it('should return CloudFront client for cloudfront provider', () => { + const client = registry.getClient('cloudfront'); + + expect(client).to.be.instanceOf(CloudFrontCdnClient); + expect(client.cdnConfig).to.deep.equal({ + distributionId: 'E123456', + region: 'us-east-1', + }); + }); + + it('should be case-insensitive for provider names', () => { + const client = registry.getClient('CloudFront'); + + expect(client).to.be.instanceOf(CloudFrontCdnClient); + }); + + it('should return null if provider is not specified', () => { + const client = registry.getClient(''); + + expect(client).to.be.null; + expect(log.warn).to.have.been.calledWith('No CDN provider specified'); + }); + + it('should return null if provider is null', () => { + const client = registry.getClient(null); + + expect(client).to.be.null; + expect(log.warn).to.have.been.calledWith('No CDN provider specified'); + }); + + it('should return null for unsupported provider', () => { + const client = registry.getClient('unsupported-provider'); + + expect(client).to.be.null; + expect(log.warn).to.have.been.calledWith( + 'No CDN client found for provider: unsupported-provider', + ); + }); + + it('should handle client creation errors gracefully', () => { + class FailingCdnClient extends BaseCdnClient { + constructor() { + throw new Error('Construction failed'); + } + } + + registry.registerClient('failing', FailingCdnClient); + + const client = registry.getClient('failing'); + + expect(client).to.be.null; + expect(log.error).to.have.been.calledWith( + sinon.match(/Failed to create CDN client for failing/), + ); + }); + }); + + describe('getSupportedProviders', () => { + it('should return list of supported providers', () => { + const providers = registry.getSupportedProviders(); + + expect(providers).to.be.an('array'); + expect(providers).to.include('cloudfront'); + }); + + it('should include custom registered providers', () => { + class CustomCdnClient extends BaseCdnClient {} + + registry.registerClient('custom', CustomCdnClient); + + const providers = registry.getSupportedProviders(); + + expect(providers).to.include('custom'); + expect(providers).to.include('cloudfront'); + }); + }); + + describe('isProviderSupported', () => { + it('should return true for supported provider', () => { + expect(registry.isProviderSupported('cloudfront')).to.be.true; + }); + + it('should return true for supported provider (case-insensitive)', () => { + expect(registry.isProviderSupported('CloudFront')).to.be.true; + }); + + it('should return false for unsupported provider', () => { + expect(registry.isProviderSupported('unsupported')).to.be.false; + }); + + it('should return false for null provider', () => { + expect(registry.isProviderSupported(null)).to.be.false; + }); + + it('should return false for undefined provider', () => { + expect(registry.isProviderSupported(undefined)).to.be.false; + }); + }); +}); diff --git a/packages/spacecat-shared-tokowaka-client/test/cdn/cloudfront-cdn-client.test.js b/packages/spacecat-shared-tokowaka-client/test/cdn/cloudfront-cdn-client.test.js new file mode 100644 index 000000000..08a3386f0 --- /dev/null +++ b/packages/spacecat-shared-tokowaka-client/test/cdn/cloudfront-cdn-client.test.js @@ -0,0 +1,324 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +/* eslint-env mocha */ + +import { expect, use } from 'chai'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import CloudFrontCdnClient from '../../src/cdn/cloudfront-cdn-client.js'; + +use(sinonChai); + +describe('CloudFrontCdnClient', () => { + let client; + let log; + let mockCloudFrontClient; + + beforeEach(() => { + log = { + info: sinon.stub(), + warn: sinon.stub(), + error: sinon.stub(), + debug: sinon.stub(), + }; + + mockCloudFrontClient = { + send: sinon.stub(), + }; + }); + + afterEach(() => { + sinon.restore(); + }); + + describe('constructor', () => { + it('should throw error for invalid JSON in TOKOWAKA_CDN_CONFIG', () => { + const env = { + TOKOWAKA_CDN_CONFIG: 'invalid-json{', + }; + + expect(() => new CloudFrontCdnClient(env, log)) + .to.throw('Invalid TOKOWAKA_CDN_CONFIG: must be valid JSON'); + }); + + it('should throw error when cloudfront config is missing', () => { + const env = { + TOKOWAKA_CDN_CONFIG: JSON.stringify({ + someOtherProvider: {}, + }), + }; + + expect(() => new CloudFrontCdnClient(env, log)) + .to.throw("Missing 'cloudfront' config in TOKOWAKA_CDN_CONFIG"); + }); + }); + + describe('getProviderName', () => { + it('should return cloudfront', () => { + const env = { + TOKOWAKA_CDN_CONFIG: JSON.stringify({ + cloudfront: { + distributionId: 'E123456', + region: 'us-east-1', + }, + }), + }; + client = new CloudFrontCdnClient(env, log); + + expect(client.getProviderName()).to.equal('cloudfront'); + }); + }); + + describe('validateConfig', () => { + it('should return true for valid config with only distributionId', () => { + const env = { + TOKOWAKA_CDN_CONFIG: JSON.stringify({ + cloudfront: { + distributionId: 'E123456', + region: 'us-east-1', + }, + }), + }; + client = new CloudFrontCdnClient(env, log); + + expect(client.validateConfig()).to.be.true; + }); + + it('should return true for valid config with credentials', () => { + const env = { + TOKOWAKA_CDN_CONFIG: JSON.stringify({ + cloudfront: { + distributionId: 'E123456', + region: 'us-east-1', + accessKeyId: 'AKIAIOSFODNN7EXAMPLE', + secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', + }, + }), + }; + client = new CloudFrontCdnClient(env, log); + + expect(client.validateConfig()).to.be.true; + }); + + it('should return false if distributionId is missing', () => { + const env = { + TOKOWAKA_CDN_CONFIG: JSON.stringify({ + cloudfront: { + region: 'us-east-1', + }, + }), + }; + client = new CloudFrontCdnClient(env, log); + + const result = client.validateConfig(); + + expect(result).to.be.false; + expect(log.error).to.have.been.calledWith( + 'CloudFront CDN config missing required fields: distributionId and region', + ); + }); + }); + + describe('invalidateCache', () => { + beforeEach(() => { + const env = { + TOKOWAKA_CDN_CONFIG: JSON.stringify({ + cloudfront: { + distributionId: 'E123456', + region: 'us-east-1', + accessKeyId: 'AKIAIOSFODNN7EXAMPLE', + secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', + }, + }), + }; + client = new CloudFrontCdnClient(env, log); + // Mock the internal client + client.client = mockCloudFrontClient; + }); + + it('should invalidate cache successfully', async () => { + const mockResponse = { + Invalidation: { + Id: 'I2J4EXAMPLE', + Status: 'InProgress', + CreateTime: new Date('2025-01-15T10:30:00.000Z'), + }, + }; + mockCloudFrontClient.send.resolves(mockResponse); + + const paths = ['/path1', '/path2']; + const result = await client.invalidateCache(paths); + + expect(result).to.deep.include({ + status: 'success', + provider: 'cloudfront', + invalidationId: 'I2J4EXAMPLE', + invalidationStatus: 'InProgress', + createTime: mockResponse.Invalidation.CreateTime, + paths: 2, + }); + + expect(mockCloudFrontClient.send).to.have.been.calledOnce; + expect(log.debug).to.have.been.calledWith(sinon.match(/Initiating CloudFront cache invalidation/)); + expect(log.info).to.have.been.calledWith(sinon.match(/CloudFront cache invalidation initiated/)); + }); + + it('should format paths to start with /', async () => { + const mockResponse = { + Invalidation: { + Id: 'I2J4EXAMPLE', + Status: 'InProgress', + CreateTime: new Date(), + }, + }; + mockCloudFrontClient.send.resolves(mockResponse); + + const paths = ['path1', '/path2', 'path3']; + await client.invalidateCache(paths); + + const command = mockCloudFrontClient.send.firstCall.args[0]; + expect(command.input.InvalidationBatch.Paths.Items).to.deep.equal([ + '/path1', + '/path2', + '/path3', + ]); + }); + + it('should throw error if config is invalid', async () => { + client.cdnConfig = {}; + + try { + await client.invalidateCache(['/path1']); + expect.fail('Should have thrown error'); + } catch (error) { + expect(error.message).to.equal('Invalid CloudFront CDN configuration'); + } + }); + + it('should return skipped result if paths array is empty', async () => { + const result = await client.invalidateCache([]); + + expect(result).to.deep.equal({ + status: 'skipped', + message: 'No paths to invalidate', + }); + expect(log.warn).to.have.been.calledWith('No paths provided for cache invalidation'); + expect(mockCloudFrontClient.send).to.not.have.been.called; + }); + + it('should return skipped result if paths is not an array', async () => { + const result = await client.invalidateCache(null); + + expect(result).to.deep.equal({ + status: 'skipped', + message: 'No paths to invalidate', + }); + expect(log.warn).to.have.been.calledWith('No paths provided for cache invalidation'); + }); + + it('should throw error on CloudFront API failure', async () => { + mockCloudFrontClient.send.rejects(new Error('CloudFront API error')); + + try { + await client.invalidateCache(['/path1']); + expect.fail('Should have thrown error'); + } catch (error) { + expect(error.message).to.include('CloudFront API error'); + expect(log.error).to.have.been.calledWith(sinon.match(/Failed to invalidate CloudFront cache/)); + } + }); + }); + + describe('client initialization', () => { + it('should initialize client with explicit credentials', async () => { + const env = { + TOKOWAKA_CDN_CONFIG: JSON.stringify({ + cloudfront: { + distributionId: 'E123456', + region: 'us-west-2', + accessKeyId: 'AKIAIOSFODNN7EXAMPLE', + secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', + sessionToken: 'SESSION_TOKEN', + }, + }), + }; + client = new CloudFrontCdnClient(env, log); + + // Client should be null initially + expect(client.client).to.be.null; + + // Mock CloudFront client for this test + client.client = mockCloudFrontClient; + mockCloudFrontClient.send.resolves({ + Invalidation: { + Id: 'I123', + Status: 'InProgress', + CreateTime: new Date(), + }, + }); + + await client.invalidateCache(['/test']); + + // Verify it was called + expect(mockCloudFrontClient.send).to.have.been.called; + }); + + it('should initialize client without credentials (Lambda role)', async () => { + const env = { + TOKOWAKA_CDN_CONFIG: JSON.stringify({ + cloudfront: { + distributionId: 'E123456', + region: 'us-east-1', + }, + }), + }; + client = new CloudFrontCdnClient(env, log); + + // Client should be null initially + expect(client.client).to.be.null; + + // Mock for test + client.client = mockCloudFrontClient; + mockCloudFrontClient.send.resolves({ + Invalidation: { + Id: 'I123', + Status: 'InProgress', + CreateTime: new Date(), + }, + }); + + await client.invalidateCache(['/test']); + + expect(mockCloudFrontClient.send).to.have.been.called; + }); + + it('should lazy-initialize CloudFront client on first use', () => { + const env = { + TOKOWAKA_CDN_CONFIG: JSON.stringify({ + cloudfront: { + distributionId: 'E123456', + region: 'us-east-1', + }, + }), + }; + client = new CloudFrontCdnClient(env, log); + + // Client should be null initially - it's lazy-initialized on first use + expect(client.client).to.be.null; + + // The #initializeClient() method is called internally by invalidateCache() + // and getInvalidationStatus(), which we test in other test cases. + // Those tests verify the client gets created when needed. + }); + }); +}); diff --git a/packages/spacecat-shared-tokowaka-client/test/index.test.js b/packages/spacecat-shared-tokowaka-client/test/index.test.js index 5e8b277af..82b3d00f6 100644 --- a/packages/spacecat-shared-tokowaka-client/test/index.test.js +++ b/packages/spacecat-shared-tokowaka-client/test/index.test.js @@ -36,17 +36,30 @@ describe('TokowakaClient', () => { info: sinon.stub(), warn: sinon.stub(), error: sinon.stub(), + debug: sinon.stub(), + }; + + const env = { + TOKOWAKA_CDN_PROVIDER: 'cloudfront', + TOKOWAKA_CDN_CONFIG: JSON.stringify({ + cloudfront: { + distributionId: 'E123456', + region: 'us-east-1', + }, + }), }; client = new TokowakaClient( - { bucketName: 'test-bucket', s3Client }, + { bucketName: 'test-bucket', s3Client, env }, log, ); mockSite = { getId: () => 'site-123', getBaseURL: () => 'https://example.com', - getConfig: () => ({ tokowakaApiKey: 'test-api-key-123' }), + getConfig: () => ({ + getTokowakaConfig: () => ({ apiKey: 'test-api-key-123' }), + }), }; mockOpportunity = { @@ -89,7 +102,7 @@ describe('TokowakaClient', () => { it('should throw error if bucketName is missing', () => { expect(() => new TokowakaClient({ s3Client }, log)) - .to.throw('TOKOWAKA_CONFIG_BUCKET is required'); + .to.throw('TOKOWAKA_SITE_CONFIG_BUCKET is required'); }); it('should throw error if s3Client is missing', () => { @@ -101,8 +114,8 @@ describe('TokowakaClient', () => { describe('createFrom', () => { it('should create client from context', () => { const context = { - env: { TOKOWAKA_CONFIG_BUCKET: 'test-bucket' }, - s3Client, + env: { TOKOWAKA_SITE_CONFIG_BUCKET: 'test-bucket' }, + s3: { s3Client }, log, }; @@ -118,8 +131,8 @@ describe('TokowakaClient', () => { log, ); const context = { - env: { TOKOWAKA_CONFIG_BUCKET: 'test-bucket' }, - s3Client, + env: { TOKOWAKA_SITE_CONFIG_BUCKET: 'test-bucket' }, + s3: { s3Client }, log, tokowakaClient: existingClient, }; @@ -130,6 +143,52 @@ describe('TokowakaClient', () => { }); }); + describe('getSupportedOpportunityTypes', () => { + it('should return list of supported opportunity types', () => { + const types = client.getSupportedOpportunityTypes(); + + expect(types).to.be.an('array'); + expect(types).to.include('headings'); + }); + }); + + describe('registerMapper', () => { + it('should register a custom mapper', () => { + class CustomMapper { + // eslint-disable-next-line class-methods-use-this + getOpportunityType() { + return 'custom-type'; + } + + // eslint-disable-next-line class-methods-use-this + requiresPrerender() { + return false; + } + + // eslint-disable-next-line class-methods-use-this + suggestionToPatch() { + return {}; + } + + // eslint-disable-next-line class-methods-use-this + validateSuggestionData() { + return true; + } + + // eslint-disable-next-line class-methods-use-this + canDeploy() { + return { eligible: true }; + } + } + + const customMapper = new CustomMapper(); + client.registerMapper(customMapper); + + const types = client.getSupportedOpportunityTypes(); + expect(types).to.include('custom-type'); + }); + }); + describe('generateConfig', () => { it('should generate config for headings opportunity', () => { const config = client.generateConfig(mockSite, mockOpportunity, mockSuggestions); @@ -265,12 +324,12 @@ describe('TokowakaClient', () => { const s3Key = await client.uploadConfig('test-api-key', config); - expect(s3Key).to.equal('test-api-key/v1/tokowaka-site-config.json'); + expect(s3Key).to.equal('opportunities/test-api-key'); expect(s3Client.send).to.have.been.calledOnce; const command = s3Client.send.firstCall.args[0]; expect(command.input.Bucket).to.equal('test-bucket'); - expect(command.input.Key).to.equal('test-api-key/v1/tokowaka-site-config.json'); + expect(command.input.Key).to.equal('opportunities/test-api-key'); expect(command.input.ContentType).to.equal('application/json'); expect(JSON.parse(command.input.Body)).to.deep.equal(config); }); @@ -312,6 +371,15 @@ describe('TokowakaClient', () => { }); describe('deploySuggestions', () => { + beforeEach(() => { + // Stub CDN invalidation for deploy tests + sinon.stub(client, 'invalidateCdnCache').resolves({ + status: 'success', + provider: 'cloudfront', + invalidationId: 'I123', + }); + }); + it('should deploy suggestions successfully', async () => { const result = await client.deploySuggestions( mockSite, @@ -320,14 +388,16 @@ describe('TokowakaClient', () => { ); expect(result).to.have.property('tokowakaApiKey', 'test-api-key-123'); - expect(result).to.have.property('s3Key', 'test-api-key-123/v1/tokowaka-site-config.json'); + expect(result).to.have.property('s3Key', 'opportunities/test-api-key-123'); expect(result).to.have.property('config'); expect(result.config.siteId).to.equal('site-123'); expect(s3Client.send).to.have.been.calledOnce; }); it('should throw error if site does not have Tokowaka API key', async () => { - mockSite.getConfig = () => ({}); + mockSite.getConfig = () => ({ + getTokowakaConfig: () => ({}), + }); try { await client.deploySuggestions(mockSite, mockOpportunity, mockSuggestions); @@ -345,5 +415,209 @@ describe('TokowakaClient', () => { expect(log.info).to.have.been.calledWith(sinon.match(/Uploading Tokowaka config/)); expect(log.info).to.have.been.calledWith(sinon.match(/Successfully uploaded/)); }); + + it('should handle suggestions that are not eligible for deployment', async () => { + // Create suggestions with different checkTypes + mockSuggestions = [ + { + getId: () => 'sugg-1', + getData: () => ({ + url: 'https://example.com/page1', + headingTag: 'h1', + recommendedAction: 'New Heading', + checkType: 'heading-missing', // Not eligible + }), + }, + { + getId: () => 'sugg-2', + getData: () => ({ + url: 'https://example.com/page1', + headingTag: 'h2', + recommendedAction: 'New Subtitle', + checkType: 'heading-empty', // Eligible + }), + }, + ]; + + const result = await client.deploySuggestions( + mockSite, + mockOpportunity, + mockSuggestions, + ); + + expect(result.succeededSuggestions).to.have.length(1); + expect(result.failedSuggestions).to.have.length(1); + expect(result.failedSuggestions[0].reason).to.include('Only empty headings can be deployed'); + }); + + it('should return early when no eligible suggestions', async () => { + // All suggestions are ineligible + mockSuggestions = [ + { + getId: () => 'sugg-1', + getData: () => ({ + url: 'https://example.com/page1', + headingTag: 'h1', + recommendedAction: 'New Heading', + checkType: 'heading-missing', + }), + }, + ]; + + const result = await client.deploySuggestions( + mockSite, + mockOpportunity, + mockSuggestions, + ); + + expect(result.s3Key).to.be.null; + expect(result.config).to.be.null; + expect(result.succeededSuggestions).to.have.length(0); + expect(result.failedSuggestions).to.have.length(1); + expect(log.warn).to.have.been.calledWith('No eligible suggestions to deploy'); + expect(s3Client.send).to.not.have.been.called; + }); + + it('should throw error for unsupported opportunity type', async () => { + mockOpportunity.getType = () => 'unsupported-type'; + + try { + await client.deploySuggestions(mockSite, mockOpportunity, mockSuggestions); + expect.fail('Should have thrown error'); + } catch (error) { + expect(error.message).to.include('No mapper found for opportunity type: unsupported-type'); + expect(error.message).to.include('Supported types:'); + expect(error.status).to.equal(501); + } + }); + + it('should handle null tokowakaConfig gracefully', async () => { + mockSite.getConfig = () => ({ + getTokowakaConfig: () => null, + }); + + try { + await client.deploySuggestions(mockSite, mockOpportunity, mockSuggestions); + expect.fail('Should have thrown error'); + } catch (error) { + expect(error.message).to.include('Tokowaka API key configured'); + } + }); + + it('should use default reason when eligibility has no reason', async () => { + // Create a mock mapper that returns eligible=false without reason + const mockMapper = { + canDeploy: sinon.stub().returns({ eligible: false }), // No reason provided + }; + sinon.stub(client.mapperRegistry, 'getMapper').returns(mockMapper); + + const result = await client.deploySuggestions( + mockSite, + mockOpportunity, + mockSuggestions, + ); + + expect(result.failedSuggestions).to.have.length(2); + expect(result.failedSuggestions[0].reason).to.equal('Suggestion cannot be deployed'); + }); + }); + + describe('invalidateCdnCache', () => { + let mockCdnClient; + + beforeEach(() => { + mockCdnClient = { + invalidateCache: sinon.stub().resolves({ + status: 'success', + provider: 'cloudfront', + invalidationId: 'I123', + }), + }; + + sinon.stub(client.cdnClientRegistry, 'getClient').returns(mockCdnClient); + }); + + it('should invalidate CDN cache successfully', async () => { + const result = await client.invalidateCdnCache('test-api-key', 'cloudfront'); + + expect(result).to.deep.equal({ + status: 'success', + provider: 'cloudfront', + invalidationId: 'I123', + }); + + expect(mockCdnClient.invalidateCache).to.have.been.calledWith([ + '/opportunities/test-api-key', + ]); + expect(log.debug).to.have.been.calledWith(sinon.match(/Invalidating CDN cache/)); + expect(log.info).to.have.been.calledWith(sinon.match(/CDN cache invalidation completed/)); + }); + + it('should return null if no CDN configuration', async () => { + try { + await client.invalidateCdnCache('', 'cloudfront'); + expect.fail('Should have thrown error'); + } catch (error) { + expect(error.message).to.equal('Tokowaka API key and provider are required'); + expect(error.status).to.equal(400); + } + }); + + it('should return null if CDN config is empty', async () => { + try { + await client.invalidateCdnCache('test-api-key', ''); + expect.fail('Should have thrown error'); + } catch (error) { + expect(error.message).to.equal('Tokowaka API key and provider are required'); + expect(error.status).to.equal(400); + } + }); + + it('should return null if CDN provider is missing', async () => { + try { + await client.invalidateCdnCache('test-api-key', null); + expect.fail('Should have thrown error'); + } catch (error) { + expect(error.message).to.equal('Tokowaka API key and provider are required'); + expect(error.status).to.equal(400); + } + }); + + it('should return null if CDN config is missing', async () => { + try { + await client.invalidateCdnCache(null, 'cloudfront'); + expect.fail('Should have thrown error'); + } catch (error) { + expect(error.message).to.equal('Tokowaka API key and provider are required'); + expect(error.status).to.equal(400); + } + }); + + it('should return null if no CDN client available', async () => { + client.cdnClientRegistry.getClient.returns(null); + + const result = await client.invalidateCdnCache('test-api-key', 'cloudfront'); + + expect(result).to.deep.equal({ + status: 'error', + provider: 'cloudfront', + message: 'No CDN client available for provider: cloudfront', + }); + expect(log.error).to.have.been.calledWith(sinon.match(/Failed to invalidate CDN cache/)); + }); + + it('should return error object if CDN invalidation fails', async () => { + mockCdnClient.invalidateCache.rejects(new Error('CDN API error')); + + const result = await client.invalidateCdnCache('test-api-key', 'cloudfront'); + + expect(result).to.deep.equal({ + status: 'error', + provider: 'cloudfront', + message: 'CDN API error', + }); + + expect(log.error).to.have.been.calledWith(sinon.match(/Failed to invalidate CDN cache/)); + }); }); }); diff --git a/packages/spacecat-shared-tokowaka-client/test/mappers/base-mapper.test.js b/packages/spacecat-shared-tokowaka-client/test/mappers/base-mapper.test.js new file mode 100644 index 000000000..4fddfb176 --- /dev/null +++ b/packages/spacecat-shared-tokowaka-client/test/mappers/base-mapper.test.js @@ -0,0 +1,52 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +/* eslint-env mocha */ + +import { expect } from 'chai'; +import BaseOpportunityMapper from '../../src/mappers/base-mapper.js'; + +describe('BaseOpportunityMapper', () => { + let mapper; + + beforeEach(() => { + mapper = new BaseOpportunityMapper(); + }); + + describe('abstract methods', () => { + it('getOpportunityType should throw error', () => { + expect(() => mapper.getOpportunityType()) + .to.throw('getOpportunityType() must be implemented by subclass'); + }); + + it('requiresPrerender should throw error', () => { + expect(() => mapper.requiresPrerender()) + .to.throw('requiresPrerender() must be implemented by subclass'); + }); + + it('suggestionToPatch should throw error', () => { + expect(() => mapper.suggestionToPatch({}, 'opp-123')) + .to.throw('suggestionToPatch() must be implemented by subclass'); + }); + + it('validateSuggestionData should return false by default', () => { + const result = mapper.validateSuggestionData({}); + expect(result).to.be.false; + }); + + it('canDeploy should return eligible by default', () => { + const result = mapper.canDeploy({}); + + expect(result).to.deep.equal({ eligible: true }); + }); + }); +}); diff --git a/packages/spacecat-shared-tokowaka-client/test/mappers/headings-mapper.test.js b/packages/spacecat-shared-tokowaka-client/test/mappers/headings-mapper.test.js new file mode 100644 index 000000000..8795e4a6d --- /dev/null +++ b/packages/spacecat-shared-tokowaka-client/test/mappers/headings-mapper.test.js @@ -0,0 +1,189 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +/* eslint-env mocha */ + +import { expect } from 'chai'; +import HeadingsMapper from '../../src/mappers/headings-mapper.js'; + +describe('HeadingsMapper', () => { + let mapper; + + beforeEach(() => { + mapper = new HeadingsMapper(); + }); + + describe('getOpportunityType', () => { + it('should return headings', () => { + expect(mapper.getOpportunityType()).to.equal('headings'); + }); + }); + + describe('requiresPrerender', () => { + it('should return true', () => { + expect(mapper.requiresPrerender()).to.be.true; + }); + }); + + describe('canDeploy', () => { + it('should return eligible for heading-empty checkType', () => { + const suggestion = { + getData: () => ({ checkType: 'heading-empty' }), + }; + + const result = mapper.canDeploy(suggestion); + + expect(result).to.deep.equal({ eligible: true }); + }); + + it('should return ineligible for heading-missing checkType', () => { + const suggestion = { + getData: () => ({ checkType: 'heading-missing' }), + }; + + const result = mapper.canDeploy(suggestion); + + expect(result).to.deep.equal({ + eligible: false, + reason: 'Only empty headings can be deployed. This suggestion has checkType: heading-missing', + }); + }); + + it('should return ineligible for unknown checkType', () => { + const suggestion = { + getData: () => ({ checkType: 'unknown-type' }), + }; + + const result = mapper.canDeploy(suggestion); + + expect(result).to.deep.equal({ + eligible: false, + reason: 'Only empty headings can be deployed. This suggestion has checkType: unknown-type', + }); + }); + + it('should return ineligible when checkType is missing', () => { + const suggestion = { + getData: () => ({}), + }; + + const result = mapper.canDeploy(suggestion); + + expect(result).to.deep.equal({ + eligible: false, + reason: 'Only empty headings can be deployed. This suggestion has checkType: undefined', + }); + }); + + it('should return ineligible when data is null', () => { + const suggestion = { + getData: () => null, + }; + + const result = mapper.canDeploy(suggestion); + + expect(result).to.deep.equal({ + eligible: false, + reason: 'Only empty headings can be deployed. This suggestion has checkType: undefined', + }); + }); + }); + + describe('suggestionToPatch', () => { + it('should create patch with headingTag selector', () => { + const suggestion = { + getId: () => 'sugg-123', + getData: () => ({ + headingTag: 'h1', + recommendedAction: 'New Heading', + }), + }; + + const patch = mapper.suggestionToPatch(suggestion, 'opp-123'); + + expect(patch).to.deep.include({ + op: 'replace', + selector: 'h1', + value: 'New Heading', + opportunityId: 'opp-123', + suggestionId: 'sugg-123', + prerenderRequired: true, + }); + expect(patch.lastUpdated).to.be.a('number'); + }); + + it('should create patch with path selector when headingTag is missing', () => { + const suggestion = { + getId: () => 'sugg-123', + getData: () => ({ + path: 'body > h1', + recommendedAction: 'New Heading', + }), + }; + + const patch = mapper.suggestionToPatch(suggestion, 'opp-123'); + + expect(patch).to.deep.include({ + op: 'replace', + selector: 'body > h1', + value: 'New Heading', + opportunityId: 'opp-123', + suggestionId: 'sugg-123', + prerenderRequired: true, + }); + }); + }); + + describe('validateSuggestionData', () => { + it('should return true for valid data with headingTag', () => { + const data = { + headingTag: 'h1', + recommendedAction: 'New Heading', + }; + + expect(mapper.validateSuggestionData(data)).to.be.true; + }); + + it('should return true for valid data with path', () => { + const data = { + path: 'body > h1', + recommendedAction: 'New Heading', + }; + + expect(mapper.validateSuggestionData(data)).to.be.true; + }); + + it('should return false if both headingTag and path are missing', () => { + const data = { + recommendedAction: 'New Heading', + }; + + expect(mapper.validateSuggestionData(data)).to.be.false; + }); + + it('should return false if recommendedAction is missing', () => { + const data = { + headingTag: 'h1', + }; + + expect(mapper.validateSuggestionData(data)).to.be.false; + }); + + it('should return false if data is empty', () => { + expect(mapper.validateSuggestionData({})).to.be.false; + }); + + it('should return false if data is null', () => { + expect(mapper.validateSuggestionData(null)).to.be.false; + }); + }); +}); diff --git a/packages/spacecat-shared-tokowaka-client/test/mappers/mapper-registry.test.js b/packages/spacecat-shared-tokowaka-client/test/mappers/mapper-registry.test.js new file mode 100644 index 000000000..92eedcc62 --- /dev/null +++ b/packages/spacecat-shared-tokowaka-client/test/mappers/mapper-registry.test.js @@ -0,0 +1,197 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +/* eslint-env mocha */ +/* eslint-disable max-classes-per-file */ + +import { expect, use } from 'chai'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import MapperRegistry from '../../src/mappers/mapper-registry.js'; +import HeadingsMapper from '../../src/mappers/headings-mapper.js'; +import BaseOpportunityMapper from '../../src/mappers/base-mapper.js'; + +use(sinonChai); + +describe('MapperRegistry', () => { + let registry; + let log; + + beforeEach(() => { + log = { + info: sinon.stub(), + warn: sinon.stub(), + error: sinon.stub(), + debug: sinon.stub(), + }; + + registry = new MapperRegistry(log); + }); + + afterEach(() => { + sinon.restore(); + }); + + describe('constructor', () => { + it('should create an instance and register default mappers', () => { + expect(registry).to.be.instanceOf(MapperRegistry); + expect(registry.mappers).to.be.instanceOf(Map); + expect(registry.mappers.size).to.be.greaterThan(0); + }); + }); + + describe('registerMapper', () => { + it('should register a custom mapper', () => { + class CustomMapper extends BaseOpportunityMapper { + // eslint-disable-next-line class-methods-use-this + getOpportunityType() { + return 'custom'; + } + + // eslint-disable-next-line class-methods-use-this + requiresPrerender() { + return false; + } + + // eslint-disable-next-line class-methods-use-this + suggestionToPatch() { + return {}; + } + + // eslint-disable-next-line class-methods-use-this + validateSuggestionData() { + return true; + } + } + + registry.registerMapper(new CustomMapper(log)); + + expect(registry.mappers.has('custom')).to.be.true; + expect(registry.getSupportedOpportunityTypes()).to.include('custom'); + }); + + it('should log debug message when overriding existing mapper', () => { + class CustomMapper extends BaseOpportunityMapper { + // eslint-disable-next-line class-methods-use-this + getOpportunityType() { + return 'headings'; // Override existing headings mapper + } + + // eslint-disable-next-line class-methods-use-this + requiresPrerender() { + return false; + } + + // eslint-disable-next-line class-methods-use-this + suggestionToPatch() { + return {}; + } + } + + registry.registerMapper(new CustomMapper(log)); + + expect(log.debug).to.have.been.calledWith( + 'Mapper for opportunity type "headings" is being overridden', + ); + expect(log.info).to.have.been.calledWith( + 'Registered mapper for opportunity type: headings', + ); + }); + }); + + describe('getMapper', () => { + it('should return headings mapper for headings opportunity type', () => { + const mapper = registry.getMapper('headings'); + + expect(mapper).to.be.instanceOf(HeadingsMapper); + }); + + it('should return null for unsupported opportunity type', () => { + const mapper = registry.getMapper('unsupported-type'); + + expect(mapper).to.be.null; + expect(log.warn).to.have.been.calledWith( + 'No mapper found for opportunity type: unsupported-type', + ); + }); + + it('should return null when opportunity type is empty', () => { + const mapper = registry.getMapper(''); + + expect(mapper).to.be.null; + }); + + it('should return null when opportunity type is null', () => { + const mapper = registry.getMapper(null); + + expect(mapper).to.be.null; + }); + }); + + describe('getSupportedOpportunityTypes', () => { + it('should return list of supported opportunity types', () => { + const types = registry.getSupportedOpportunityTypes(); + + expect(types).to.be.an('array'); + expect(types).to.include('headings'); + }); + + it('should include custom registered mappers', () => { + class CustomMapper extends BaseOpportunityMapper { + // eslint-disable-next-line class-methods-use-this + getOpportunityType() { + return 'custom'; + } + + // eslint-disable-next-line class-methods-use-this + requiresPrerender() { + return false; + } + + // eslint-disable-next-line class-methods-use-this + suggestionToPatch() { + return {}; + } + + // eslint-disable-next-line class-methods-use-this + validateSuggestionData() { + return true; + } + } + + registry.registerMapper(new CustomMapper(log)); + + const types = registry.getSupportedOpportunityTypes(); + + expect(types).to.include('custom'); + expect(types).to.include('headings'); + }); + }); + + describe('hasMapper', () => { + it('should return true for supported opportunity type', () => { + expect(registry.hasMapper('headings')).to.be.true; + }); + + it('should return false for unsupported opportunity type', () => { + expect(registry.hasMapper('unsupported')).to.be.false; + }); + + it('should return false for null opportunity type', () => { + expect(registry.hasMapper(null)).to.be.false; + }); + + it('should return false for undefined opportunity type', () => { + expect(registry.hasMapper(undefined)).to.be.false; + }); + }); +}); diff --git a/packages/spacecat-shared-tokowaka-client/test/setup-env.js b/packages/spacecat-shared-tokowaka-client/test/setup-env.js index f9c07f88c..7afe506f8 100644 --- a/packages/spacecat-shared-tokowaka-client/test/setup-env.js +++ b/packages/spacecat-shared-tokowaka-client/test/setup-env.js @@ -9,5 +9,10 @@ * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ - -process.env.TOKOWAKA_CONFIG_BUCKET = 'test-bucket'; +// eslint-disable-next-line no-console +console.log('Forcing HTTP/1.1 for Adobe Fetch'); +process.env.HELIX_FETCH_FORCE_HTTP1 = 'true'; +// eslint-disable-next-line no-console +console.log('Disabling AWS XRay'); +process.env.AWS_XRAY_SDK_ENABLED = 'false'; +process.env.AWS_XRAY_CONTEXT_MISSING = 'IGNORE_ERROR'; From b88540c5301d9d283ccb5cd4343f011773b345bb Mon Sep 17 00:00:00 2001 From: Divyansh Pratap Date: Fri, 17 Oct 2025 02:01:32 +0530 Subject: [PATCH 09/20] fix: add tests --- .../test/unit/models/site/config.test.js | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/packages/spacecat-shared-data-access/test/unit/models/site/config.test.js b/packages/spacecat-shared-data-access/test/unit/models/site/config.test.js index 3624da953..152b49553 100644 --- a/packages/spacecat-shared-data-access/test/unit/models/site/config.test.js +++ b/packages/spacecat-shared-data-access/test/unit/models/site/config.test.js @@ -2312,6 +2312,75 @@ describe('Config Tests', () => { }); }); + describe('Tokowaka Config', () => { + it('creates a Config with tokowakaConfig property', () => { + const data = { + tokowakaConfig: { + apiKey: 'test-api-key', + }, + }; + const config = Config(data); + expect(config.getTokowakaConfig()).to.deep.equal(data.tokowakaConfig); + }); + + it('has undefined tokowakaConfig in default config', () => { + const config = Config(); + expect(config.getTokowakaConfig()).to.be.undefined; + }); + + it('should return undefined for tokowakaConfig if not provided', () => { + const config = Config({}); + expect(config.getTokowakaConfig()).to.be.undefined; + }); + + it('should preserve provided data if tokowakaConfig is invalid', () => { + const data = { + tokowakaConfig: { + // missing required apiKey + }, + }; + const config = Config(data); + expect(config.getSlackConfig()).to.be.undefined; + expect(config.getHandlers()).to.be.undefined; + expect(config.getTokowakaConfig()).to.deep.equal({}); + }); + + it('should be able to update tokowakaConfig', () => { + const data = { + tokowakaConfig: { + apiKey: 'initial-api-key', + }, + }; + const config = Config({}); + config.updateTokowakaConfig(data.tokowakaConfig); + expect(config.getTokowakaConfig()).to.deep.equal(data.tokowakaConfig); + }); + + it('should be able to update tokowakaConfig with different apiKey', () => { + const config = Config({ + tokowakaConfig: { + apiKey: 'old-api-key', + }, + }); + + const newConfig = { + apiKey: 'new-api-key', + }; + config.updateTokowakaConfig(newConfig); + expect(config.getTokowakaConfig()).to.deep.equal(newConfig); + }); + + it('includes tokowakaConfig in toDynamoItem conversion', () => { + const data = Config({ + tokowakaConfig: { + apiKey: 'test-api-key', + }, + }); + const dynamoItem = Config.toDynamoItem(data); + expect(dynamoItem.tokowakaConfig).to.deep.equal(data.getTokowakaConfig()); + }); + }); + describe('LLMO Well Known Tags', () => { const { extractWellKnownTags } = Config(); From a4e1386f09fcfa6757f86315d707eab7b3418c38 Mon Sep 17 00:00:00 2001 From: Divyansh Pratap Date: Fri, 17 Oct 2025 16:58:04 +0530 Subject: [PATCH 10/20] fix: update package lock --- package-lock.json | 3707 ++++++++++++++++++--------------------------- 1 file changed, 1476 insertions(+), 2231 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8db16a1db..06a519fab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -404,6 +404,10 @@ "resolved": "packages/spacecat-shared-tier-client", "link": true }, + "node_modules/@adobe/spacecat-shared-tokowaka-client": { + "resolved": "packages/spacecat-shared-tokowaka-client", + "link": true + }, "node_modules/@adobe/spacecat-shared-utils": { "resolved": "packages/spacecat-shared-utils", "link": true @@ -628,13 +632,27 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-athena/node_modules/@aws-sdk/client-sso": { + "node_modules/@aws-sdk/client-athena/node_modules/@aws-sdk/types": { + "version": "3.893.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudfront": { "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudfront/-/client-cloudfront-3.893.0.tgz", + "integrity": "sha512-sxBIrVMN6a78y3/YqogpShLuR+aCSZ2j1eAD00gnhr0+shjToz8WdOvKnseC43tNyvC485lEm2sZMgMhRxitEA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.893.0", + "@aws-sdk/credential-provider-node": "3.893.0", "@aws-sdk/middleware-host-header": "3.893.0", "@aws-sdk/middleware-logger": "3.893.0", "@aws-sdk/middleware-recursion-detection": "3.893.0", @@ -644,6 +662,7 @@ "@aws-sdk/util-endpoints": "3.893.0", "@aws-sdk/util-user-agent-browser": "3.893.0", "@aws-sdk/util-user-agent-node": "3.893.0", + "@aws-sdk/xml-builder": "3.893.0", "@smithy/config-resolver": "^4.2.2", "@smithy/core": "^3.11.1", "@smithy/fetch-http-handler": "^5.2.1", @@ -668,44 +687,21 @@ "@smithy/util-endpoints": "^3.1.2", "@smithy/util-middleware": "^4.1.1", "@smithy/util-retry": "^4.1.2", + "@smithy/util-stream": "^4.3.2", "@smithy/util-utf8": "^4.1.0", + "@smithy/util-waiter": "^4.1.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-athena/node_modules/@aws-sdk/core": { - "version": "3.893.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.893.0", - "@aws-sdk/xml-builder": "3.893.0", - "@smithy/core": "^3.11.1", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/property-provider": "^4.1.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/signature-v4": "^5.2.1", - "@smithy/smithy-client": "^4.6.3", - "@smithy/types": "^4.5.0", - "@smithy/util-base64": "^4.1.0", - "@smithy/util-body-length-browser": "^4.1.0", - "@smithy/util-middleware": "^4.1.1", - "@smithy/util-utf8": "^4.1.0", - "fast-xml-parser": "5.2.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-athena/node_modules/@aws-sdk/credential-provider-env": { + "node_modules/@aws-sdk/client-cloudfront/node_modules/@aws-sdk/types": { "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", + "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/property-provider": "^4.1.1", "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, @@ -713,164 +709,62 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-athena/node_modules/@aws-sdk/credential-provider-http": { + "node_modules/@aws-sdk/client-dynamodb": { "version": "3.893.0", "license": "Apache-2.0", "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.893.0", + "@aws-sdk/credential-provider-node": "3.893.0", + "@aws-sdk/middleware-endpoint-discovery": "3.893.0", + "@aws-sdk/middleware-host-header": "3.893.0", + "@aws-sdk/middleware-logger": "3.893.0", + "@aws-sdk/middleware-recursion-detection": "3.893.0", + "@aws-sdk/middleware-user-agent": "3.893.0", + "@aws-sdk/region-config-resolver": "3.893.0", "@aws-sdk/types": "3.893.0", + "@aws-sdk/util-endpoints": "3.893.0", + "@aws-sdk/util-user-agent-browser": "3.893.0", + "@aws-sdk/util-user-agent-node": "3.893.0", + "@smithy/config-resolver": "^4.2.2", + "@smithy/core": "^3.11.1", "@smithy/fetch-http-handler": "^5.2.1", + "@smithy/hash-node": "^4.1.1", + "@smithy/invalid-dependency": "^4.1.1", + "@smithy/middleware-content-length": "^4.1.1", + "@smithy/middleware-endpoint": "^4.2.3", + "@smithy/middleware-retry": "^4.2.4", + "@smithy/middleware-serde": "^4.1.1", + "@smithy/middleware-stack": "^4.1.1", + "@smithy/node-config-provider": "^4.2.2", "@smithy/node-http-handler": "^4.2.1", - "@smithy/property-provider": "^4.1.1", "@smithy/protocol-http": "^5.2.1", "@smithy/smithy-client": "^4.6.3", "@smithy/types": "^4.5.0", - "@smithy/util-stream": "^4.3.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-athena/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.893.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.893.0", - "@aws-sdk/credential-provider-env": "3.893.0", - "@aws-sdk/credential-provider-http": "3.893.0", - "@aws-sdk/credential-provider-process": "3.893.0", - "@aws-sdk/credential-provider-sso": "3.893.0", - "@aws-sdk/credential-provider-web-identity": "3.893.0", - "@aws-sdk/nested-clients": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/credential-provider-imds": "^4.1.2", - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-athena/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.893.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.893.0", - "@aws-sdk/credential-provider-http": "3.893.0", - "@aws-sdk/credential-provider-ini": "3.893.0", - "@aws-sdk/credential-provider-process": "3.893.0", - "@aws-sdk/credential-provider-sso": "3.893.0", - "@aws-sdk/credential-provider-web-identity": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/credential-provider-imds": "^4.1.2", - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-athena/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.893.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-athena/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.893.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sso": "3.893.0", - "@aws-sdk/core": "3.893.0", - "@aws-sdk/token-providers": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-athena/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.893.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.893.0", - "@aws-sdk/nested-clients": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-athena/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.893.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.893.0", - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-athena/node_modules/@aws-sdk/middleware-logger": { - "version": "3.893.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.893.0", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-athena/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.893.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.893.0", - "@aws/lambda-invoke-store": "^0.0.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" + "@smithy/url-parser": "^4.1.1", + "@smithy/util-base64": "^4.1.0", + "@smithy/util-body-length-browser": "^4.1.0", + "@smithy/util-body-length-node": "^4.1.0", + "@smithy/util-defaults-mode-browser": "^4.1.3", + "@smithy/util-defaults-mode-node": "^4.1.3", + "@smithy/util-endpoints": "^3.1.2", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-retry": "^4.1.2", + "@smithy/util-utf8": "^4.1.0", + "@smithy/util-waiter": "^4.1.1", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-athena/node_modules/@aws-sdk/middleware-user-agent": { + "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/types": { "version": "3.893.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@aws-sdk/util-endpoints": "3.893.0", - "@smithy/core": "^3.11.1", - "@smithy/protocol-http": "^5.2.1", "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, @@ -878,27 +772,45 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-athena/node_modules/@aws-sdk/nested-clients": { + "node_modules/@aws-sdk/client-s3": { "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.893.0.tgz", + "integrity": "sha512-/P74KDJhOijnIAQR93sq1DQn8vbU3WaPZDyy1XUMRJJIY6iEJnDo1toD9XY6AFDz5TRto8/8NbcXT30AMOUtJQ==", "license": "Apache-2.0", "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.893.0", + "@aws-sdk/credential-provider-node": "3.893.0", + "@aws-sdk/middleware-bucket-endpoint": "3.893.0", + "@aws-sdk/middleware-expect-continue": "3.893.0", + "@aws-sdk/middleware-flexible-checksums": "3.893.0", "@aws-sdk/middleware-host-header": "3.893.0", + "@aws-sdk/middleware-location-constraint": "3.893.0", "@aws-sdk/middleware-logger": "3.893.0", "@aws-sdk/middleware-recursion-detection": "3.893.0", + "@aws-sdk/middleware-sdk-s3": "3.893.0", + "@aws-sdk/middleware-ssec": "3.893.0", "@aws-sdk/middleware-user-agent": "3.893.0", "@aws-sdk/region-config-resolver": "3.893.0", + "@aws-sdk/signature-v4-multi-region": "3.893.0", "@aws-sdk/types": "3.893.0", "@aws-sdk/util-endpoints": "3.893.0", "@aws-sdk/util-user-agent-browser": "3.893.0", "@aws-sdk/util-user-agent-node": "3.893.0", + "@aws-sdk/xml-builder": "3.893.0", "@smithy/config-resolver": "^4.2.2", "@smithy/core": "^3.11.1", + "@smithy/eventstream-serde-browser": "^4.1.1", + "@smithy/eventstream-serde-config-resolver": "^4.2.1", + "@smithy/eventstream-serde-node": "^4.1.1", "@smithy/fetch-http-handler": "^5.2.1", + "@smithy/hash-blob-browser": "^4.1.1", "@smithy/hash-node": "^4.1.1", + "@smithy/hash-stream-node": "^4.1.1", "@smithy/invalid-dependency": "^4.1.1", + "@smithy/md5-js": "^4.1.1", "@smithy/middleware-content-length": "^4.1.1", "@smithy/middleware-endpoint": "^4.2.3", "@smithy/middleware-retry": "^4.2.4", @@ -918,103 +830,21 @@ "@smithy/util-endpoints": "^3.1.2", "@smithy/util-middleware": "^4.1.1", "@smithy/util-retry": "^4.1.2", + "@smithy/util-stream": "^4.3.2", "@smithy/util-utf8": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-athena/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.893.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.893.0", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/types": "^4.5.0", - "@smithy/util-config-provider": "^4.1.0", - "@smithy/util-middleware": "^4.1.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-athena/node_modules/@aws-sdk/token-providers": { - "version": "3.893.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.893.0", - "@aws-sdk/nested-clients": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-athena/node_modules/@aws-sdk/types": { - "version": "3.893.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-athena/node_modules/@aws-sdk/util-endpoints": { - "version": "3.893.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.893.0", - "@smithy/types": "^4.5.0", - "@smithy/url-parser": "^4.1.1", - "@smithy/util-endpoints": "^3.1.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-athena/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.893.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.893.0", - "@smithy/types": "^4.5.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-athena/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.893.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" + "@smithy/util-waiter": "^4.1.1", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { "node": ">=18.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } } }, - "node_modules/@aws-sdk/client-athena/node_modules/@aws-sdk/xml-builder": { + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/types": { "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", + "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.5.0", @@ -1024,16 +854,14 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-dynamodb": { + "node_modules/@aws-sdk/client-secrets-manager": { "version": "3.893.0", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.893.0", "@aws-sdk/credential-provider-node": "3.893.0", - "@aws-sdk/middleware-endpoint-discovery": "3.893.0", "@aws-sdk/middleware-host-header": "3.893.0", "@aws-sdk/middleware-logger": "3.893.0", "@aws-sdk/middleware-recursion-detection": "3.893.0", @@ -1068,7 +896,6 @@ "@smithy/util-middleware": "^4.1.1", "@smithy/util-retry": "^4.1.2", "@smithy/util-utf8": "^4.1.0", - "@smithy/util-waiter": "^4.1.1", "@types/uuid": "^9.0.1", "tslib": "^2.6.2", "uuid": "^9.0.1" @@ -1077,8 +904,21 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/client-sso": { + "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/types": { + "version": "3.893.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.893.0.tgz", + "integrity": "sha512-0+qRGq7H8UNfxI0F02ObyOgOiYxkN4DSlFfwQUQMPfqENDNYOrL++2H9X3EInyc1lUM/+aK8TZqSbh473gdxcg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -1124,330 +964,379 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/core": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.726.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", - "@aws-sdk/xml-builder": "3.893.0", - "@smithy/core": "^3.11.1", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/property-provider": "^4.1.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/signature-v4": "^5.2.1", - "@smithy/smithy-client": "^4.6.3", - "@smithy/types": "^4.5.0", - "@smithy/util-base64": "^4.1.0", - "@smithy/util-body-length-browser": "^4.1.0", - "@smithy/util-middleware": "^4.1.1", - "@smithy/util-utf8": "^4.1.0", - "fast-xml-parser": "5.2.5", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.723.0", + "@aws-sdk/credential-provider-node": "3.726.0", + "@aws-sdk/middleware-host-header": "3.723.0", + "@aws-sdk/middleware-logger": "3.723.0", + "@aws-sdk/middleware-recursion-detection": "3.723.0", + "@aws-sdk/middleware-user-agent": "3.726.0", + "@aws-sdk/region-config-resolver": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@aws-sdk/util-endpoints": "3.726.0", + "@aws-sdk/util-user-agent-browser": "3.723.0", + "@aws-sdk/util-user-agent-node": "3.726.0", + "@smithy/config-resolver": "^4.0.0", + "@smithy/core": "^3.0.0", + "@smithy/fetch-http-handler": "^5.0.0", + "@smithy/hash-node": "^4.0.0", + "@smithy/invalid-dependency": "^4.0.0", + "@smithy/middleware-content-length": "^4.0.0", + "@smithy/middleware-endpoint": "^4.0.0", + "@smithy/middleware-retry": "^4.0.0", + "@smithy/middleware-serde": "^4.0.0", + "@smithy/middleware-stack": "^4.0.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/node-http-handler": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/url-parser": "^4.0.0", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.0", + "@smithy/util-defaults-mode-node": "^4.0.0", + "@smithy/util-endpoints": "^3.0.0", + "@smithy/util-middleware": "^4.0.0", + "@smithy/util-retry": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.893.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/property-provider": "^4.1.1", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@aws-sdk/client-sts": "^3.726.0" } }, - "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/client-sso": { + "version": "3.726.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/fetch-http-handler": "^5.2.1", - "@smithy/node-http-handler": "^4.2.1", - "@smithy/property-provider": "^4.1.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/smithy-client": "^4.6.3", - "@smithy/types": "^4.5.0", - "@smithy/util-stream": "^4.3.2", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.723.0", + "@aws-sdk/middleware-host-header": "3.723.0", + "@aws-sdk/middleware-logger": "3.723.0", + "@aws-sdk/middleware-recursion-detection": "3.723.0", + "@aws-sdk/middleware-user-agent": "3.726.0", + "@aws-sdk/region-config-resolver": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@aws-sdk/util-endpoints": "3.726.0", + "@aws-sdk/util-user-agent-browser": "3.723.0", + "@aws-sdk/util-user-agent-node": "3.726.0", + "@smithy/config-resolver": "^4.0.0", + "@smithy/core": "^3.0.0", + "@smithy/fetch-http-handler": "^5.0.0", + "@smithy/hash-node": "^4.0.0", + "@smithy/invalid-dependency": "^4.0.0", + "@smithy/middleware-content-length": "^4.0.0", + "@smithy/middleware-endpoint": "^4.0.0", + "@smithy/middleware-retry": "^4.0.0", + "@smithy/middleware-serde": "^4.0.0", + "@smithy/middleware-stack": "^4.0.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/node-http-handler": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/url-parser": "^4.0.0", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.0", + "@smithy/util-defaults-mode-node": "^4.0.0", + "@smithy/util-endpoints": "^3.0.0", + "@smithy/util-middleware": "^4.0.0", + "@smithy/util-retry": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/core": { + "version": "3.723.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.893.0", - "@aws-sdk/credential-provider-env": "3.893.0", - "@aws-sdk/credential-provider-http": "3.893.0", - "@aws-sdk/credential-provider-process": "3.893.0", - "@aws-sdk/credential-provider-sso": "3.893.0", - "@aws-sdk/credential-provider-web-identity": "3.893.0", - "@aws-sdk/nested-clients": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/credential-provider-imds": "^4.1.2", - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/types": "3.723.0", + "@smithy/core": "^3.0.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/signature-v4": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/util-middleware": "^4.0.0", + "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.723.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.893.0", - "@aws-sdk/credential-provider-http": "3.893.0", - "@aws-sdk/credential-provider-ini": "3.893.0", - "@aws-sdk/credential-provider-process": "3.893.0", - "@aws-sdk/credential-provider-sso": "3.893.0", - "@aws-sdk/credential-provider-web-identity": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/credential-provider-imds": "^4.1.2", - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/core": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.723.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/core": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@smithy/fetch-http-handler": "^5.0.0", + "@smithy/node-http-handler": "^4.0.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/util-stream": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.726.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.893.0", - "@aws-sdk/core": "3.893.0", - "@aws-sdk/token-providers": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/core": "3.723.0", + "@aws-sdk/credential-provider-env": "3.723.0", + "@aws-sdk/credential-provider-http": "3.723.0", + "@aws-sdk/credential-provider-process": "3.723.0", + "@aws-sdk/credential-provider-sso": "3.726.0", + "@aws-sdk/credential-provider-web-identity": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@smithy/credential-provider-imds": "^4.0.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/shared-ini-file-loader": "^4.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.726.0" } }, - "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.726.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.893.0", - "@aws-sdk/nested-clients": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/credential-provider-env": "3.723.0", + "@aws-sdk/credential-provider-http": "3.723.0", + "@aws-sdk/credential-provider-ini": "3.726.0", + "@aws-sdk/credential-provider-process": "3.723.0", + "@aws-sdk/credential-provider-sso": "3.726.0", + "@aws-sdk/credential-provider-web-identity": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@smithy/credential-provider-imds": "^4.0.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/shared-ini-file-loader": "^4.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.723.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", + "@aws-sdk/core": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/shared-ini-file-loader": "^4.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/middleware-logger": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.726.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/client-sso": "3.726.0", + "@aws-sdk/core": "3.723.0", + "@aws-sdk/token-providers": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/shared-ini-file-loader": "^4.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.723.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", - "@aws/lambda-invoke-store": "^0.0.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", + "@aws-sdk/core": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.723.0" } }, - "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.723.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@aws-sdk/util-endpoints": "3.893.0", - "@smithy/core": "^3.11.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", + "@aws-sdk/types": "3.723.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/nested-clients": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-logger": { + "version": "3.723.0", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.893.0", - "@aws-sdk/middleware-host-header": "3.893.0", - "@aws-sdk/middleware-logger": "3.893.0", - "@aws-sdk/middleware-recursion-detection": "3.893.0", - "@aws-sdk/middleware-user-agent": "3.893.0", - "@aws-sdk/region-config-resolver": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@aws-sdk/util-endpoints": "3.893.0", - "@aws-sdk/util-user-agent-browser": "3.893.0", - "@aws-sdk/util-user-agent-node": "3.893.0", - "@smithy/config-resolver": "^4.2.2", - "@smithy/core": "^3.11.1", - "@smithy/fetch-http-handler": "^5.2.1", - "@smithy/hash-node": "^4.1.1", - "@smithy/invalid-dependency": "^4.1.1", - "@smithy/middleware-content-length": "^4.1.1", - "@smithy/middleware-endpoint": "^4.2.3", - "@smithy/middleware-retry": "^4.2.4", - "@smithy/middleware-serde": "^4.1.1", - "@smithy/middleware-stack": "^4.1.1", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/node-http-handler": "^4.2.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/smithy-client": "^4.6.3", - "@smithy/types": "^4.5.0", - "@smithy/url-parser": "^4.1.1", - "@smithy/util-base64": "^4.1.0", - "@smithy/util-body-length-browser": "^4.1.0", - "@smithy/util-body-length-node": "^4.1.0", - "@smithy/util-defaults-mode-browser": "^4.1.3", - "@smithy/util-defaults-mode-node": "^4.1.3", - "@smithy/util-endpoints": "^3.1.2", - "@smithy/util-middleware": "^4.1.1", - "@smithy/util-retry": "^4.1.2", - "@smithy/util-utf8": "^4.1.0", + "@aws-sdk/types": "3.723.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.723.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/types": "^4.5.0", - "@smithy/util-config-provider": "^4.1.0", - "@smithy/util-middleware": "^4.1.1", + "@aws-sdk/types": "3.723.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/token-providers": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.726.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.893.0", - "@aws-sdk/nested-clients": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/core": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@aws-sdk/util-endpoints": "3.726.0", + "@smithy/core": "^3.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/types": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.723.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.5.0", + "@aws-sdk/types": "3.723.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/util-endpoints": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/token-providers": { + "version": "3.723.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", - "@smithy/types": "^4.5.0", - "@smithy/url-parser": "^4.1.1", - "@smithy/util-endpoints": "^3.1.2", + "@aws-sdk/types": "3.723.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/shared-ini-file-loader": "^4.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.723.0" } }, - "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/types": { + "version": "3.723.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", - "@smithy/types": "^4.5.0", + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/util-endpoints": { + "version": "3.726.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.723.0", + "@smithy/types": "^4.0.0", + "@smithy/util-endpoints": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.723.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.723.0", + "@smithy/types": "^4.0.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.726.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/types": "^4.5.0", + "@aws-sdk/middleware-user-agent": "3.726.0", + "@aws-sdk/types": "3.723.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { @@ -1462,438 +1351,420 @@ } } }, - "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/xml-builder": { - "version": "3.893.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-sso-oidc/node_modules/fast-xml-parser": { + "version": "4.4.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "license": "MIT", "dependencies": { - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" + "strnum": "^1.0.5" }, - "engines": { - "node": ">=18.0.0" + "bin": { + "fxparser": "src/cli/cli.js" } }, - "node_modules/@aws-sdk/client-secrets-manager": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/strnum": { + "version": "1.1.2", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/types": { "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", + "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.893.0", - "@aws-sdk/credential-provider-node": "3.893.0", - "@aws-sdk/middleware-host-header": "3.893.0", - "@aws-sdk/middleware-logger": "3.893.0", - "@aws-sdk/middleware-recursion-detection": "3.893.0", - "@aws-sdk/middleware-user-agent": "3.893.0", - "@aws-sdk/region-config-resolver": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@aws-sdk/util-endpoints": "3.893.0", - "@aws-sdk/util-user-agent-browser": "3.893.0", - "@aws-sdk/util-user-agent-node": "3.893.0", - "@smithy/config-resolver": "^4.2.2", - "@smithy/core": "^3.11.1", - "@smithy/fetch-http-handler": "^5.2.1", - "@smithy/hash-node": "^4.1.1", - "@smithy/invalid-dependency": "^4.1.1", - "@smithy/middleware-content-length": "^4.1.1", - "@smithy/middleware-endpoint": "^4.2.3", - "@smithy/middleware-retry": "^4.2.4", - "@smithy/middleware-serde": "^4.1.1", - "@smithy/middleware-stack": "^4.1.1", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/node-http-handler": "^4.2.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/smithy-client": "^4.6.3", "@smithy/types": "^4.5.0", - "@smithy/url-parser": "^4.1.1", - "@smithy/util-base64": "^4.1.0", - "@smithy/util-body-length-browser": "^4.1.0", - "@smithy/util-body-length-node": "^4.1.0", - "@smithy/util-defaults-mode-browser": "^4.1.3", - "@smithy/util-defaults-mode-node": "^4.1.3", - "@smithy/util-endpoints": "^3.1.2", - "@smithy/util-middleware": "^4.1.1", - "@smithy/util-retry": "^4.1.2", - "@smithy/util-utf8": "^4.1.0", - "@types/uuid": "^9.0.1", - "tslib": "^2.6.2", - "uuid": "^9.0.1" + "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/client-sso": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sts": { + "version": "3.726.1", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.893.0", - "@aws-sdk/middleware-host-header": "3.893.0", - "@aws-sdk/middleware-logger": "3.893.0", - "@aws-sdk/middleware-recursion-detection": "3.893.0", - "@aws-sdk/middleware-user-agent": "3.893.0", - "@aws-sdk/region-config-resolver": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@aws-sdk/util-endpoints": "3.893.0", - "@aws-sdk/util-user-agent-browser": "3.893.0", - "@aws-sdk/util-user-agent-node": "3.893.0", - "@smithy/config-resolver": "^4.2.2", - "@smithy/core": "^3.11.1", - "@smithy/fetch-http-handler": "^5.2.1", - "@smithy/hash-node": "^4.1.1", - "@smithy/invalid-dependency": "^4.1.1", - "@smithy/middleware-content-length": "^4.1.1", - "@smithy/middleware-endpoint": "^4.2.3", - "@smithy/middleware-retry": "^4.2.4", - "@smithy/middleware-serde": "^4.1.1", - "@smithy/middleware-stack": "^4.1.1", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/node-http-handler": "^4.2.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/smithy-client": "^4.6.3", - "@smithy/types": "^4.5.0", - "@smithy/url-parser": "^4.1.1", - "@smithy/util-base64": "^4.1.0", - "@smithy/util-body-length-browser": "^4.1.0", - "@smithy/util-body-length-node": "^4.1.0", - "@smithy/util-defaults-mode-browser": "^4.1.3", - "@smithy/util-defaults-mode-node": "^4.1.3", - "@smithy/util-endpoints": "^3.1.2", - "@smithy/util-middleware": "^4.1.1", - "@smithy/util-retry": "^4.1.2", - "@smithy/util-utf8": "^4.1.0", + "@aws-sdk/client-sso-oidc": "3.726.0", + "@aws-sdk/core": "3.723.0", + "@aws-sdk/credential-provider-node": "3.726.0", + "@aws-sdk/middleware-host-header": "3.723.0", + "@aws-sdk/middleware-logger": "3.723.0", + "@aws-sdk/middleware-recursion-detection": "3.723.0", + "@aws-sdk/middleware-user-agent": "3.726.0", + "@aws-sdk/region-config-resolver": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@aws-sdk/util-endpoints": "3.726.0", + "@aws-sdk/util-user-agent-browser": "3.723.0", + "@aws-sdk/util-user-agent-node": "3.726.0", + "@smithy/config-resolver": "^4.0.0", + "@smithy/core": "^3.0.0", + "@smithy/fetch-http-handler": "^5.0.0", + "@smithy/hash-node": "^4.0.0", + "@smithy/invalid-dependency": "^4.0.0", + "@smithy/middleware-content-length": "^4.0.0", + "@smithy/middleware-endpoint": "^4.0.0", + "@smithy/middleware-retry": "^4.0.0", + "@smithy/middleware-serde": "^4.0.0", + "@smithy/middleware-stack": "^4.0.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/node-http-handler": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/url-parser": "^4.0.0", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.0", + "@smithy/util-defaults-mode-node": "^4.0.0", + "@smithy/util-endpoints": "^3.0.0", + "@smithy/util-middleware": "^4.0.0", + "@smithy/util-retry": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/core": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/client-sso": { + "version": "3.726.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", - "@aws-sdk/xml-builder": "3.893.0", - "@smithy/core": "^3.11.1", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/property-provider": "^4.1.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/signature-v4": "^5.2.1", - "@smithy/smithy-client": "^4.6.3", - "@smithy/types": "^4.5.0", - "@smithy/util-base64": "^4.1.0", - "@smithy/util-body-length-browser": "^4.1.0", - "@smithy/util-middleware": "^4.1.1", - "@smithy/util-utf8": "^4.1.0", - "fast-xml-parser": "5.2.5", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.723.0", + "@aws-sdk/middleware-host-header": "3.723.0", + "@aws-sdk/middleware-logger": "3.723.0", + "@aws-sdk/middleware-recursion-detection": "3.723.0", + "@aws-sdk/middleware-user-agent": "3.726.0", + "@aws-sdk/region-config-resolver": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@aws-sdk/util-endpoints": "3.726.0", + "@aws-sdk/util-user-agent-browser": "3.723.0", + "@aws-sdk/util-user-agent-node": "3.726.0", + "@smithy/config-resolver": "^4.0.0", + "@smithy/core": "^3.0.0", + "@smithy/fetch-http-handler": "^5.0.0", + "@smithy/hash-node": "^4.0.0", + "@smithy/invalid-dependency": "^4.0.0", + "@smithy/middleware-content-length": "^4.0.0", + "@smithy/middleware-endpoint": "^4.0.0", + "@smithy/middleware-retry": "^4.0.0", + "@smithy/middleware-serde": "^4.0.0", + "@smithy/middleware-stack": "^4.0.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/node-http-handler": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/url-parser": "^4.0.0", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.0", + "@smithy/util-defaults-mode-node": "^4.0.0", + "@smithy/util-endpoints": "^3.0.0", + "@smithy/util-middleware": "^4.0.0", + "@smithy/util-retry": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/core": { + "version": "3.723.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/property-provider": "^4.1.1", - "@smithy/types": "^4.5.0", + "@aws-sdk/types": "3.723.0", + "@smithy/core": "^3.0.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/signature-v4": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/util-middleware": "^4.0.0", + "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.723.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/fetch-http-handler": "^5.2.1", - "@smithy/node-http-handler": "^4.2.1", - "@smithy/property-provider": "^4.1.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/smithy-client": "^4.6.3", - "@smithy/types": "^4.5.0", - "@smithy/util-stream": "^4.3.2", + "@aws-sdk/core": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.723.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.893.0", - "@aws-sdk/credential-provider-env": "3.893.0", - "@aws-sdk/credential-provider-http": "3.893.0", - "@aws-sdk/credential-provider-process": "3.893.0", - "@aws-sdk/credential-provider-sso": "3.893.0", - "@aws-sdk/credential-provider-web-identity": "3.893.0", - "@aws-sdk/nested-clients": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/credential-provider-imds": "^4.1.2", - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/core": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@smithy/fetch-http-handler": "^5.0.0", + "@smithy/node-http-handler": "^4.0.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/util-stream": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.726.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.893.0", - "@aws-sdk/credential-provider-http": "3.893.0", - "@aws-sdk/credential-provider-ini": "3.893.0", - "@aws-sdk/credential-provider-process": "3.893.0", - "@aws-sdk/credential-provider-sso": "3.893.0", - "@aws-sdk/credential-provider-web-identity": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/credential-provider-imds": "^4.1.2", - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/core": "3.723.0", + "@aws-sdk/credential-provider-env": "3.723.0", + "@aws-sdk/credential-provider-http": "3.723.0", + "@aws-sdk/credential-provider-process": "3.723.0", + "@aws-sdk/credential-provider-sso": "3.726.0", + "@aws-sdk/credential-provider-web-identity": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@smithy/credential-provider-imds": "^4.0.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/shared-ini-file-loader": "^4.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.726.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.726.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/credential-provider-env": "3.723.0", + "@aws-sdk/credential-provider-http": "3.723.0", + "@aws-sdk/credential-provider-ini": "3.726.0", + "@aws-sdk/credential-provider-process": "3.723.0", + "@aws-sdk/credential-provider-sso": "3.726.0", + "@aws-sdk/credential-provider-web-identity": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@smithy/credential-provider-imds": "^4.0.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/shared-ini-file-loader": "^4.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.723.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.893.0", - "@aws-sdk/core": "3.893.0", - "@aws-sdk/token-providers": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/core": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/shared-ini-file-loader": "^4.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.726.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.893.0", - "@aws-sdk/nested-clients": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/client-sso": "3.726.0", + "@aws-sdk/core": "3.723.0", + "@aws-sdk/token-providers": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/shared-ini-file-loader": "^4.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { + "version": "3.723.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", + "@aws-sdk/types": "3.723.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/shared-ini-file-loader": "^4.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.723.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/middleware-logger": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.723.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/core": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.723.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.723.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", - "@aws/lambda-invoke-store": "^0.0.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", + "@aws-sdk/types": "3.723.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/middleware-logger": { + "version": "3.723.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@aws-sdk/util-endpoints": "3.893.0", - "@smithy/core": "^3.11.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", + "@aws-sdk/types": "3.723.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/nested-clients": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.723.0", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.893.0", - "@aws-sdk/middleware-host-header": "3.893.0", - "@aws-sdk/middleware-logger": "3.893.0", - "@aws-sdk/middleware-recursion-detection": "3.893.0", - "@aws-sdk/middleware-user-agent": "3.893.0", - "@aws-sdk/region-config-resolver": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@aws-sdk/util-endpoints": "3.893.0", - "@aws-sdk/util-user-agent-browser": "3.893.0", - "@aws-sdk/util-user-agent-node": "3.893.0", - "@smithy/config-resolver": "^4.2.2", - "@smithy/core": "^3.11.1", - "@smithy/fetch-http-handler": "^5.2.1", - "@smithy/hash-node": "^4.1.1", - "@smithy/invalid-dependency": "^4.1.1", - "@smithy/middleware-content-length": "^4.1.1", - "@smithy/middleware-endpoint": "^4.2.3", - "@smithy/middleware-retry": "^4.2.4", - "@smithy/middleware-serde": "^4.1.1", - "@smithy/middleware-stack": "^4.1.1", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/node-http-handler": "^4.2.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/smithy-client": "^4.6.3", - "@smithy/types": "^4.5.0", - "@smithy/url-parser": "^4.1.1", - "@smithy/util-base64": "^4.1.0", - "@smithy/util-body-length-browser": "^4.1.0", - "@smithy/util-body-length-node": "^4.1.0", - "@smithy/util-defaults-mode-browser": "^4.1.3", - "@smithy/util-defaults-mode-node": "^4.1.3", - "@smithy/util-endpoints": "^3.1.2", - "@smithy/util-middleware": "^4.1.1", - "@smithy/util-retry": "^4.1.2", - "@smithy/util-utf8": "^4.1.0", + "@aws-sdk/types": "3.723.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.726.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/types": "^4.5.0", - "@smithy/util-config-provider": "^4.1.0", - "@smithy/util-middleware": "^4.1.1", + "@aws-sdk/core": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@aws-sdk/util-endpoints": "3.726.0", + "@smithy/core": "^3.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/token-providers": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.723.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.893.0", - "@aws-sdk/nested-clients": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/types": "3.723.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/types": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/types": { + "version": "3.723.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.5.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/util-endpoints": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/util-endpoints": { + "version": "3.726.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", - "@smithy/types": "^4.5.0", - "@smithy/url-parser": "^4.1.1", - "@smithy/util-endpoints": "^3.1.2", + "@aws-sdk/types": "3.723.0", + "@smithy/types": "^4.0.0", + "@smithy/util-endpoints": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.723.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/types": "3.723.0", + "@smithy/types": "^4.0.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.893.0", + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.726.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/types": "^4.5.0", + "@aws-sdk/middleware-user-agent": "3.726.0", + "@aws-sdk/types": "3.723.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { @@ -1908,906 +1779,715 @@ } } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/xml-builder": { + "node_modules/@aws-sdk/client-sts/node_modules/fast-xml-parser": { + "version": "4.4.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/strnum": { + "version": "1.1.2", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/@aws-sdk/core": { "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.893.0.tgz", + "integrity": "sha512-E1NAWHOprBXIJ9CVb6oTsRD/tNOozrKBD/Sb4t7WZd3dpby6KpYfM6FaEGfRGcJBIcB4245hww8Rmg16qDMJWg==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/types": "3.893.0", + "@aws-sdk/xml-builder": "3.893.0", + "@smithy/core": "^3.11.1", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/property-provider": "^4.1.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/signature-v4": "^5.2.1", + "@smithy/smithy-client": "^4.6.3", "@smithy/types": "^4.5.0", + "@smithy/util-base64": "^4.1.0", + "@smithy/util-body-length-browser": "^4.1.0", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-utf8": "^4.1.0", + "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.726.0", + "node_modules/@aws-sdk/core/node_modules/@aws-sdk/types": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", + "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.723.0", - "@aws-sdk/credential-provider-node": "3.726.0", - "@aws-sdk/middleware-host-header": "3.723.0", - "@aws-sdk/middleware-logger": "3.723.0", - "@aws-sdk/middleware-recursion-detection": "3.723.0", - "@aws-sdk/middleware-user-agent": "3.726.0", - "@aws-sdk/region-config-resolver": "3.723.0", - "@aws-sdk/types": "3.723.0", - "@aws-sdk/util-endpoints": "3.726.0", - "@aws-sdk/util-user-agent-browser": "3.723.0", - "@aws-sdk/util-user-agent-node": "3.726.0", - "@smithy/config-resolver": "^4.0.0", - "@smithy/core": "^3.0.0", - "@smithy/fetch-http-handler": "^5.0.0", - "@smithy/hash-node": "^4.0.0", - "@smithy/invalid-dependency": "^4.0.0", - "@smithy/middleware-content-length": "^4.0.0", - "@smithy/middleware-endpoint": "^4.0.0", - "@smithy/middleware-retry": "^4.0.0", - "@smithy/middleware-serde": "^4.0.0", - "@smithy/middleware-stack": "^4.0.0", - "@smithy/node-config-provider": "^4.0.0", - "@smithy/node-http-handler": "^4.0.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/smithy-client": "^4.0.0", - "@smithy/types": "^4.0.0", - "@smithy/url-parser": "^4.0.0", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.0", - "@smithy/util-defaults-mode-node": "^4.0.0", - "@smithy/util-endpoints": "^3.0.0", - "@smithy/util-middleware": "^4.0.0", - "@smithy/util-retry": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.726.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/client-sso": { - "version": "3.726.0", + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.893.0.tgz", + "integrity": "sha512-h4sYNk1iDrSZQLqFfbuD1GWY6KoVCvourfqPl6JZCYj8Vmnox5y9+7taPxwlU2VVII0hiV8UUbO79P35oPBSyA==", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.723.0", - "@aws-sdk/middleware-host-header": "3.723.0", - "@aws-sdk/middleware-logger": "3.723.0", - "@aws-sdk/middleware-recursion-detection": "3.723.0", - "@aws-sdk/middleware-user-agent": "3.726.0", - "@aws-sdk/region-config-resolver": "3.723.0", - "@aws-sdk/types": "3.723.0", - "@aws-sdk/util-endpoints": "3.726.0", - "@aws-sdk/util-user-agent-browser": "3.723.0", - "@aws-sdk/util-user-agent-node": "3.726.0", - "@smithy/config-resolver": "^4.0.0", - "@smithy/core": "^3.0.0", - "@smithy/fetch-http-handler": "^5.0.0", - "@smithy/hash-node": "^4.0.0", - "@smithy/invalid-dependency": "^4.0.0", - "@smithy/middleware-content-length": "^4.0.0", - "@smithy/middleware-endpoint": "^4.0.0", - "@smithy/middleware-retry": "^4.0.0", - "@smithy/middleware-serde": "^4.0.0", - "@smithy/middleware-stack": "^4.0.0", - "@smithy/node-config-provider": "^4.0.0", - "@smithy/node-http-handler": "^4.0.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/smithy-client": "^4.0.0", - "@smithy/types": "^4.0.0", - "@smithy/url-parser": "^4.0.0", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.0", - "@smithy/util-defaults-mode-node": "^4.0.0", - "@smithy/util-endpoints": "^3.0.0", - "@smithy/util-middleware": "^4.0.0", - "@smithy/util-retry": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@aws-sdk/core": "3.893.0", + "@aws-sdk/types": "3.893.0", + "@smithy/property-provider": "^4.1.1", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/core": { - "version": "3.723.0", + "node_modules/@aws-sdk/credential-provider-env/node_modules/@aws-sdk/types": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", + "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.723.0", - "@smithy/core": "^3.0.0", - "@smithy/node-config-provider": "^4.0.0", - "@smithy/property-provider": "^4.0.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/signature-v4": "^5.0.0", - "@smithy/smithy-client": "^4.0.0", - "@smithy/types": "^4.0.0", - "@smithy/util-middleware": "^4.0.0", - "fast-xml-parser": "4.4.1", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.723.0", + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.893.0.tgz", + "integrity": "sha512-xYoC7DRr++zWZ9jG7/hvd6YjCbGDQzsAu2fBHHf91RVmSETXUgdEaP9rOdfCM02iIK/MYlwiWEIVBcBxWY/GQw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.723.0", - "@aws-sdk/types": "3.723.0", - "@smithy/property-provider": "^4.0.0", - "@smithy/types": "^4.0.0", + "@aws-sdk/core": "3.893.0", + "@aws-sdk/types": "3.893.0", + "@smithy/fetch-http-handler": "^5.2.1", + "@smithy/node-http-handler": "^4.2.1", + "@smithy/property-provider": "^4.1.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/smithy-client": "^4.6.3", + "@smithy/types": "^4.5.0", + "@smithy/util-stream": "^4.3.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.723.0", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@aws-sdk/types": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", + "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.723.0", - "@aws-sdk/types": "3.723.0", - "@smithy/fetch-http-handler": "^5.0.0", - "@smithy/node-http-handler": "^4.0.0", - "@smithy/property-provider": "^4.0.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/smithy-client": "^4.0.0", - "@smithy/types": "^4.0.0", - "@smithy/util-stream": "^4.0.0", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.726.0", + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.893.0.tgz", + "integrity": "sha512-ZQWOl4jdLhJHHrHsOfNRjgpP98A5kw4YzkMOUoK+TgSQVLi7wjb957V0htvwpi6KmGr3f2F8J06D6u2OtIc62w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.723.0", - "@aws-sdk/credential-provider-env": "3.723.0", - "@aws-sdk/credential-provider-http": "3.723.0", - "@aws-sdk/credential-provider-process": "3.723.0", - "@aws-sdk/credential-provider-sso": "3.726.0", - "@aws-sdk/credential-provider-web-identity": "3.723.0", - "@aws-sdk/types": "3.723.0", - "@smithy/credential-provider-imds": "^4.0.0", - "@smithy/property-provider": "^4.0.0", - "@smithy/shared-ini-file-loader": "^4.0.0", - "@smithy/types": "^4.0.0", + "@aws-sdk/core": "3.893.0", + "@aws-sdk/credential-provider-env": "3.893.0", + "@aws-sdk/credential-provider-http": "3.893.0", + "@aws-sdk/credential-provider-process": "3.893.0", + "@aws-sdk/credential-provider-sso": "3.893.0", + "@aws-sdk/credential-provider-web-identity": "3.893.0", + "@aws-sdk/nested-clients": "3.893.0", + "@aws-sdk/types": "3.893.0", + "@smithy/credential-provider-imds": "^4.1.2", + "@smithy/property-provider": "^4.1.1", + "@smithy/shared-ini-file-loader": "^4.2.0", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.726.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.726.0", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/types": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", + "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.723.0", - "@aws-sdk/credential-provider-http": "3.723.0", - "@aws-sdk/credential-provider-ini": "3.726.0", - "@aws-sdk/credential-provider-process": "3.723.0", - "@aws-sdk/credential-provider-sso": "3.726.0", - "@aws-sdk/credential-provider-web-identity": "3.723.0", - "@aws-sdk/types": "3.723.0", - "@smithy/credential-provider-imds": "^4.0.0", - "@smithy/property-provider": "^4.0.0", - "@smithy/shared-ini-file-loader": "^4.0.0", - "@smithy/types": "^4.0.0", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.723.0", + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.893.0.tgz", + "integrity": "sha512-NjvDUXciC2+EaQnbL/u/ZuCXj9PZQ/9ciPhI62LGCoJ3ft91lI1Z58Dgut0OFPpV6i16GhpFxzmbuf40wTgDbA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.723.0", - "@aws-sdk/types": "3.723.0", - "@smithy/property-provider": "^4.0.0", - "@smithy/shared-ini-file-loader": "^4.0.0", - "@smithy/types": "^4.0.0", + "@aws-sdk/credential-provider-env": "3.893.0", + "@aws-sdk/credential-provider-http": "3.893.0", + "@aws-sdk/credential-provider-ini": "3.893.0", + "@aws-sdk/credential-provider-process": "3.893.0", + "@aws-sdk/credential-provider-sso": "3.893.0", + "@aws-sdk/credential-provider-web-identity": "3.893.0", + "@aws-sdk/types": "3.893.0", + "@smithy/credential-provider-imds": "^4.1.2", + "@smithy/property-provider": "^4.1.1", + "@smithy/shared-ini-file-loader": "^4.2.0", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.726.0", + "node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/types": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", + "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.726.0", - "@aws-sdk/core": "3.723.0", - "@aws-sdk/token-providers": "3.723.0", - "@aws-sdk/types": "3.723.0", - "@smithy/property-provider": "^4.0.0", - "@smithy/shared-ini-file-loader": "^4.0.0", - "@smithy/types": "^4.0.0", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.723.0", + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.893.0.tgz", + "integrity": "sha512-5XitkZdiQhjWJV71qWqrH7hMXwuK/TvIRwiwKs7Pj0sapGSk3YgD3Ykdlolz7sQOleoKWYYqgoq73fIPpTTmFA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.723.0", - "@aws-sdk/types": "3.723.0", - "@smithy/property-provider": "^4.0.0", - "@smithy/types": "^4.0.0", + "@aws-sdk/core": "3.893.0", + "@aws-sdk/types": "3.893.0", + "@smithy/property-provider": "^4.1.1", + "@smithy/shared-ini-file-loader": "^4.2.0", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.723.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.723.0", + "node_modules/@aws-sdk/credential-provider-process/node_modules/@aws-sdk/types": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", + "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.723.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/types": "^4.0.0", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-logger": { - "version": "3.723.0", + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.893.0.tgz", + "integrity": "sha512-ms8v13G1r0aHZh5PLcJu6AnQZPs23sRm3Ph0A7+GdqbPvWewP8M7jgZTKyTXi+oYXswdYECU1zPVur8zamhtLg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.723.0", - "@smithy/types": "^4.0.0", + "@aws-sdk/client-sso": "3.893.0", + "@aws-sdk/core": "3.893.0", + "@aws-sdk/token-providers": "3.893.0", + "@aws-sdk/types": "3.893.0", + "@smithy/property-provider": "^4.1.1", + "@smithy/shared-ini-file-loader": "^4.2.0", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.723.0", + "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/types": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", + "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.723.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/types": "^4.0.0", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.726.0", + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.893.0.tgz", + "integrity": "sha512-wWD8r2ot4jf/CoogdPTl13HbwNLW4UheGUCu6gW7n9GoHh1JImYyooPHK8K7kD42hihydIA7OW7iFAf7//JYTw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.723.0", - "@aws-sdk/types": "3.723.0", - "@aws-sdk/util-endpoints": "3.726.0", - "@smithy/core": "^3.0.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/types": "^4.0.0", + "@aws-sdk/core": "3.893.0", + "@aws-sdk/nested-clients": "3.893.0", + "@aws-sdk/types": "3.893.0", + "@smithy/property-provider": "^4.1.1", + "@smithy/shared-ini-file-loader": "^4.2.0", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.723.0", + "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@aws-sdk/types": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", + "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.723.0", - "@smithy/node-config-provider": "^4.0.0", - "@smithy/types": "^4.0.0", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.0", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/token-providers": { - "version": "3.723.0", + "node_modules/@aws-sdk/endpoint-cache": { + "version": "3.893.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.723.0", - "@smithy/property-provider": "^4.0.0", - "@smithy/shared-ini-file-loader": "^4.0.0", - "@smithy/types": "^4.0.0", + "mnemonist": "0.38.3", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sso-oidc": "^3.723.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/types": { - "version": "3.723.0", + "node_modules/@aws-sdk/lib-dynamodb": { + "version": "3.893.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.0.0", + "@aws-sdk/core": "3.893.0", + "@aws-sdk/util-dynamodb": "3.893.0", + "@smithy/core": "^3.11.1", + "@smithy/smithy-client": "^4.6.3", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/util-endpoints": { - "version": "3.726.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.723.0", - "@smithy/types": "^4.0.0", - "@smithy/util-endpoints": "^3.0.0", - "tslib": "^2.6.2" }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.723.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.723.0", - "@smithy/types": "^4.0.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" + "peerDependencies": { + "@aws-sdk/client-dynamodb": "^3.893.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.726.0", + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.893.0.tgz", + "integrity": "sha512-H+wMAoFC73T7M54OFIezdHXR9/lH8TZ3Cx1C3MEBb2ctlzQrVCd8LX8zmOtcGYC8plrRwV+8rNPe0FMqecLRew==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.726.0", - "@aws-sdk/types": "3.723.0", - "@smithy/node-config-provider": "^4.0.0", - "@smithy/types": "^4.0.0", + "@aws-sdk/types": "3.893.0", + "@aws-sdk/util-arn-parser": "3.893.0", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "@smithy/util-config-provider": "^4.1.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/fast-xml-parser": { - "version": "4.4.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - }, - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - } - ], - "license": "MIT", - "dependencies": { - "strnum": "^1.0.5" - }, - "bin": { - "fxparser": "src/cli/cli.js" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/strnum": { - "version": "1.1.2", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, - "node_modules/@aws-sdk/client-sts": { - "version": "3.726.1", + "node_modules/@aws-sdk/middleware-bucket-endpoint/node_modules/@aws-sdk/types": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", + "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.726.0", - "@aws-sdk/core": "3.723.0", - "@aws-sdk/credential-provider-node": "3.726.0", - "@aws-sdk/middleware-host-header": "3.723.0", - "@aws-sdk/middleware-logger": "3.723.0", - "@aws-sdk/middleware-recursion-detection": "3.723.0", - "@aws-sdk/middleware-user-agent": "3.726.0", - "@aws-sdk/region-config-resolver": "3.723.0", - "@aws-sdk/types": "3.723.0", - "@aws-sdk/util-endpoints": "3.726.0", - "@aws-sdk/util-user-agent-browser": "3.723.0", - "@aws-sdk/util-user-agent-node": "3.726.0", - "@smithy/config-resolver": "^4.0.0", - "@smithy/core": "^3.0.0", - "@smithy/fetch-http-handler": "^5.0.0", - "@smithy/hash-node": "^4.0.0", - "@smithy/invalid-dependency": "^4.0.0", - "@smithy/middleware-content-length": "^4.0.0", - "@smithy/middleware-endpoint": "^4.0.0", - "@smithy/middleware-retry": "^4.0.0", - "@smithy/middleware-serde": "^4.0.0", - "@smithy/middleware-stack": "^4.0.0", - "@smithy/node-config-provider": "^4.0.0", - "@smithy/node-http-handler": "^4.0.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/smithy-client": "^4.0.0", - "@smithy/types": "^4.0.0", - "@smithy/url-parser": "^4.0.0", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.0", - "@smithy/util-defaults-mode-node": "^4.0.0", - "@smithy/util-endpoints": "^3.0.0", - "@smithy/util-middleware": "^4.0.0", - "@smithy/util-retry": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/client-sso": { - "version": "3.726.0", + "node_modules/@aws-sdk/middleware-endpoint-discovery": { + "version": "3.893.0", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.723.0", - "@aws-sdk/middleware-host-header": "3.723.0", - "@aws-sdk/middleware-logger": "3.723.0", - "@aws-sdk/middleware-recursion-detection": "3.723.0", - "@aws-sdk/middleware-user-agent": "3.726.0", - "@aws-sdk/region-config-resolver": "3.723.0", - "@aws-sdk/types": "3.723.0", - "@aws-sdk/util-endpoints": "3.726.0", - "@aws-sdk/util-user-agent-browser": "3.723.0", - "@aws-sdk/util-user-agent-node": "3.726.0", - "@smithy/config-resolver": "^4.0.0", - "@smithy/core": "^3.0.0", - "@smithy/fetch-http-handler": "^5.0.0", - "@smithy/hash-node": "^4.0.0", - "@smithy/invalid-dependency": "^4.0.0", - "@smithy/middleware-content-length": "^4.0.0", - "@smithy/middleware-endpoint": "^4.0.0", - "@smithy/middleware-retry": "^4.0.0", - "@smithy/middleware-serde": "^4.0.0", - "@smithy/middleware-stack": "^4.0.0", - "@smithy/node-config-provider": "^4.0.0", - "@smithy/node-http-handler": "^4.0.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/smithy-client": "^4.0.0", - "@smithy/types": "^4.0.0", - "@smithy/url-parser": "^4.0.0", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.0", - "@smithy/util-defaults-mode-node": "^4.0.0", - "@smithy/util-endpoints": "^3.0.0", - "@smithy/util-middleware": "^4.0.0", - "@smithy/util-retry": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@aws-sdk/endpoint-cache": "3.893.0", + "@aws-sdk/types": "3.893.0", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/core": { - "version": "3.723.0", + "node_modules/@aws-sdk/middleware-endpoint-discovery/node_modules/@aws-sdk/types": { + "version": "3.893.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.723.0", - "@smithy/core": "^3.0.0", - "@smithy/node-config-provider": "^4.0.0", - "@smithy/property-provider": "^4.0.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/signature-v4": "^5.0.0", - "@smithy/smithy-client": "^4.0.0", - "@smithy/types": "^4.0.0", - "@smithy/util-middleware": "^4.0.0", - "fast-xml-parser": "4.4.1", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.723.0", + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.893.0.tgz", + "integrity": "sha512-PEZkvD6k0X9sacHkvkVF4t2QyQEAzd35OJ2bIrjWCfc862TwukMMJ1KErRmQ1WqKXHKF4L0ed5vtWaO/8jVLNA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.723.0", - "@aws-sdk/types": "3.723.0", - "@smithy/property-provider": "^4.0.0", - "@smithy/types": "^4.0.0", + "@aws-sdk/types": "3.893.0", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.723.0", + "node_modules/@aws-sdk/middleware-expect-continue/node_modules/@aws-sdk/types": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", + "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.723.0", - "@aws-sdk/types": "3.723.0", - "@smithy/fetch-http-handler": "^5.0.0", - "@smithy/node-http-handler": "^4.0.0", - "@smithy/property-provider": "^4.0.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/smithy-client": "^4.0.0", - "@smithy/types": "^4.0.0", - "@smithy/util-stream": "^4.0.0", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.726.0", + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.893.0.tgz", + "integrity": "sha512-2swRPpyGK6xpZwIFmmFSFKp10iuyBLZEouhrt1ycBVA8iHGmPkuJSCim6Vb+JoRKqINp5tizWeQwdg9boIxJPw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.723.0", - "@aws-sdk/credential-provider-env": "3.723.0", - "@aws-sdk/credential-provider-http": "3.723.0", - "@aws-sdk/credential-provider-process": "3.723.0", - "@aws-sdk/credential-provider-sso": "3.726.0", - "@aws-sdk/credential-provider-web-identity": "3.723.0", - "@aws-sdk/types": "3.723.0", - "@smithy/credential-provider-imds": "^4.0.0", - "@smithy/property-provider": "^4.0.0", - "@smithy/shared-ini-file-loader": "^4.0.0", - "@smithy/types": "^4.0.0", + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "3.893.0", + "@aws-sdk/types": "3.893.0", + "@smithy/is-array-buffer": "^4.1.0", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-stream": "^4.3.2", + "@smithy/util-utf8": "^4.1.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.726.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.726.0", + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@aws-sdk/types": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", + "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.723.0", - "@aws-sdk/credential-provider-http": "3.723.0", - "@aws-sdk/credential-provider-ini": "3.726.0", - "@aws-sdk/credential-provider-process": "3.723.0", - "@aws-sdk/credential-provider-sso": "3.726.0", - "@aws-sdk/credential-provider-web-identity": "3.723.0", - "@aws-sdk/types": "3.723.0", - "@smithy/credential-provider-imds": "^4.0.0", - "@smithy/property-provider": "^4.0.0", - "@smithy/shared-ini-file-loader": "^4.0.0", - "@smithy/types": "^4.0.0", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.723.0", + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.893.0.tgz", + "integrity": "sha512-qL5xYRt80ahDfj9nDYLhpCNkDinEXvjLe/Qen/Y/u12+djrR2MB4DRa6mzBCkLkdXDtf0WAoW2EZsNCfGrmOEQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.723.0", - "@aws-sdk/types": "3.723.0", - "@smithy/property-provider": "^4.0.0", - "@smithy/shared-ini-file-loader": "^4.0.0", - "@smithy/types": "^4.0.0", + "@aws-sdk/types": "3.893.0", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.726.0", + "node_modules/@aws-sdk/middleware-host-header/node_modules/@aws-sdk/types": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", + "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.726.0", - "@aws-sdk/core": "3.723.0", - "@aws-sdk/token-providers": "3.723.0", - "@aws-sdk/types": "3.723.0", - "@smithy/property-provider": "^4.0.0", - "@smithy/shared-ini-file-loader": "^4.0.0", - "@smithy/types": "^4.0.0", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { - "version": "3.723.0", + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.893.0.tgz", + "integrity": "sha512-MlbBc7Ttb1ekbeeeFBU4DeEZOLb5s0Vl4IokvO17g6yJdLk4dnvZro9zdXl3e7NXK+kFxHRBFZe55p/42mVgDA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.723.0", - "@smithy/property-provider": "^4.0.0", - "@smithy/shared-ini-file-loader": "^4.0.0", - "@smithy/types": "^4.0.0", + "@aws-sdk/types": "3.893.0", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sso-oidc": "^3.723.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.723.0", + "node_modules/@aws-sdk/middleware-location-constraint/node_modules/@aws-sdk/types": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", + "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.723.0", - "@aws-sdk/types": "3.723.0", - "@smithy/property-provider": "^4.0.0", - "@smithy/types": "^4.0.0", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.723.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.723.0", + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.893.0.tgz", + "integrity": "sha512-ZqzMecjju5zkBquSIfVfCORI/3Mge21nUY4nWaGQy+NUXehqCGG4W7AiVpiHGOcY2cGJa7xeEkYcr2E2U9U0AA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.723.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/types": "^4.0.0", + "@aws-sdk/types": "3.893.0", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/middleware-logger": { - "version": "3.723.0", + "node_modules/@aws-sdk/middleware-logger/node_modules/@aws-sdk/types": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", + "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.723.0", - "@smithy/types": "^4.0.0", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.723.0", + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.893.0.tgz", + "integrity": "sha512-H7Zotd9zUHQAr/wr3bcWHULYhEeoQrF54artgsoUGIf/9emv6LzY89QUccKIxYd6oHKNTrTyXm9F0ZZrzXNxlg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.723.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/types": "^4.0.0", + "@aws-sdk/types": "3.893.0", + "@aws/lambda-invoke-store": "^0.0.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.726.0", + "node_modules/@aws-sdk/middleware-recursion-detection/node_modules/@aws-sdk/types": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", + "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.723.0", - "@aws-sdk/types": "3.723.0", - "@aws-sdk/util-endpoints": "3.726.0", - "@smithy/core": "^3.0.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/types": "^4.0.0", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.723.0", + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.893.0.tgz", + "integrity": "sha512-J2v7jOoSlE4o416yQiuka6+cVJzyrU7mbJEQA9VFCb+TYT2cG3xQB0bDzE24QoHeonpeBDghbg/zamYMnt+GsQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.723.0", - "@smithy/node-config-provider": "^4.0.0", - "@smithy/types": "^4.0.0", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.0", + "@aws-sdk/core": "3.893.0", + "@aws-sdk/types": "3.893.0", + "@aws-sdk/util-arn-parser": "3.893.0", + "@smithy/core": "^3.11.1", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/protocol-http": "^5.2.1", + "@smithy/signature-v4": "^5.2.1", + "@smithy/smithy-client": "^4.6.3", + "@smithy/types": "^4.5.0", + "@smithy/util-config-provider": "^4.1.0", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-stream": "^4.3.2", + "@smithy/util-utf8": "^4.1.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/types": { - "version": "3.723.0", + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@aws-sdk/types": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", + "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.0.0", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/util-endpoints": { - "version": "3.726.0", + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.893.0.tgz", + "integrity": "sha512-e4ccCiAnczv9mMPheKjgKxZQN473mcup+3DPLVNnIw5GRbQoDqPSB70nUzfORKZvM7ar7xLMPxNR8qQgo1C8Rg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.723.0", - "@smithy/types": "^4.0.0", - "@smithy/util-endpoints": "^3.0.0", + "@aws-sdk/types": "3.893.0", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.723.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.723.0", - "@smithy/types": "^4.0.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.726.0", + "node_modules/@aws-sdk/middleware-ssec/node_modules/@aws-sdk/types": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", + "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.726.0", - "@aws-sdk/types": "3.723.0", - "@smithy/node-config-provider": "^4.0.0", - "@smithy/types": "^4.0.0", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/fast-xml-parser": { - "version": "4.4.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - }, - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - } - ], - "license": "MIT", - "dependencies": { - "strnum": "^1.0.5" - }, - "bin": { - "fxparser": "src/cli/cli.js" } }, - "node_modules/@aws-sdk/client-sts/node_modules/strnum": { - "version": "1.1.2", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, - "node_modules/@aws-sdk/endpoint-cache": { + "node_modules/@aws-sdk/middleware-user-agent": { "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.893.0.tgz", + "integrity": "sha512-n1vHj7bdC4ycIAKkny0rmgvgvGOIgYnGBAqfPAFPR26WuGWmCxH2cT9nQTNA+li8ofxX9F9FIFBTKkW92Pc8iQ==", "license": "Apache-2.0", "dependencies": { - "mnemonist": "0.38.3", + "@aws-sdk/core": "3.893.0", + "@aws-sdk/types": "3.893.0", + "@aws-sdk/util-endpoints": "3.893.0", + "@smithy/core": "^3.11.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/lib-dynamodb": { + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@aws-sdk/types": { "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", + "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.893.0", - "@aws-sdk/util-dynamodb": "3.893.0", - "@smithy/core": "^3.11.1", - "@smithy/smithy-client": "^4.6.3", "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-dynamodb": "^3.893.0" } }, - "node_modules/@aws-sdk/lib-dynamodb/node_modules/@aws-sdk/core": { + "node_modules/@aws-sdk/nested-clients": { "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.893.0.tgz", + "integrity": "sha512-HIUCyNtWkxvc0BmaJPUj/A0/29OapT/dzBNxr2sjgKNZgO81JuDFp+aXCmnf7vQoA2D1RzCsAIgEtfTExNFZqA==", "license": "Apache-2.0", "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.893.0", + "@aws-sdk/middleware-host-header": "3.893.0", + "@aws-sdk/middleware-logger": "3.893.0", + "@aws-sdk/middleware-recursion-detection": "3.893.0", + "@aws-sdk/middleware-user-agent": "3.893.0", + "@aws-sdk/region-config-resolver": "3.893.0", "@aws-sdk/types": "3.893.0", - "@aws-sdk/xml-builder": "3.893.0", + "@aws-sdk/util-endpoints": "3.893.0", + "@aws-sdk/util-user-agent-browser": "3.893.0", + "@aws-sdk/util-user-agent-node": "3.893.0", + "@smithy/config-resolver": "^4.2.2", "@smithy/core": "^3.11.1", + "@smithy/fetch-http-handler": "^5.2.1", + "@smithy/hash-node": "^4.1.1", + "@smithy/invalid-dependency": "^4.1.1", + "@smithy/middleware-content-length": "^4.1.1", + "@smithy/middleware-endpoint": "^4.2.3", + "@smithy/middleware-retry": "^4.2.4", + "@smithy/middleware-serde": "^4.1.1", + "@smithy/middleware-stack": "^4.1.1", "@smithy/node-config-provider": "^4.2.2", - "@smithy/property-provider": "^4.1.1", + "@smithy/node-http-handler": "^4.2.1", "@smithy/protocol-http": "^5.2.1", - "@smithy/signature-v4": "^5.2.1", "@smithy/smithy-client": "^4.6.3", "@smithy/types": "^4.5.0", + "@smithy/url-parser": "^4.1.1", "@smithy/util-base64": "^4.1.0", "@smithy/util-body-length-browser": "^4.1.0", + "@smithy/util-body-length-node": "^4.1.0", + "@smithy/util-defaults-mode-browser": "^4.1.3", + "@smithy/util-defaults-mode-node": "^4.1.3", + "@smithy/util-endpoints": "^3.1.2", "@smithy/util-middleware": "^4.1.1", + "@smithy/util-retry": "^4.1.2", "@smithy/util-utf8": "^4.1.0", - "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/lib-dynamodb/node_modules/@aws-sdk/types": { + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/types": { "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", + "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.5.0", @@ -2817,25 +2497,29 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/lib-dynamodb/node_modules/@aws-sdk/xml-builder": { + "node_modules/@aws-sdk/region-config-resolver": { "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.893.0.tgz", + "integrity": "sha512-/cJvh3Zsa+Of0Zbg7vl9wp/kZtdb40yk/2+XcroAMVPO9hPvmS9r/UOm6tO7FeX4TtkRFwWaQJiTZTgSdsPY+Q==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/types": "3.893.0", + "@smithy/node-config-provider": "^4.2.2", "@smithy/types": "^4.5.0", + "@smithy/util-config-provider": "^4.1.0", + "@smithy/util-middleware": "^4.1.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-endpoint-discovery": { + "node_modules/@aws-sdk/region-config-resolver/node_modules/@aws-sdk/types": { "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", + "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/endpoint-cache": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/protocol-http": "^5.2.1", "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, @@ -2843,10 +2527,16 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-endpoint-discovery/node_modules/@aws-sdk/types": { + "node_modules/@aws-sdk/signature-v4-multi-region": { "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.893.0.tgz", + "integrity": "sha512-pp4Bn8dL4i68P/mHgZ7sgkm8OSIpwjtGxP73oGseu9Cli0JRyJ1asTSsT60hUz3sbo+3oKk3hEobD6UxLUeGRA==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.893.0", + "@aws-sdk/types": "3.893.0", + "@smithy/protocol-http": "^5.2.1", + "@smithy/signature-v4": "^5.2.1", "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, @@ -2854,30 +2544,115 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/types": { - "version": "3.862.0", + "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/@aws-sdk/types": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", + "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.2", + "@smithy/types": "^4.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/util-dynamodb": { + "node_modules/@aws-sdk/token-providers": { "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.893.0.tgz", + "integrity": "sha512-nkzuE910TxW4pnIwJ+9xDMx5m+A8iXGM16Oa838YKsds07cgkRp7sPnpH9B8NbGK2szskAAkXfj7t1f59EKd1Q==", "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - }, + "@aws-sdk/core": "3.893.0", + "@aws-sdk/nested-clients": "3.893.0", + "@aws-sdk/types": "3.893.0", + "@smithy/property-provider": "^4.1.1", + "@smithy/shared-ini-file-loader": "^4.2.0", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/token-providers/node_modules/@aws-sdk/types": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", + "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.862.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.893.0.tgz", + "integrity": "sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-dynamodb": { + "version": "3.893.0", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, "peerDependencies": { "@aws-sdk/client-dynamodb": "^3.893.0" } }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.893.0.tgz", + "integrity": "sha512-xeMcL31jXHKyxRwB3oeNjs8YEpyvMnSYWr2OwLydgzgTr0G349AHlJHwYGCF9xiJ2C27kDxVvXV/Hpdp0p7TWw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.893.0", + "@smithy/types": "^4.5.0", + "@smithy/url-parser": "^4.1.1", + "@smithy/util-endpoints": "^3.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints/node_modules/@aws-sdk/types": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", + "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@aws-sdk/util-locate-window": { "version": "3.873.0", "license": "Apache-2.0", @@ -2888,6 +2663,81 @@ "node": ">=18.0.0" } }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.893.0.tgz", + "integrity": "sha512-PE9NtbDBW6Kgl1bG6A5fF3EPo168tnkj8TgMcT0sg4xYBWsBpq0bpJZRh+Jm5Bkwiw9IgTCLjEU7mR6xWaMB9w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.893.0", + "@smithy/types": "^4.5.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser/node_modules/@aws-sdk/types": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", + "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.893.0.tgz", + "integrity": "sha512-tTRkJo/fth9NPJ2AO/XLuJWVsOhbhejQRLyP0WXG3z0Waa5IWK5YBxBC1tWWATUCwsN748JQXU03C1aF9cRD9w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.893.0", + "@aws-sdk/types": "3.893.0", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/util-user-agent-node/node_modules/@aws-sdk/types": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", + "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.893.0.tgz", + "integrity": "sha512-qKkJ2E0hU60iq0o2+hBSIWS8sf34xhqiRRGw5nbRhwEnE2MsWsWBpRoysmr32uq9dHMWUzII0c/fS29+wOSdMA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@aws/lambda-invoke-store": { "version": "0.0.1", "license": "Apache-2.0", @@ -2950,7 +2800,6 @@ "version": "7.28.4", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -3758,7 +3607,6 @@ "version": "7.0.3", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.1", @@ -5306,7 +5154,6 @@ "version": "14.1.2", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" @@ -5439,7 +5286,6 @@ "version": "8.44.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.44.0", "@typescript-eslint/types": "8.44.0", @@ -5647,7 +5493,6 @@ "version": "8.15.0", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5938,7 +5783,6 @@ "node_modules/aws-xray-sdk-core": { "version": "3.10.3", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -6135,7 +5979,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001737", "electron-to-chromium": "^1.5.211", @@ -6318,7 +6161,6 @@ "version": "6.0.1", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -7974,7 +7816,6 @@ "version": "9.36.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -10901,7 +10742,6 @@ "version": "4.3.0", "dev": true, "license": "MIT", - "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -14462,7 +14302,6 @@ "dev": true, "inBundle": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -15920,7 +15759,6 @@ "version": "4.52.2", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -16057,7 +15895,6 @@ "version": "24.2.9", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^13.0.0-beta.1", "@semantic-release/error": "^4.0.0", @@ -16796,7 +16633,6 @@ "version": "21.0.0", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@sinonjs/commons": "^3.0.1", "@sinonjs/fake-timers": "^13.0.5", @@ -17601,7 +17437,6 @@ "version": "5.9.2", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -17689,7 +17524,6 @@ "node_modules/unified": { "version": "11.0.5", "license": "MIT", - "peer": true, "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", @@ -19291,7 +19125,6 @@ "packages/spacecat-shared-ahrefs-client/node_modules/aws-xray-sdk-core": { "version": "3.10.2", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -20962,7 +20795,6 @@ "packages/spacecat-shared-athena-client/node_modules/@aws-sdk/client-dynamodb": { "version": "3.859.0", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -23197,7 +23029,6 @@ "packages/spacecat-shared-athena-client/node_modules/aws-xray-sdk-core": { "version": "3.10.2", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -25145,7 +24976,6 @@ "packages/spacecat-shared-brand-client/node_modules/aws-xray-sdk-core": { "version": "3.10.2", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -26594,7 +26424,6 @@ "packages/spacecat-shared-content-client/node_modules/@aws-sdk/client-dynamodb": { "version": "3.859.0", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -28379,7 +28208,7 @@ }, "packages/spacecat-shared-data-access": { "name": "@adobe/spacecat-shared-data-access", - "version": "2.71.0", + "version": "2.71.1", "license": "Apache-2.0", "dependencies": { "@adobe/spacecat-shared-utils": "1.49.0", @@ -32177,7 +32006,6 @@ "packages/spacecat-shared-data-access/node_modules/aws-xray-sdk-core": { "version": "3.10.2", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -32335,7 +32163,7 @@ }, "packages/spacecat-shared-google-client": { "name": "@adobe/spacecat-shared-google-client", - "version": "1.4.49", + "version": "1.4.50", "license": "Apache-2.0", "dependencies": { "@adobe/fetch": "4.2.3", @@ -32674,7 +32502,6 @@ "packages/spacecat-shared-google-client/node_modules/@adobe/spacecat-shared-data-access/node_modules/@aws-sdk/client-sso-oidc": { "version": "3.716.0", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -32726,7 +32553,6 @@ "packages/spacecat-shared-google-client/node_modules/@adobe/spacecat-shared-data-access/node_modules/@aws-sdk/client-sts": { "version": "3.716.0", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -33829,7 +33655,6 @@ "packages/spacecat-shared-google-client/node_modules/@adobe/spacecat-shared-http-utils/node_modules/@aws-sdk/client-sso-oidc": { "version": "3.716.0", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -33881,7 +33706,6 @@ "packages/spacecat-shared-google-client/node_modules/@adobe/spacecat-shared-http-utils/node_modules/@aws-sdk/client-sts": { "version": "3.716.0", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -35133,7 +34957,6 @@ "packages/spacecat-shared-google-client/node_modules/@aws-sdk/client-dynamodb": { "version": "3.721.0", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -37222,7 +37045,6 @@ "packages/spacecat-shared-google-client/node_modules/@aws-sdk/client-sts": { "version": "3.721.0", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -40115,7 +39937,6 @@ "packages/spacecat-shared-google-client/node_modules/aws-xray-sdk-core": { "version": "3.10.2", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -40613,7 +40434,6 @@ "packages/spacecat-shared-gpt-client/node_modules/@adobe/spacecat-shared-ims-client/node_modules/@aws-sdk/client-sso-oidc": { "version": "3.721.0", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -40665,7 +40485,6 @@ "packages/spacecat-shared-gpt-client/node_modules/@adobe/spacecat-shared-ims-client/node_modules/@aws-sdk/client-sts": { "version": "3.721.0", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -42565,7 +42384,6 @@ "packages/spacecat-shared-gpt-client/node_modules/aws-xray-sdk-core": { "version": "3.10.2", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -43610,7 +43428,6 @@ "packages/spacecat-shared-http-utils/node_modules/@aws-sdk/client-dynamodb": { "version": "3.859.0", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -45701,7 +45518,6 @@ "packages/spacecat-shared-http-utils/node_modules/aws-xray-sdk-core": { "version": "3.10.2", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -46605,7 +46421,6 @@ "packages/spacecat-shared-ims-client/node_modules/aws-xray-sdk-core": { "version": "3.10.2", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -48422,7 +48237,6 @@ "packages/spacecat-shared-rum-api-client/node_modules/@aws-sdk/client-dynamodb": { "version": "3.859.0", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -50657,7 +50471,6 @@ "packages/spacecat-shared-rum-api-client/node_modules/aws-xray-sdk-core": { "version": "3.10.2", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -51598,7 +51411,6 @@ "packages/spacecat-shared-scrape-client/node_modules/@aws-sdk/client-dynamodb": { "version": "3.859.0", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -54642,7 +54454,6 @@ "packages/spacecat-shared-slack-client/node_modules/aws-xray-sdk-core": { "version": "3.10.2", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -56299,7 +56110,6 @@ "packages/spacecat-shared-tier-client/node_modules/aws-xray-sdk-core": { "version": "3.10.2", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -56410,6 +56220,29 @@ ], "license": "MIT" }, + "packages/spacecat-shared-tokowaka-client": { + "name": "@adobe/spacecat-shared-tokowaka-client", + "version": "1.0.0", + "license": "Apache-2.0", + "dependencies": { + "@adobe/spacecat-shared-utils": "*", + "@aws-sdk/client-cloudfront": "3.893.0", + "@aws-sdk/client-s3": "3.893.0" + }, + "devDependencies": { + "c8": "^10.1.3", + "chai": "^6.0.1", + "eslint": "^9.36.0", + "mocha": "^11.7.2", + "nock": "^14.0.10", + "sinon": "^21.0.0", + "sinon-chai": "^4.0.1" + }, + "engines": { + "node": ">=22.0.0 <23.0.0", + "npm": ">=10.9.0 <12.0.0" + } + }, "packages/spacecat-shared-utils": { "name": "@adobe/spacecat-shared-utils", "version": "1.59.4", @@ -57433,1065 +57266,388 @@ "node": ">=18.0.0" } }, - "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.826.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.826.0", - "@aws-sdk/types": "3.821.0", - "@smithy/fetch-http-handler": "^5.0.4", - "@smithy/node-http-handler": "^4.0.6", - "@smithy/property-provider": "^4.0.4", - "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.3", - "@smithy/types": "^4.3.1", - "@smithy/util-stream": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.828.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.826.0", - "@aws-sdk/credential-provider-env": "3.826.0", - "@aws-sdk/credential-provider-http": "3.826.0", - "@aws-sdk/credential-provider-process": "3.826.0", - "@aws-sdk/credential-provider-sso": "3.828.0", - "@aws-sdk/credential-provider-web-identity": "3.828.0", - "@aws-sdk/nested-clients": "3.828.0", - "@aws-sdk/types": "3.821.0", - "@smithy/credential-provider-imds": "^4.0.6", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.828.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.826.0", - "@aws-sdk/credential-provider-http": "3.826.0", - "@aws-sdk/credential-provider-ini": "3.828.0", - "@aws-sdk/credential-provider-process": "3.826.0", - "@aws-sdk/credential-provider-sso": "3.828.0", - "@aws-sdk/credential-provider-web-identity": "3.828.0", - "@aws-sdk/types": "3.821.0", - "@smithy/credential-provider-imds": "^4.0.6", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.826.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.826.0", - "@aws-sdk/types": "3.821.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.828.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sso": "3.828.0", - "@aws-sdk/core": "3.826.0", - "@aws-sdk/token-providers": "3.828.0", - "@aws-sdk/types": "3.821.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.828.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.826.0", - "@aws-sdk/nested-clients": "3.828.0", - "@aws-sdk/types": "3.821.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.821.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.821.0", - "@aws-sdk/util-arn-parser": "3.804.0", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "@smithy/util-config-provider": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.821.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.821.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.826.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/crc32": "5.2.0", - "@aws-crypto/crc32c": "5.2.0", - "@aws-crypto/util": "5.2.0", - "@aws-sdk/core": "3.826.0", - "@aws-sdk/types": "3.821.0", - "@smithy/is-array-buffer": "^4.0.0", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-stream": "^4.2.2", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.821.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.821.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/middleware-location-constraint": { - "version": "3.821.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.821.0", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/middleware-logger": { - "version": "3.821.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.821.0", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.821.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.821.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.826.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.826.0", - "@aws-sdk/types": "3.821.0", - "@aws-sdk/util-arn-parser": "3.804.0", - "@smithy/core": "^3.5.3", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/protocol-http": "^5.1.2", - "@smithy/signature-v4": "^5.1.2", - "@smithy/smithy-client": "^4.4.3", - "@smithy/types": "^4.3.1", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-stream": "^4.2.2", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/middleware-sdk-sqs": { - "version": "3.826.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.821.0", - "@smithy/smithy-client": "^4.4.3", - "@smithy/types": "^4.3.1", - "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/middleware-ssec": { - "version": "3.821.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.821.0", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.828.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.826.0", - "@aws-sdk/types": "3.821.0", - "@aws-sdk/util-endpoints": "3.828.0", - "@smithy/core": "^3.5.3", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/nested-clients": { - "version": "3.828.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.826.0", - "@aws-sdk/middleware-host-header": "3.821.0", - "@aws-sdk/middleware-logger": "3.821.0", - "@aws-sdk/middleware-recursion-detection": "3.821.0", - "@aws-sdk/middleware-user-agent": "3.828.0", - "@aws-sdk/region-config-resolver": "3.821.0", - "@aws-sdk/types": "3.821.0", - "@aws-sdk/util-endpoints": "3.828.0", - "@aws-sdk/util-user-agent-browser": "3.821.0", - "@aws-sdk/util-user-agent-node": "3.828.0", - "@smithy/config-resolver": "^4.1.4", - "@smithy/core": "^3.5.3", - "@smithy/fetch-http-handler": "^5.0.4", - "@smithy/hash-node": "^4.0.4", - "@smithy/invalid-dependency": "^4.0.4", - "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.11", - "@smithy/middleware-retry": "^4.1.12", - "@smithy/middleware-serde": "^4.0.8", - "@smithy/middleware-stack": "^4.0.4", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/node-http-handler": "^4.0.6", - "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.3", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.19", - "@smithy/util-defaults-mode-node": "^4.0.19", - "@smithy/util-endpoints": "^3.0.6", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.5", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.821.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.821.0", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.826.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.826.0", - "@aws-sdk/types": "3.821.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/signature-v4": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/token-providers": { - "version": "3.828.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.826.0", - "@aws-sdk/nested-clients": "3.828.0", - "@aws-sdk/types": "3.821.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/types": { - "version": "3.821.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/util-arn-parser": { - "version": "3.804.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/util-endpoints": { - "version": "3.828.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.821.0", - "@smithy/types": "^4.3.1", - "@smithy/util-endpoints": "^3.0.6", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.821.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.821.0", - "@smithy/types": "^4.3.1", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.828.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "3.828.0", - "@aws-sdk/types": "3.821.0", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/debug": { - "version": "4.4.1", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/fast-xml-parser": { - "version": "4.4.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - }, - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - } - ], - "license": "MIT", - "dependencies": { - "strnum": "^1.0.5" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, - "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/http-cache-semantics": { - "version": "4.2.0", - "license": "BSD-2-Clause" - }, - "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/uuid": { - "version": "11.1.0", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/esm/bin/uuid" - } - }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-dynamodb": { - "version": "3.859.0", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.858.0", - "@aws-sdk/credential-provider-node": "3.859.0", - "@aws-sdk/middleware-endpoint-discovery": "3.840.0", - "@aws-sdk/middleware-host-header": "3.840.0", - "@aws-sdk/middleware-logger": "3.840.0", - "@aws-sdk/middleware-recursion-detection": "3.840.0", - "@aws-sdk/middleware-user-agent": "3.858.0", - "@aws-sdk/region-config-resolver": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@aws-sdk/util-endpoints": "3.848.0", - "@aws-sdk/util-user-agent-browser": "3.840.0", - "@aws-sdk/util-user-agent-node": "3.858.0", - "@smithy/config-resolver": "^4.1.4", - "@smithy/core": "^3.7.2", - "@smithy/fetch-http-handler": "^5.1.0", - "@smithy/hash-node": "^4.0.4", - "@smithy/invalid-dependency": "^4.0.4", - "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.17", - "@smithy/middleware-retry": "^4.1.18", - "@smithy/middleware-serde": "^4.0.8", - "@smithy/middleware-stack": "^4.0.4", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/node-http-handler": "^4.1.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.9", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.25", - "@smithy/util-defaults-mode-node": "^4.0.25", - "@smithy/util-endpoints": "^3.0.6", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.6", - "@smithy/util-utf8": "^4.0.0", - "@smithy/util-waiter": "^4.0.6", - "@types/uuid": "^9.0.1", - "tslib": "^2.6.2", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-s3": { - "version": "3.893.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha1-browser": "5.2.0", - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.893.0", - "@aws-sdk/credential-provider-node": "3.893.0", - "@aws-sdk/middleware-bucket-endpoint": "3.893.0", - "@aws-sdk/middleware-expect-continue": "3.893.0", - "@aws-sdk/middleware-flexible-checksums": "3.893.0", - "@aws-sdk/middleware-host-header": "3.893.0", - "@aws-sdk/middleware-location-constraint": "3.893.0", - "@aws-sdk/middleware-logger": "3.893.0", - "@aws-sdk/middleware-recursion-detection": "3.893.0", - "@aws-sdk/middleware-sdk-s3": "3.893.0", - "@aws-sdk/middleware-ssec": "3.893.0", - "@aws-sdk/middleware-user-agent": "3.893.0", - "@aws-sdk/region-config-resolver": "3.893.0", - "@aws-sdk/signature-v4-multi-region": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@aws-sdk/util-endpoints": "3.893.0", - "@aws-sdk/util-user-agent-browser": "3.893.0", - "@aws-sdk/util-user-agent-node": "3.893.0", - "@aws-sdk/xml-builder": "3.893.0", - "@smithy/config-resolver": "^4.2.2", - "@smithy/core": "^3.11.1", - "@smithy/eventstream-serde-browser": "^4.1.1", - "@smithy/eventstream-serde-config-resolver": "^4.2.1", - "@smithy/eventstream-serde-node": "^4.1.1", - "@smithy/fetch-http-handler": "^5.2.1", - "@smithy/hash-blob-browser": "^4.1.1", - "@smithy/hash-node": "^4.1.1", - "@smithy/hash-stream-node": "^4.1.1", - "@smithy/invalid-dependency": "^4.1.1", - "@smithy/md5-js": "^4.1.1", - "@smithy/middleware-content-length": "^4.1.1", - "@smithy/middleware-endpoint": "^4.2.3", - "@smithy/middleware-retry": "^4.2.4", - "@smithy/middleware-serde": "^4.1.1", - "@smithy/middleware-stack": "^4.1.1", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/node-http-handler": "^4.2.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/smithy-client": "^4.6.3", - "@smithy/types": "^4.5.0", - "@smithy/url-parser": "^4.1.1", - "@smithy/util-base64": "^4.1.0", - "@smithy/util-body-length-browser": "^4.1.0", - "@smithy/util-body-length-node": "^4.1.0", - "@smithy/util-defaults-mode-browser": "^4.1.3", - "@smithy/util-defaults-mode-node": "^4.1.3", - "@smithy/util-endpoints": "^3.1.2", - "@smithy/util-middleware": "^4.1.1", - "@smithy/util-retry": "^4.1.2", - "@smithy/util-stream": "^4.3.2", - "@smithy/util-utf8": "^4.1.0", - "@smithy/util-waiter": "^4.1.1", - "@types/uuid": "^9.0.1", - "tslib": "^2.6.2", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/client-sso": { - "version": "3.893.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.893.0", - "@aws-sdk/middleware-host-header": "3.893.0", - "@aws-sdk/middleware-logger": "3.893.0", - "@aws-sdk/middleware-recursion-detection": "3.893.0", - "@aws-sdk/middleware-user-agent": "3.893.0", - "@aws-sdk/region-config-resolver": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@aws-sdk/util-endpoints": "3.893.0", - "@aws-sdk/util-user-agent-browser": "3.893.0", - "@aws-sdk/util-user-agent-node": "3.893.0", - "@smithy/config-resolver": "^4.2.2", - "@smithy/core": "^3.11.1", - "@smithy/fetch-http-handler": "^5.2.1", - "@smithy/hash-node": "^4.1.1", - "@smithy/invalid-dependency": "^4.1.1", - "@smithy/middleware-content-length": "^4.1.1", - "@smithy/middleware-endpoint": "^4.2.3", - "@smithy/middleware-retry": "^4.2.4", - "@smithy/middleware-serde": "^4.1.1", - "@smithy/middleware-stack": "^4.1.1", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/node-http-handler": "^4.2.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/smithy-client": "^4.6.3", - "@smithy/types": "^4.5.0", - "@smithy/url-parser": "^4.1.1", - "@smithy/util-base64": "^4.1.0", - "@smithy/util-body-length-browser": "^4.1.0", - "@smithy/util-body-length-node": "^4.1.0", - "@smithy/util-defaults-mode-browser": "^4.1.3", - "@smithy/util-defaults-mode-node": "^4.1.3", - "@smithy/util-endpoints": "^3.1.2", - "@smithy/util-middleware": "^4.1.1", - "@smithy/util-retry": "^4.1.2", - "@smithy/util-utf8": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/core": { - "version": "3.893.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.893.0", - "@aws-sdk/xml-builder": "3.893.0", - "@smithy/core": "^3.11.1", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/property-provider": "^4.1.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/signature-v4": "^5.2.1", - "@smithy/smithy-client": "^4.6.3", - "@smithy/types": "^4.5.0", - "@smithy/util-base64": "^4.1.0", - "@smithy/util-body-length-browser": "^4.1.0", - "@smithy/util-middleware": "^4.1.1", - "@smithy/util-utf8": "^4.1.0", - "fast-xml-parser": "5.2.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.893.0", + "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.826.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/property-provider": "^4.1.1", - "@smithy/types": "^4.5.0", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/types": "3.821.0", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.893.0", + "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.828.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/fetch-http-handler": "^5.2.1", - "@smithy/node-http-handler": "^4.2.1", - "@smithy/property-provider": "^4.1.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/smithy-client": "^4.6.3", - "@smithy/types": "^4.5.0", - "@smithy/util-stream": "^4.3.2", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/credential-provider-env": "3.826.0", + "@aws-sdk/credential-provider-http": "3.826.0", + "@aws-sdk/credential-provider-process": "3.826.0", + "@aws-sdk/credential-provider-sso": "3.828.0", + "@aws-sdk/credential-provider-web-identity": "3.828.0", + "@aws-sdk/nested-clients": "3.828.0", + "@aws-sdk/types": "3.821.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.893.0", + "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.828.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.893.0", - "@aws-sdk/credential-provider-env": "3.893.0", - "@aws-sdk/credential-provider-http": "3.893.0", - "@aws-sdk/credential-provider-process": "3.893.0", - "@aws-sdk/credential-provider-sso": "3.893.0", - "@aws-sdk/credential-provider-web-identity": "3.893.0", - "@aws-sdk/nested-clients": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/credential-provider-imds": "^4.1.2", - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/credential-provider-env": "3.826.0", + "@aws-sdk/credential-provider-http": "3.826.0", + "@aws-sdk/credential-provider-ini": "3.828.0", + "@aws-sdk/credential-provider-process": "3.826.0", + "@aws-sdk/credential-provider-sso": "3.828.0", + "@aws-sdk/credential-provider-web-identity": "3.828.0", + "@aws-sdk/types": "3.821.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.893.0", + "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.826.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.893.0", - "@aws-sdk/credential-provider-http": "3.893.0", - "@aws-sdk/credential-provider-ini": "3.893.0", - "@aws-sdk/credential-provider-process": "3.893.0", - "@aws-sdk/credential-provider-sso": "3.893.0", - "@aws-sdk/credential-provider-web-identity": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/credential-provider-imds": "^4.1.2", - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.893.0", + "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.828.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/client-sso": "3.828.0", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/token-providers": "3.828.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.893.0", + "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.828.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.893.0", - "@aws-sdk/core": "3.893.0", - "@aws-sdk/token-providers": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/nested-clients": "3.828.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.893.0", + "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.821.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.893.0", - "@aws-sdk/nested-clients": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-arn-parser": "3.804.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.893.0", + "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.821.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", - "@aws-sdk/util-arn-parser": "3.893.0", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", - "@smithy/util-config-provider": "^4.1.0", + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.893.0", + "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.826.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/types": "3.821.0", + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.893.0", + "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.821.0", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/crc32": "5.2.0", - "@aws-crypto/crc32c": "5.2.0", - "@aws-crypto/util": "5.2.0", - "@aws-sdk/core": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/is-array-buffer": "^4.1.0", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", - "@smithy/util-middleware": "^4.1.1", - "@smithy/util-stream": "^4.3.2", - "@smithy/util-utf8": "^4.1.0", + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.893.0", + "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.821.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-location-constraint": { - "version": "3.893.0", + "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/middleware-logger": { + "version": "3.821.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-logger": { - "version": "3.893.0", + "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.821.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.893.0", + "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.826.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", - "@aws/lambda-invoke-store": "^0.0.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-arn-parser": "3.804.0", + "@smithy/core": "^3.5.3", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.893.0", + "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/middleware-sdk-sqs": { + "version": "3.826.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@aws-sdk/util-arn-parser": "3.893.0", - "@smithy/core": "^3.11.1", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/protocol-http": "^5.2.1", - "@smithy/signature-v4": "^5.2.1", - "@smithy/smithy-client": "^4.6.3", - "@smithy/types": "^4.5.0", - "@smithy/util-config-provider": "^4.1.0", - "@smithy/util-middleware": "^4.1.1", - "@smithy/util-stream": "^4.3.2", - "@smithy/util-utf8": "^4.1.0", + "@aws-sdk/types": "3.821.0", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-ssec": { - "version": "3.893.0", + "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/middleware-ssec": { + "version": "3.821.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.893.0", + "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.828.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@aws-sdk/util-endpoints": "3.893.0", - "@smithy/core": "^3.11.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@smithy/core": "^3.5.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/nested-clients": { - "version": "3.893.0", + "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/nested-clients": { + "version": "3.828.0", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.893.0", - "@aws-sdk/middleware-host-header": "3.893.0", - "@aws-sdk/middleware-logger": "3.893.0", - "@aws-sdk/middleware-recursion-detection": "3.893.0", - "@aws-sdk/middleware-user-agent": "3.893.0", - "@aws-sdk/region-config-resolver": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@aws-sdk/util-endpoints": "3.893.0", - "@aws-sdk/util-user-agent-browser": "3.893.0", - "@aws-sdk/util-user-agent-node": "3.893.0", - "@smithy/config-resolver": "^4.2.2", - "@smithy/core": "^3.11.1", - "@smithy/fetch-http-handler": "^5.2.1", - "@smithy/hash-node": "^4.1.1", - "@smithy/invalid-dependency": "^4.1.1", - "@smithy/middleware-content-length": "^4.1.1", - "@smithy/middleware-endpoint": "^4.2.3", - "@smithy/middleware-retry": "^4.2.4", - "@smithy/middleware-serde": "^4.1.1", - "@smithy/middleware-stack": "^4.1.1", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/node-http-handler": "^4.2.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/smithy-client": "^4.6.3", - "@smithy/types": "^4.5.0", - "@smithy/url-parser": "^4.1.1", - "@smithy/util-base64": "^4.1.0", - "@smithy/util-body-length-browser": "^4.1.0", - "@smithy/util-body-length-node": "^4.1.0", - "@smithy/util-defaults-mode-browser": "^4.1.3", - "@smithy/util-defaults-mode-node": "^4.1.3", - "@smithy/util-endpoints": "^3.1.2", - "@smithy/util-middleware": "^4.1.1", - "@smithy/util-retry": "^4.1.2", - "@smithy/util-utf8": "^4.1.0", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.828.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.828.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.5.3", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.11", + "@smithy/middleware-retry": "^4.1.12", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.19", + "@smithy/util-defaults-mode-node": "^4.0.19", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.5", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.893.0", + "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.821.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/types": "^4.5.0", - "@smithy/util-config-provider": "^4.1.0", - "@smithy/util-middleware": "^4.1.1", + "@aws-sdk/types": "3.821.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.893.0", + "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.826.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/protocol-http": "^5.2.1", - "@smithy/signature-v4": "^5.2.1", - "@smithy/types": "^4.5.0", + "@aws-sdk/middleware-sdk-s3": "3.826.0", + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/token-providers": { - "version": "3.893.0", + "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/token-providers": { + "version": "3.828.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.893.0", - "@aws-sdk/nested-clients": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.2.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/core": "3.826.0", + "@aws-sdk/nested-clients": "3.828.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/types": { - "version": "3.893.0", + "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/types": { + "version": "3.821.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.5.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-arn-parser": { - "version": "3.893.0", + "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/util-arn-parser": { + "version": "3.804.0", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -58500,38 +57656,37 @@ "node": ">=18.0.0" } }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-endpoints": { - "version": "3.893.0", + "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/util-endpoints": { + "version": "3.828.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", - "@smithy/types": "^4.5.0", - "@smithy/url-parser": "^4.1.1", - "@smithy/util-endpoints": "^3.1.2", + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "@smithy/util-endpoints": "^3.0.6", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.893.0", + "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.821.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.893.0", - "@smithy/types": "^4.5.0", + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.893.0", + "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.828.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.893.0", - "@aws-sdk/types": "3.893.0", - "@smithy/node-config-provider": "^4.2.2", - "@smithy/types": "^4.5.0", + "@aws-sdk/middleware-user-agent": "3.828.0", + "@aws-sdk/types": "3.821.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { @@ -58546,12 +57701,103 @@ } } }, - "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/xml-builder": { - "version": "3.893.0", + "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/debug": { + "version": "4.4.1", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/fast-xml-parser": { + "version": "4.4.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/http-cache-semantics": { + "version": "4.2.0", + "license": "BSD-2-Clause" + }, + "packages/spacecat-shared-utils/node_modules/@adobe/spacecat-shared-utils/node_modules/uuid": { + "version": "11.1.0", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "packages/spacecat-shared-utils/node_modules/@aws-sdk/client-dynamodb": { + "version": "3.859.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.858.0", + "@aws-sdk/credential-provider-node": "3.859.0", + "@aws-sdk/middleware-endpoint-discovery": "3.840.0", + "@aws-sdk/middleware-host-header": "3.840.0", + "@aws-sdk/middleware-logger": "3.840.0", + "@aws-sdk/middleware-recursion-detection": "3.840.0", + "@aws-sdk/middleware-user-agent": "3.858.0", + "@aws-sdk/region-config-resolver": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.848.0", + "@aws-sdk/util-user-agent-browser": "3.840.0", + "@aws-sdk/util-user-agent-node": "3.858.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.7.2", + "@smithy/fetch-http-handler": "^5.1.0", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.17", + "@smithy/middleware-retry": "^4.1.18", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.1.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.9", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.25", + "@smithy/util-defaults-mode-node": "^4.0.25", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", + "@smithy/util-waiter": "^4.0.6", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { "node": ">=18.0.0" @@ -59809,7 +59055,6 @@ "packages/spacecat-shared-utils/node_modules/aws-xray-sdk-core": { "version": "3.10.2", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", From 1dc4d6eb58f227e4a86deb617717249073610239 Mon Sep 17 00:00:00 2001 From: Divyansh Pratap Date: Fri, 17 Oct 2025 17:29:23 +0530 Subject: [PATCH 11/20] fix: tests --- packages/spacecat-shared-tokowaka-client/src/index.js | 6 ++---- packages/spacecat-shared-tokowaka-client/test/index.test.js | 5 +---- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/spacecat-shared-tokowaka-client/src/index.js b/packages/spacecat-shared-tokowaka-client/src/index.js index 0387aae6f..c82f3c088 100644 --- a/packages/spacecat-shared-tokowaka-client/src/index.js +++ b/packages/spacecat-shared-tokowaka-client/src/index.js @@ -299,7 +299,7 @@ class TokowakaClient { // Upload to S3 this.log.info(`Uploading Tokowaka config for ${eligibleSuggestions.length} suggestions`); - const s3Key = await this.uploadConfig(apiKey, config); + const s3Path = await this.uploadConfig(apiKey, config); // Invalidate CDN cache (non-blocking, failures are logged but don't fail deployment) const cdnInvalidationResult = await this.invalidateCdnCache( @@ -308,9 +308,7 @@ class TokowakaClient { ); return { - tokowakaApiKey: apiKey, - s3Key, - config, + s3Path, cdnInvalidation: cdnInvalidationResult, succeededSuggestions: eligibleSuggestions, failedSuggestions: ineligibleSuggestions, diff --git a/packages/spacecat-shared-tokowaka-client/test/index.test.js b/packages/spacecat-shared-tokowaka-client/test/index.test.js index 82b3d00f6..5602f041d 100644 --- a/packages/spacecat-shared-tokowaka-client/test/index.test.js +++ b/packages/spacecat-shared-tokowaka-client/test/index.test.js @@ -387,10 +387,7 @@ describe('TokowakaClient', () => { mockSuggestions, ); - expect(result).to.have.property('tokowakaApiKey', 'test-api-key-123'); - expect(result).to.have.property('s3Key', 'opportunities/test-api-key-123'); - expect(result).to.have.property('config'); - expect(result.config.siteId).to.equal('site-123'); + expect(result).to.have.property('s3Path', 'opportunities/test-api-key-123'); expect(s3Client.send).to.have.been.calledOnce; }); From ed273064013f4e03dfd74c6dcff93ce651be857c Mon Sep 17 00:00:00 2001 From: Divyansh Pratap Date: Fri, 17 Oct 2025 17:50:34 +0530 Subject: [PATCH 12/20] fix: tests --- packages/spacecat-shared-tokowaka-client/src/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/spacecat-shared-tokowaka-client/src/index.js b/packages/spacecat-shared-tokowaka-client/src/index.js index c82f3c088..448518740 100644 --- a/packages/spacecat-shared-tokowaka-client/src/index.js +++ b/packages/spacecat-shared-tokowaka-client/src/index.js @@ -184,20 +184,20 @@ class TokowakaClient { throw this.#createError('Config object is required', HTTP_BAD_REQUEST); } - const s3Key = `opportunities/${apiKey}`; + const s3Path = `opportunities/${apiKey}`; try { const command = new PutObjectCommand({ Bucket: this.bucketName, - Key: s3Key, + Key: s3Path, Body: JSON.stringify(config, null, 2), ContentType: 'application/json', }); await this.s3Client.send(command); - this.log.info(`Successfully uploaded Tokowaka config to s3://${this.bucketName}/${s3Key}`); + this.log.info(`Successfully uploaded Tokowaka config to s3://${this.bucketName}/${s3Path}`); - return s3Key; + return s3Path; } catch (error) { this.log.error(`Failed to upload Tokowaka config to S3: ${error.message}`, error); throw this.#createError(`S3 upload failed: ${error.message}`, HTTP_INTERNAL_SERVER_ERROR); From 9fdd78108fa62d419e3afbcd383e17611cb4ddf1 Mon Sep 17 00:00:00 2001 From: Divyansh Pratap Date: Fri, 17 Oct 2025 20:29:29 +0530 Subject: [PATCH 13/20] fix: tests --- packages/spacecat-shared-tokowaka-client/src/index.js | 6 +----- packages/spacecat-shared-tokowaka-client/test/index.test.js | 2 -- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/spacecat-shared-tokowaka-client/src/index.js b/packages/spacecat-shared-tokowaka-client/src/index.js index 448518740..c9ced8b83 100644 --- a/packages/spacecat-shared-tokowaka-client/src/index.js +++ b/packages/spacecat-shared-tokowaka-client/src/index.js @@ -244,7 +244,7 @@ class TokowakaClient { */ async deploySuggestions(site, opportunity, suggestions) { // Get site's Tokowaka API key - const { apiKey } = site.getConfig().getTokowakaConfig() || {}; + const { apiKey } = site.getConfig()?.getTokowakaConfig() || {}; if (!hasText(apiKey)) { throw this.#createError( @@ -284,10 +284,6 @@ class TokowakaClient { if (eligibleSuggestions.length === 0) { this.log.warn('No eligible suggestions to deploy'); return { - tokowakaApiKey: apiKey, - s3Key: null, - config: null, - cdnInvalidation: null, succeededSuggestions: [], failedSuggestions: ineligibleSuggestions, }; diff --git a/packages/spacecat-shared-tokowaka-client/test/index.test.js b/packages/spacecat-shared-tokowaka-client/test/index.test.js index 5602f041d..6fe988966 100644 --- a/packages/spacecat-shared-tokowaka-client/test/index.test.js +++ b/packages/spacecat-shared-tokowaka-client/test/index.test.js @@ -467,8 +467,6 @@ describe('TokowakaClient', () => { mockSuggestions, ); - expect(result.s3Key).to.be.null; - expect(result.config).to.be.null; expect(result.succeededSuggestions).to.have.length(0); expect(result.failedSuggestions).to.have.length(1); expect(log.warn).to.have.been.calledWith('No eligible suggestions to deploy'); From dba9bb432ab21e7aada4443fac74a75332a2239d Mon Sep 17 00:00:00 2001 From: Divyansh Pratap Date: Fri, 17 Oct 2025 20:33:44 +0530 Subject: [PATCH 14/20] fix: tests --- packages/spacecat-shared-tokowaka-client/src/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/spacecat-shared-tokowaka-client/src/index.js b/packages/spacecat-shared-tokowaka-client/src/index.js index c9ced8b83..d2d2e64a0 100644 --- a/packages/spacecat-shared-tokowaka-client/src/index.js +++ b/packages/spacecat-shared-tokowaka-client/src/index.js @@ -171,12 +171,12 @@ class TokowakaClient { /** * Uploads Tokowaka configuration to S3 - * @param {string} apiKey - Tokowaka API key (used as S3 key prefix) + * @param {string} siteTokowakaKey - Tokowaka API key (used as S3 key prefix) * @param {Object} config - Tokowaka configuration object * @returns {Promise} - S3 key of uploaded config */ - async uploadConfig(apiKey, config) { - if (!hasText(apiKey)) { + async uploadConfig(siteTokowakaKey, config) { + if (!hasText(siteTokowakaKey)) { throw this.#createError('Tokowaka API key is required', HTTP_BAD_REQUEST); } @@ -184,7 +184,7 @@ class TokowakaClient { throw this.#createError('Config object is required', HTTP_BAD_REQUEST); } - const s3Path = `opportunities/${apiKey}`; + const s3Path = `opportunities/${siteTokowakaKey}`; try { const command = new PutObjectCommand({ From 8eac4637e39f958ecc1d556d2baa9aeddfeb57a4 Mon Sep 17 00:00:00 2001 From: Divyansh Pratap Date: Fri, 24 Oct 2025 00:42:12 +0530 Subject: [PATCH 15/20] fix: add opportunity deployment granularity --- package-lock.json | 3612 +++-------------- .../spacecat-shared-tokowaka-client/README.md | 472 +-- .../package.json | 2 +- .../src/index.d.ts | 10 + .../src/index.js | 138 +- .../test/index.test.js | 452 ++- 6 files changed, 1245 insertions(+), 3441 deletions(-) diff --git a/package-lock.json b/package-lock.json index f2fb163a4..268629c5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -271,75 +271,6 @@ "@adobe/micromark-extension-gridtables": "2.0.4" } }, - "node_modules/@adobe/rum-distiller": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/@adobe/rum-distiller/-/rum-distiller-1.20.0.tgz", - "integrity": "sha512-U8/i3MW0E7OYALOaHrMbRQWoxhDOCDHpLTFXXAnUTAYLFlyBePuWC3RHTGnUrTErI6B80DjTTo4ywj3Y8bazVg==", - "license": "Apache-2.0" - }, - "node_modules/@adobe/spacecat-helix-content-sdk": { - "version": "1.4.24", - "resolved": "https://registry.npmjs.org/@adobe/spacecat-helix-content-sdk/-/spacecat-helix-content-sdk-1.4.24.tgz", - "integrity": "sha512-RzWPWv/uP5yhbCGGKKxf/oSVxG5GXTZ2b1DakxiQhGDRq5fZWblzrFCK2k9nfQ9dmAnuOql0sDsNGstbNod6cg==", - "license": "Apache-2.0", - "dependencies": { - "@adobe/fetch": "4.2.3", - "@adobe/helix-docx2md": "1.8.0", - "@adobe/helix-markdown-support": "7.1.12", - "@adobe/helix-md2docx": "2.2.19", - "@adobe/mdast-util-gridtables": "4.0.13", - "@azure/msal-node": "3.8.0", - "@googleapis/docs": "6.0.0", - "@googleapis/drive": "17.0.0", - "@googleapis/sheets": "12.0.0", - "@microsoft/microsoft-graph-client": "3.0.7", - "deep-equal": "2.2.3", - "github-slugger": "2.0.0", - "jszip": "3.10.1", - "mdast-util-to-string": "4.0.0", - "node-fetch": "3.3.2", - "unist-util-find-all-after": "5.0.0", - "unist-util-find-all-before": "5.0.0", - "unist-util-find-all-between": "2.1.0", - "unist-util-select": "5.1.0", - "unist-util-visit": "5.0.0" - }, - "engines": { - "node": ">=22.0.0 <23.0.0", - "npm": ">=10.9.0 <12.0.0" - } - }, - "node_modules/@adobe/spacecat-helix-content-sdk/node_modules/@adobe/fetch": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@adobe/fetch/-/fetch-4.2.3.tgz", - "integrity": "sha512-Sn1oRY9WMnLWTIa0nibWJkuck/LWypnckZk1Ude/COAQbanI0mn3jLecJMP0DcGITsl7lWfdcoUpT+a5DpBy8g==", - "license": "Apache-2.0", - "dependencies": { - "debug": "4.4.3", - "http-cache-semantics": "4.2.0", - "lru-cache": "7.18.3" - }, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/@adobe/spacecat-helix-content-sdk/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/@adobe/spacecat-shared-ahrefs-client": { "resolved": "packages/spacecat-shared-ahrefs-client", "link": true @@ -3254,30 +3185,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@googleapis/docs": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@googleapis/docs/-/docs-6.0.0.tgz", - "integrity": "sha512-ad3OCvucUmazbHnvAMMW1ViBknjPs78NBJrECo1TEDNPGoGa4KA6YdTp6RLMNUqLI46L2Zqw/goGOMxeXE/fCQ==", - "license": "Apache-2.0", - "dependencies": { - "googleapis-common": "^8.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@googleapis/drive": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@googleapis/drive/-/drive-17.0.0.tgz", - "integrity": "sha512-HcjLweYgSO4GlU1qSH9uR5RVh/jgyKQU8zVuKlKVscop/CypAl4FIcZdJkG+UjbuBWyNk529YczCmRx+HLNjgg==", - "license": "Apache-2.0", - "dependencies": { - "googleapis-common": "^8.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/@googleapis/sheets": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/@googleapis/sheets/-/sheets-12.0.0.tgz", @@ -3597,6 +3504,8 @@ }, "node_modules/@octokit/auth-token": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", + "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", "dev": true, "license": "MIT", "engines": { @@ -3604,15 +3513,17 @@ } }, "node_modules/@octokit/core": { - "version": "7.0.3", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.5.tgz", + "integrity": "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==", "dev": true, "license": "MIT", "dependencies": { "@octokit/auth-token": "^6.0.0", - "@octokit/graphql": "^9.0.1", - "@octokit/request": "^10.0.2", - "@octokit/request-error": "^7.0.0", - "@octokit/types": "^14.0.0", + "@octokit/graphql": "^9.0.2", + "@octokit/request": "^10.0.4", + "@octokit/request-error": "^7.0.1", + "@octokit/types": "^15.0.0", "before-after-hook": "^4.0.0", "universal-user-agent": "^7.0.0" }, @@ -3621,11 +3532,13 @@ } }, "node_modules/@octokit/endpoint": { - "version": "11.0.0", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.1.tgz", + "integrity": "sha512-7P1dRAZxuWAOPI7kXfio88trNi/MegQ0IJD3vfgC3b+LZo1Qe6gRJc2v0mz2USWWJOKrB2h5spXCzGbw+fAdqA==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^14.0.0", + "@octokit/types": "^15.0.0", "universal-user-agent": "^7.0.2" }, "engines": { @@ -3633,12 +3546,14 @@ } }, "node_modules/@octokit/graphql": { - "version": "9.0.1", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.2.tgz", + "integrity": "sha512-iz6KzZ7u95Fzy9Nt2L8cG88lGRMr/qy1Q36ih/XVzMIlPDMYwaNLE/ENhqmIzgPrlNWiYJkwmveEetvxAgFBJw==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/request": "^10.0.2", - "@octokit/types": "^14.0.0", + "@octokit/request": "^10.0.4", + "@octokit/types": "^15.0.0", "universal-user-agent": "^7.0.0" }, "engines": { @@ -3646,16 +3561,20 @@ } }, "node_modules/@octokit/openapi-types": { - "version": "25.1.0", + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-26.0.0.tgz", + "integrity": "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA==", "dev": true, "license": "MIT" }, "node_modules/@octokit/plugin-paginate-rest": { - "version": "13.1.1", + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-13.2.1.tgz", + "integrity": "sha512-Tj4PkZyIL6eBMYcG/76QGsedF0+dWVeLhYprTmuFVVxzDW7PQh23tM0TP0z+1MvSkxB29YFZwnUX+cXfTiSdyw==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^14.1.0" + "@octokit/types": "^15.0.1" }, "engines": { "node": ">= 20" @@ -3665,12 +3584,14 @@ } }, "node_modules/@octokit/plugin-retry": { - "version": "8.0.1", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-8.0.2.tgz", + "integrity": "sha512-mVPCe77iaD8g1lIX46n9bHPUirFLzc3BfIzsZOpB7bcQh1ecS63YsAgcsyMGqvGa2ARQWKEFTrhMJX2MLJVHVw==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/request-error": "^7.0.0", - "@octokit/types": "^14.0.0", + "@octokit/request-error": "^7.0.1", + "@octokit/types": "^15.0.0", "bottleneck": "^2.15.3" }, "engines": { @@ -3681,11 +3602,13 @@ } }, "node_modules/@octokit/plugin-throttling": { - "version": "11.0.1", + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-11.0.2.tgz", + "integrity": "sha512-ntNIig4zZhQVOZF4fG9Wt8QCoz9ehb+xnlUwp74Ic2ANChCk8oKmRwV9zDDCtrvU1aERIOvtng8wsalEX7Jk5Q==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^14.0.0", + "@octokit/types": "^15.0.0", "bottleneck": "^2.15.3" }, "engines": { @@ -3696,13 +3619,15 @@ } }, "node_modules/@octokit/request": { - "version": "10.0.3", + "version": "10.0.5", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.5.tgz", + "integrity": "sha512-TXnouHIYLtgDhKo+N6mXATnDBkV05VwbR0TtMWpgTHIoQdRQfCSzmy/LGqR1AbRMbijq/EckC/E3/ZNcU92NaQ==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/endpoint": "^11.0.0", - "@octokit/request-error": "^7.0.0", - "@octokit/types": "^14.0.0", + "@octokit/endpoint": "^11.0.1", + "@octokit/request-error": "^7.0.1", + "@octokit/types": "^15.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" }, @@ -3711,22 +3636,26 @@ } }, "node_modules/@octokit/request-error": { - "version": "7.0.0", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.0.1.tgz", + "integrity": "sha512-CZpFwV4+1uBrxu7Cw8E5NCXDWFNf18MSY23TdxCBgjw1tXXHvTrZVsXlW8hgFTOLw8RQR1BBrMvYRtuyaijHMA==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^14.0.0" + "@octokit/types": "^15.0.0" }, "engines": { "node": ">= 20" } }, "node_modules/@octokit/types": { - "version": "14.1.0", + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-15.0.1.tgz", + "integrity": "sha512-sdiirM93IYJ9ODDCBgmRPIboLbSkpLa5i+WLuXH8b8Atg+YMLAyLvDDhNWLV4OYd08tlvYfVm/dw88cqHWtw1Q==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^25.1.0" + "@octokit/openapi-types": "^26.0.0" } }, "node_modules/@open-draft/deferred-promise": { @@ -3960,7 +3889,9 @@ } }, "node_modules/@semantic-release/github": { - "version": "11.0.5", + "version": "11.0.6", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-11.0.6.tgz", + "integrity": "sha512-ctDzdSMrT3H+pwKBPdyCPty6Y47X8dSrjd3aPZ5KKIKKWTwZBE9De8GtsH3TyAlw3Uyo2stegMx6rJMXKpJwJA==", "dev": true, "license": "MIT", "dependencies": { @@ -3972,13 +3903,13 @@ "aggregate-error": "^5.0.0", "debug": "^4.3.4", "dir-glob": "^3.0.1", - "globby": "^14.0.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "issue-parser": "^7.0.0", "lodash-es": "^4.17.21", "mime": "^4.0.0", "p-filter": "^4.0.0", + "tinyglobby": "^0.2.14", "url-join": "^5.0.0" }, "engines": { @@ -3990,6 +3921,8 @@ }, "node_modules/@semantic-release/github/node_modules/@semantic-release/error": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", + "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", "dev": true, "license": "MIT", "engines": { @@ -3998,6 +3931,8 @@ }, "node_modules/@semantic-release/github/node_modules/aggregate-error": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", + "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", "dev": true, "license": "MIT", "dependencies": { @@ -4012,7 +3947,9 @@ } }, "node_modules/@semantic-release/github/node_modules/clean-stack": { - "version": "5.2.0", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.3.0.tgz", + "integrity": "sha512-9ngPTOhYGQqNVSfeJkYXHmF7AGWp4/nN5D/QqNQs3Dvxd1Kk/WpjHfNujKHYUQ/5CoGyOyFNoWSPk5afzP0QVg==", "dev": true, "license": "MIT", "dependencies": { @@ -4027,6 +3964,8 @@ }, "node_modules/@semantic-release/github/node_modules/escape-string-regexp": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "dev": true, "license": "MIT", "engines": { @@ -4038,6 +3977,8 @@ }, "node_modules/@semantic-release/github/node_modules/indent-string": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", "dev": true, "license": "MIT", "engines": { @@ -4049,6 +3990,8 @@ }, "node_modules/@semantic-release/npm": { "version": "12.0.2", + "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-12.0.2.tgz", + "integrity": "sha512-+M9/Lb35IgnlUO6OSJ40Ie+hUsZLuph2fqXC/qrKn0fMvUU/jiCjpoL6zEm69vzcmaZJ8yNKtMBEKHWN49WBbQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4075,25 +4018,18 @@ }, "node_modules/@semantic-release/npm/node_modules/@semantic-release/error": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", + "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", "dev": true, "license": "MIT", "engines": { "node": ">=18" } }, - "node_modules/@semantic-release/npm/node_modules/@sindresorhus/merge-streams": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@semantic-release/npm/node_modules/aggregate-error": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", + "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", "dev": true, "license": "MIT", "dependencies": { @@ -4108,7 +4044,9 @@ } }, "node_modules/@semantic-release/npm/node_modules/clean-stack": { - "version": "5.2.0", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.3.0.tgz", + "integrity": "sha512-9ngPTOhYGQqNVSfeJkYXHmF7AGWp4/nN5D/QqNQs3Dvxd1Kk/WpjHfNujKHYUQ/5CoGyOyFNoWSPk5afzP0QVg==", "dev": true, "license": "MIT", "dependencies": { @@ -4123,6 +4061,8 @@ }, "node_modules/@semantic-release/npm/node_modules/escape-string-regexp": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "dev": true, "license": "MIT", "engines": { @@ -4134,6 +4074,8 @@ }, "node_modules/@semantic-release/npm/node_modules/execa": { "version": "9.6.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.0.tgz", + "integrity": "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==", "dev": true, "license": "MIT", "dependencies": { @@ -4159,6 +4101,8 @@ }, "node_modules/@semantic-release/npm/node_modules/get-stream": { "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", "dev": true, "license": "MIT", "dependencies": { @@ -4174,6 +4118,8 @@ }, "node_modules/@semantic-release/npm/node_modules/human-signals": { "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -4182,6 +4128,8 @@ }, "node_modules/@semantic-release/npm/node_modules/indent-string": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", "dev": true, "license": "MIT", "engines": { @@ -4193,6 +4141,8 @@ }, "node_modules/@semantic-release/npm/node_modules/is-stream": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", "dev": true, "license": "MIT", "engines": { @@ -4204,6 +4154,8 @@ }, "node_modules/@semantic-release/npm/node_modules/npm-run-path": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", "dev": true, "license": "MIT", "dependencies": { @@ -4219,6 +4171,8 @@ }, "node_modules/@semantic-release/npm/node_modules/path-key": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, "license": "MIT", "engines": { @@ -4229,7 +4183,9 @@ } }, "node_modules/@semantic-release/npm/node_modules/semver": { - "version": "7.7.2", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { @@ -4241,6 +4197,8 @@ }, "node_modules/@semantic-release/npm/node_modules/signal-exit": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", "engines": { @@ -4252,6 +4210,8 @@ }, "node_modules/@semantic-release/npm/node_modules/strip-final-newline": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", "dev": true, "license": "MIT", "engines": { @@ -4263,6 +4223,8 @@ }, "node_modules/@semantic-release/npm/node_modules/unicorn-magic": { "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", "dev": true, "license": "MIT", "engines": { @@ -4337,7 +4299,9 @@ } }, "node_modules/@sindresorhus/merge-streams": { - "version": "2.3.0", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", "dev": true, "license": "MIT", "engines": { @@ -5910,6 +5874,8 @@ }, "node_modules/before-after-hook": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", + "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", "dev": true, "license": "Apache-2.0" }, @@ -5930,6 +5896,8 @@ }, "node_modules/bottleneck": { "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", "dev": true, "license": "MIT" }, @@ -8201,6 +8169,8 @@ }, "node_modules/fast-content-type-parse": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", + "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", "dev": true, "funding": [ { @@ -8821,47 +8791,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/globby": { - "version": "14.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@sindresorhus/merge-streams": "^2.1.0", - "fast-glob": "^3.3.3", - "ignore": "^7.0.3", - "path-type": "^6.0.0", - "slash": "^5.1.0", - "unicorn-magic": "^0.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby/node_modules/path-type": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby/node_modules/unicorn-magic": { - "version": "0.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/google-auth-library": { "version": "10.3.0", "license": "Apache-2.0", @@ -8885,19 +8814,6 @@ "node": ">=14" } }, - "node_modules/googleapis": { - "version": "162.0.0", - "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-162.0.0.tgz", - "integrity": "sha512-dxHgGExRjbRLHxHZuPyiFB2692H9hP4Qg9OI6nhzw5OOFlxl6ufG5GMqraKv6HAFvHltz7Ytx/C1ndcM85USxA==", - "license": "Apache-2.0", - "dependencies": { - "google-auth-library": "^10.2.0", - "googleapis-common": "^8.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/googleapis-common": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-8.0.0.tgz", @@ -9346,6 +9262,8 @@ }, "node_modules/http-proxy-agent": { "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "license": "MIT", "dependencies": { @@ -9976,6 +9894,8 @@ }, "node_modules/issue-parser": { "version": "7.0.1", + "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-7.0.1.tgz", + "integrity": "sha512-3YZcUUR2Wt1WsapF+S/WiA2WmlW0cWAoPccMqne7AxEBhCdFeTPjfv/Axb8V2gyCgY3nRw+ksZ3xSUX+R47iAg==", "dev": true, "license": "MIT", "dependencies": { @@ -10523,11 +10443,15 @@ }, "node_modules/lodash.capitalize": { "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", + "integrity": "sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==", "dev": true, "license": "MIT" }, "node_modules/lodash.escaperegexp": { "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", "dev": true, "license": "MIT" }, @@ -10575,6 +10499,8 @@ }, "node_modules/lodash.uniqby": { "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", + "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==", "dev": true, "license": "MIT" }, @@ -11681,2877 +11607,357 @@ "mocha": ">=3.1.2" } }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "license": "MIT" - }, - "node_modules/mz": { - "version": "2.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "node_modules/n-gram": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/n-gram/-/n-gram-2.0.2.tgz", - "integrity": "sha512-S24aGsn+HLBxUGVAUFOwGpKs7LBcG4RudKU//eWzt/mQ97/NMKQxDWHyHx63UNWk/OOdihgmzoETn1tf5nQDzQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/nano-spawn": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/nano-spawn?sponsor=1" - } - }, - "node_modules/nanoid": { - "version": "5.1.6", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.js" - }, - "engines": { - "node": "^18 || >=20" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/neo-async": { - "version": "2.6.2", - "dev": true, - "license": "MIT" - }, - "node_modules/nerf-dart": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/nock": { - "version": "14.0.10", - "dev": true, - "license": "MIT", - "dependencies": { - "@mswjs/interceptors": "^0.39.5", - "json-stringify-safe": "^5.0.1", - "propagate": "^2.0.0" - }, - "engines": { - "node": ">=18.20.0 <20 || >=20.12.1" - } - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-emoji": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@sindresorhus/is": "^4.6.0", - "char-regex": "^1.0.2", - "emojilib": "^2.4.0", - "skin-tone": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/node-fetch": { - "version": "3.3.2", - "license": "MIT", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/node-releases": { - "version": "2.0.20", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-package-data": { - "version": "6.0.2", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^7.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "7.7.2", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/normalize-url": { - "version": "8.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm": { - "version": "10.9.3", - "bundleDependencies": [ - "@isaacs/string-locale-compare", - "@npmcli/arborist", - "@npmcli/config", - "@npmcli/fs", - "@npmcli/map-workspaces", - "@npmcli/package-json", - "@npmcli/promise-spawn", - "@npmcli/redact", - "@npmcli/run-script", - "@sigstore/tuf", - "abbrev", - "archy", - "cacache", - "chalk", - "ci-info", - "cli-columns", - "fastest-levenshtein", - "fs-minipass", - "glob", - "graceful-fs", - "hosted-git-info", - "ini", - "init-package-json", - "is-cidr", - "json-parse-even-better-errors", - "libnpmaccess", - "libnpmdiff", - "libnpmexec", - "libnpmfund", - "libnpmhook", - "libnpmorg", - "libnpmpack", - "libnpmpublish", - "libnpmsearch", - "libnpmteam", - "libnpmversion", - "make-fetch-happen", - "minimatch", - "minipass", - "minipass-pipeline", - "ms", - "node-gyp", - "nopt", - "normalize-package-data", - "npm-audit-report", - "npm-install-checks", - "npm-package-arg", - "npm-pick-manifest", - "npm-profile", - "npm-registry-fetch", - "npm-user-validate", - "p-map", - "pacote", - "parse-conflict-json", - "proc-log", - "qrcode-terminal", - "read", - "semver", - "spdx-expression-parse", - "ssri", - "supports-color", - "tar", - "text-table", - "tiny-relative-date", - "treeverse", - "validate-npm-package-name", - "which", - "write-file-atomic" - ], - "dev": true, - "license": "Artistic-2.0", - "workspaces": [ - "docs", - "smoke-tests", - "mock-globals", - "mock-registry", - "workspaces/*" - ], - "dependencies": { - "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^8.0.1", - "@npmcli/config": "^9.0.0", - "@npmcli/fs": "^4.0.0", - "@npmcli/map-workspaces": "^4.0.2", - "@npmcli/package-json": "^6.2.0", - "@npmcli/promise-spawn": "^8.0.2", - "@npmcli/redact": "^3.2.2", - "@npmcli/run-script": "^9.1.0", - "@sigstore/tuf": "^3.1.1", - "abbrev": "^3.0.1", - "archy": "~1.0.0", - "cacache": "^19.0.1", - "chalk": "^5.4.1", - "ci-info": "^4.2.0", - "cli-columns": "^4.0.0", - "fastest-levenshtein": "^1.0.16", - "fs-minipass": "^3.0.3", - "glob": "^10.4.5", - "graceful-fs": "^4.2.11", - "hosted-git-info": "^8.1.0", - "ini": "^5.0.0", - "init-package-json": "^7.0.2", - "is-cidr": "^5.1.1", - "json-parse-even-better-errors": "^4.0.0", - "libnpmaccess": "^9.0.0", - "libnpmdiff": "^7.0.1", - "libnpmexec": "^9.0.1", - "libnpmfund": "^6.0.1", - "libnpmhook": "^11.0.0", - "libnpmorg": "^7.0.0", - "libnpmpack": "^8.0.1", - "libnpmpublish": "^10.0.1", - "libnpmsearch": "^8.0.0", - "libnpmteam": "^7.0.0", - "libnpmversion": "^7.0.0", - "make-fetch-happen": "^14.0.3", - "minimatch": "^9.0.5", - "minipass": "^7.1.1", - "minipass-pipeline": "^1.2.4", - "ms": "^2.1.2", - "node-gyp": "^11.2.0", - "nopt": "^8.1.0", - "normalize-package-data": "^7.0.0", - "npm-audit-report": "^6.0.0", - "npm-install-checks": "^7.1.1", - "npm-package-arg": "^12.0.2", - "npm-pick-manifest": "^10.0.0", - "npm-profile": "^11.0.1", - "npm-registry-fetch": "^18.0.2", - "npm-user-validate": "^3.0.0", - "p-map": "^7.0.3", - "pacote": "^19.0.1", - "parse-conflict-json": "^4.0.0", - "proc-log": "^5.0.0", - "qrcode-terminal": "^0.12.0", - "read": "^4.1.0", - "semver": "^7.7.2", - "spdx-expression-parse": "^4.0.0", - "ssri": "^12.0.0", - "supports-color": "^9.4.0", - "tar": "^6.2.1", - "text-table": "~0.2.0", - "tiny-relative-date": "^1.3.0", - "treeverse": "^3.0.0", - "validate-npm-package-name": "^6.0.1", - "which": "^5.0.0", - "write-file-atomic": "^6.0.0" - }, - "bin": { - "npm": "bin/npm-cli.js", - "npx": "bin/npx-cli.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/@isaacs/cliui": { - "version": "8.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/npm/node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/npm/node_modules/@isaacs/string-locale-compare": { - "version": "1.1.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/@npmcli/agent": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "agent-base": "^7.1.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.1", - "lru-cache": "^10.0.1", - "socks-proxy-agent": "^8.0.3" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@npmcli/arborist": { - "version": "8.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/fs": "^4.0.0", - "@npmcli/installed-package-contents": "^3.0.0", - "@npmcli/map-workspaces": "^4.0.1", - "@npmcli/metavuln-calculator": "^8.0.0", - "@npmcli/name-from-folder": "^3.0.0", - "@npmcli/node-gyp": "^4.0.0", - "@npmcli/package-json": "^6.0.1", - "@npmcli/query": "^4.0.0", - "@npmcli/redact": "^3.0.0", - "@npmcli/run-script": "^9.0.1", - "bin-links": "^5.0.0", - "cacache": "^19.0.1", - "common-ancestor-path": "^1.0.1", - "hosted-git-info": "^8.0.0", - "json-parse-even-better-errors": "^4.0.0", - "json-stringify-nice": "^1.1.4", - "lru-cache": "^10.2.2", - "minimatch": "^9.0.4", - "nopt": "^8.0.0", - "npm-install-checks": "^7.1.0", - "npm-package-arg": "^12.0.0", - "npm-pick-manifest": "^10.0.0", - "npm-registry-fetch": "^18.0.1", - "pacote": "^19.0.0", - "parse-conflict-json": "^4.0.0", - "proc-log": "^5.0.0", - "proggy": "^3.0.0", - "promise-all-reject-late": "^1.0.0", - "promise-call-limit": "^3.0.1", - "read-package-json-fast": "^4.0.0", - "semver": "^7.3.7", - "ssri": "^12.0.0", - "treeverse": "^3.0.0", - "walk-up-path": "^3.0.1" - }, - "bin": { - "arborist": "bin/index.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@npmcli/config": { - "version": "9.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/map-workspaces": "^4.0.1", - "@npmcli/package-json": "^6.0.1", - "ci-info": "^4.0.0", - "ini": "^5.0.0", - "nopt": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "walk-up-path": "^3.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@npmcli/fs": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@npmcli/git": { - "version": "6.0.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/promise-spawn": "^8.0.0", - "ini": "^5.0.0", - "lru-cache": "^10.0.1", - "npm-pick-manifest": "^10.0.0", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1", - "semver": "^7.3.5", - "which": "^5.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@npmcli/installed-package-contents": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-bundled": "^4.0.0", - "npm-normalize-package-bin": "^4.0.0" - }, - "bin": { - "installed-package-contents": "bin/index.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@npmcli/map-workspaces": { - "version": "4.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/name-from-folder": "^3.0.0", - "@npmcli/package-json": "^6.0.0", - "glob": "^10.2.2", - "minimatch": "^9.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { - "version": "8.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "cacache": "^19.0.0", - "json-parse-even-better-errors": "^4.0.0", - "pacote": "^20.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@npmcli/metavuln-calculator/node_modules/pacote": { - "version": "20.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/git": "^6.0.0", - "@npmcli/installed-package-contents": "^3.0.0", - "@npmcli/package-json": "^6.0.0", - "@npmcli/promise-spawn": "^8.0.0", - "@npmcli/run-script": "^9.0.0", - "cacache": "^19.0.0", - "fs-minipass": "^3.0.0", - "minipass": "^7.0.2", - "npm-package-arg": "^12.0.0", - "npm-packlist": "^9.0.0", - "npm-pick-manifest": "^10.0.0", - "npm-registry-fetch": "^18.0.0", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1", - "sigstore": "^3.0.0", - "ssri": "^12.0.0", - "tar": "^6.1.11" - }, - "bin": { - "pacote": "bin/index.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@npmcli/name-from-folder": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@npmcli/node-gyp": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@npmcli/package-json": { - "version": "6.2.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/git": "^6.0.0", - "glob": "^10.2.2", - "hosted-git-info": "^8.0.0", - "json-parse-even-better-errors": "^4.0.0", - "proc-log": "^5.0.0", - "semver": "^7.5.3", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@npmcli/promise-spawn": { - "version": "8.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "which": "^5.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@npmcli/query": { - "version": "4.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@npmcli/redact": { - "version": "3.2.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@npmcli/run-script": { - "version": "9.1.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/node-gyp": "^4.0.0", - "@npmcli/package-json": "^6.0.0", - "@npmcli/promise-spawn": "^8.0.0", - "node-gyp": "^11.0.0", - "proc-log": "^5.0.0", - "which": "^5.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/npm/node_modules/@sigstore/protobuf-specs": { - "version": "0.4.3", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@sigstore/tuf": { - "version": "3.1.1", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/protobuf-specs": "^0.4.1", - "tuf-js": "^3.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@tufjs/canonical-json": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/abbrev": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/agent-base": { - "version": "7.1.3", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/npm/node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/ansi-styles": { - "version": "6.2.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/npm/node_modules/aproba": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/archy": { - "version": "1.0.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/balanced-match": { - "version": "1.0.2", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/bin-links": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "cmd-shim": "^7.0.0", - "npm-normalize-package-bin": "^4.0.0", - "proc-log": "^5.0.0", - "read-cmd-shim": "^5.0.0", - "write-file-atomic": "^6.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/binary-extensions": { - "version": "2.3.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm/node_modules/brace-expansion": { - "version": "2.0.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/npm/node_modules/cacache": { - "version": "19.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/fs": "^4.0.0", - "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^10.0.1", - "minipass": "^7.0.3", - "minipass-collect": "^2.0.1", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "p-map": "^7.0.2", - "ssri": "^12.0.0", - "tar": "^7.4.3", - "unique-filename": "^4.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/cacache/node_modules/chownr": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/npm/node_modules/cacache/node_modules/mkdirp": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/cacache/node_modules/tar": { - "version": "7.4.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/npm/node_modules/cacache/node_modules/yallist": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/npm/node_modules/chalk": { - "version": "5.4.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/npm/node_modules/chownr": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/ci-info": { - "version": "4.2.0", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/cidr-regex": { - "version": "4.1.3", - "dev": true, - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "ip-regex": "^5.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/npm/node_modules/cli-columns": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/npm/node_modules/cmd-shim": { - "version": "7.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/npm/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/common-ancestor-path": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/cross-spawn": { - "version": "7.0.6", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/cross-spawn/node_modules/which": { - "version": "2.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/cssesc": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/debug": { - "version": "4.4.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/npm/node_modules/diff": { - "version": "5.2.0", - "dev": true, - "inBundle": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/npm/node_modules/eastasianwidth": { - "version": "0.2.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/emoji-regex": { - "version": "8.0.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/encoding": { - "version": "0.1.13", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, - "node_modules/npm/node_modules/env-paths": { - "version": "2.2.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/err-code": { - "version": "2.0.3", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/exponential-backoff": { - "version": "3.1.2", - "dev": true, - "inBundle": true, - "license": "Apache-2.0" - }, - "node_modules/npm/node_modules/fastest-levenshtein": { - "version": "1.0.16", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 4.9.1" - } - }, - "node_modules/npm/node_modules/foreground-child": { - "version": "3.3.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/fs-minipass": { - "version": "3.0.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/glob": { - "version": "10.4.5", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/graceful-fs": { - "version": "4.2.11", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/hosted-git-info": { - "version": "8.1.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^10.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/http-cache-semantics": { - "version": "4.2.0", - "dev": true, - "inBundle": true, - "license": "BSD-2-Clause" - }, - "node_modules/npm/node_modules/http-proxy-agent": { - "version": "7.0.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/npm/node_modules/https-proxy-agent": { - "version": "7.0.6", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/npm/node_modules/iconv-lite": { - "version": "0.6.3", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/ignore-walk": { - "version": "7.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minimatch": "^9.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/imurmurhash": { - "version": "0.1.4", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/npm/node_modules/ini": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/init-package-json": { - "version": "7.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/package-json": "^6.0.0", - "npm-package-arg": "^12.0.0", - "promzard": "^2.0.0", - "read": "^4.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4", - "validate-npm-package-name": "^6.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/ip-address": { - "version": "9.0.5", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" - }, - "engines": { - "node": ">= 12" - } - }, - "node_modules/npm/node_modules/ip-regex": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm/node_modules/is-cidr": { - "version": "5.1.1", - "dev": true, - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "cidr-regex": "^4.1.1" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/npm/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/isexe": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/jackspeak": { - "version": "3.4.3", - "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/npm/node_modules/jsbn": { - "version": "1.1.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/json-parse-even-better-errors": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/json-stringify-nice": { - "version": "1.1.4", - "dev": true, - "inBundle": true, - "license": "ISC", - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/jsonparse": { - "version": "1.3.1", - "dev": true, - "engines": [ - "node >= 0.2.0" - ], - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/just-diff": { - "version": "6.0.2", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/just-diff-apply": { - "version": "5.5.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/libnpmaccess": { - "version": "9.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-package-arg": "^12.0.0", - "npm-registry-fetch": "^18.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/libnpmdiff": { - "version": "7.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/arborist": "^8.0.1", - "@npmcli/installed-package-contents": "^3.0.0", - "binary-extensions": "^2.3.0", - "diff": "^5.1.0", - "minimatch": "^9.0.4", - "npm-package-arg": "^12.0.0", - "pacote": "^19.0.0", - "tar": "^6.2.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/libnpmexec": { - "version": "9.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/arborist": "^8.0.1", - "@npmcli/run-script": "^9.0.1", - "ci-info": "^4.0.0", - "npm-package-arg": "^12.0.0", - "pacote": "^19.0.0", - "proc-log": "^5.0.0", - "read": "^4.0.0", - "read-package-json-fast": "^4.0.0", - "semver": "^7.3.7", - "walk-up-path": "^3.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/libnpmfund": { - "version": "6.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/arborist": "^8.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/libnpmhook": { - "version": "11.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^18.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/libnpmorg": { - "version": "7.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^18.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/libnpmpack": { - "version": "8.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/arborist": "^8.0.1", - "@npmcli/run-script": "^9.0.1", - "npm-package-arg": "^12.0.0", - "pacote": "^19.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/libnpmpublish": { - "version": "10.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "ci-info": "^4.0.0", - "normalize-package-data": "^7.0.0", - "npm-package-arg": "^12.0.0", - "npm-registry-fetch": "^18.0.1", - "proc-log": "^5.0.0", - "semver": "^7.3.7", - "sigstore": "^3.0.0", - "ssri": "^12.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/libnpmsearch": { - "version": "8.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-registry-fetch": "^18.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/libnpmteam": { - "version": "7.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^18.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/libnpmversion": { - "version": "7.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/git": "^6.0.1", - "@npmcli/run-script": "^9.0.1", - "json-parse-even-better-errors": "^4.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.7" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/lru-cache": { - "version": "10.4.3", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/make-fetch-happen": { - "version": "14.0.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/agent": "^3.0.0", - "cacache": "^19.0.1", - "http-cache-semantics": "^4.1.1", - "minipass": "^7.0.2", - "minipass-fetch": "^4.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^1.0.0", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1", - "ssri": "^12.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/make-fetch-happen/node_modules/negotiator": { - "version": "1.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/npm/node_modules/minimatch": { - "version": "9.0.5", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/minipass": { - "version": "7.1.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/npm/node_modules/minipass-collect": { - "version": "2.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/npm/node_modules/minipass-fetch": { - "version": "4.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", - "minizlib": "^3.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "node_modules/npm/node_modules/minipass-flush": { - "version": "1.0.5", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/minipass-pipeline": { - "version": "1.2.4", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/minipass-sized": { - "version": "1.0.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/minizlib": { - "version": "3.0.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/npm/node_modules/mkdirp": { - "version": "1.0.4", - "dev": true, - "inBundle": true, - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/mute-stream": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/node-gyp": { - "version": "11.2.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.0", - "exponential-backoff": "^3.1.1", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^14.0.3", - "nopt": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "tar": "^7.4.3", - "tinyglobby": "^0.2.12", - "which": "^5.0.0" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/chownr": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/mkdirp": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/tar": { - "version": "7.4.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/yallist": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/npm/node_modules/nopt": { - "version": "8.1.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "abbrev": "^3.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/normalize-package-data": { - "version": "7.0.0", - "dev": true, - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^8.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/npm-audit-report": { - "version": "6.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/npm-bundled": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-normalize-package-bin": "^4.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/npm-install-checks": { - "version": "7.1.1", - "dev": true, - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "semver": "^7.1.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/npm-normalize-package-bin": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/npm-package-arg": { - "version": "12.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "hosted-git-info": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^6.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/npm-packlist": { - "version": "9.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "ignore-walk": "^7.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/npm-pick-manifest": { - "version": "10.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-install-checks": "^7.1.0", - "npm-normalize-package-bin": "^4.0.0", - "npm-package-arg": "^12.0.0", - "semver": "^7.3.5" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/npm-profile": { - "version": "11.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-registry-fetch": "^18.0.0", - "proc-log": "^5.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/npm-registry-fetch": { - "version": "18.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/redact": "^3.0.0", - "jsonparse": "^1.3.1", - "make-fetch-happen": "^14.0.0", - "minipass": "^7.0.2", - "minipass-fetch": "^4.0.0", - "minizlib": "^3.0.1", - "npm-package-arg": "^12.0.0", - "proc-log": "^5.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/npm-user-validate": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "BSD-2-Clause", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/p-map": { - "version": "7.0.3", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm/node_modules/package-json-from-dist": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/npm/node_modules/pacote": { - "version": "19.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/git": "^6.0.0", - "@npmcli/installed-package-contents": "^3.0.0", - "@npmcli/package-json": "^6.0.0", - "@npmcli/promise-spawn": "^8.0.0", - "@npmcli/run-script": "^9.0.0", - "cacache": "^19.0.0", - "fs-minipass": "^3.0.0", - "minipass": "^7.0.2", - "npm-package-arg": "^12.0.0", - "npm-packlist": "^9.0.0", - "npm-pick-manifest": "^10.0.0", - "npm-registry-fetch": "^18.0.0", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1", - "sigstore": "^3.0.0", - "ssri": "^12.0.0", - "tar": "^6.1.11" - }, - "bin": { - "pacote": "bin/index.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/parse-conflict-json": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "json-parse-even-better-errors": "^4.0.0", - "just-diff": "^6.0.0", - "just-diff-apply": "^5.2.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/path-key": { - "version": "3.1.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/path-scurry": { - "version": "1.11.1", - "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/proc-log": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/proggy": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/promise-all-reject-late": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/promise-call-limit": { - "version": "3.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/promise-retry": { - "version": "2.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/promzard": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "read": "^4.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/qrcode-terminal": { - "version": "0.12.0", - "dev": true, - "inBundle": true, - "bin": { - "qrcode-terminal": "bin/qrcode-terminal.js" - } - }, - "node_modules/npm/node_modules/read": { - "version": "4.1.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "mute-stream": "^2.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/read-cmd-shim": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/read-package-json-fast": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "json-parse-even-better-errors": "^4.0.0", - "npm-normalize-package-bin": "^4.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/retry": { - "version": "0.12.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/npm/node_modules/safer-buffer": { - "version": "2.1.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true - }, - "node_modules/npm/node_modules/semver": { - "version": "7.7.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/shebang-command": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/shebang-regex": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/signal-exit": { - "version": "4.1.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/sigstore": { - "version": "3.1.0", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/bundle": "^3.1.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.4.0", - "@sigstore/sign": "^3.1.0", - "@sigstore/tuf": "^3.1.0", - "@sigstore/verify": "^2.1.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/bundle": { - "version": "3.1.0", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/protobuf-specs": "^0.4.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/core": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/sign": { - "version": "3.1.0", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/bundle": "^3.1.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.4.0", - "make-fetch-happen": "^14.0.2", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/verify": { - "version": "2.1.1", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/bundle": "^3.1.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.4.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/smart-buffer": { - "version": "4.2.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/npm/node_modules/socks": { - "version": "2.8.5", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ip-address": "^9.0.5", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/npm/node_modules/socks-proxy-agent": { - "version": "8.0.5", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/npm/node_modules/spdx-correct": { - "version": "3.2.0", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/npm/node_modules/spdx-exceptions": { - "version": "2.5.0", - "dev": true, - "inBundle": true, - "license": "CC-BY-3.0" - }, - "node_modules/npm/node_modules/spdx-expression-parse": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/npm/node_modules/spdx-license-ids": { - "version": "3.0.21", - "dev": true, - "inBundle": true, - "license": "CC0-1.0" - }, - "node_modules/npm/node_modules/sprintf-js": { - "version": "1.1.3", - "dev": true, - "inBundle": true, - "license": "BSD-3-Clause" - }, - "node_modules/npm/node_modules/ssri": { - "version": "12.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/string-width": { - "version": "4.2.3", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/supports-color": { - "version": "9.4.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/npm/node_modules/tar": { - "version": "6.2.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/tar/node_modules/fs-minipass": { - "version": "2.1.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/tar/node_modules/minizlib": { - "version": "2.1.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/tar/node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/text-table": { - "version": "0.2.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/tiny-relative-date": { - "version": "1.3.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/tinyglobby": { - "version": "0.2.14", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/npm/node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.6", - "dev": true, - "inBundle": true, - "license": "MIT", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/npm/node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/npm/node_modules/treeverse": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/tuf-js": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "@tufjs/models": "3.0.1", - "debug": "^4.3.6", - "make-fetch-happen": "^14.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/tuf-js/node_modules/@tufjs/models": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "@tufjs/canonical-json": "2.0.0", - "minimatch": "^9.0.5" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/unique-filename": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "unique-slug": "^5.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/unique-slug": { - "version": "5.0.0", + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "imurmurhash": "^0.1.4" + "has-flag": "^4.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/npm/node_modules/util-deprecate": { - "version": "1.0.2", - "dev": true, - "inBundle": true, + "node_modules/ms": { + "version": "2.1.3", "license": "MIT" }, - "node_modules/npm/node_modules/validate-npm-package-license": { - "version": "3.0.4", + "node_modules/mz": { + "version": "2.7.0", "dev": true, - "inBundle": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" } }, - "node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { - "version": "3.0.1", - "dev": true, - "inBundle": true, + "node_modules/n-gram": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/n-gram/-/n-gram-2.0.2.tgz", + "integrity": "sha512-S24aGsn+HLBxUGVAUFOwGpKs7LBcG4RudKU//eWzt/mQ97/NMKQxDWHyHx63UNWk/OOdihgmzoETn1tf5nQDzQ==", "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/npm/node_modules/validate-npm-package-name": { - "version": "6.0.1", + "node_modules/nano-spawn": { + "version": "1.0.3", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=20.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/nano-spawn?sponsor=1" } }, - "node_modules/npm/node_modules/walk-up-path": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/which": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "isexe": "^3.1.1" - }, + "node_modules/nanoid": { + "version": "5.1.6", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "bin": { - "node-which": "bin/which.js" + "nanoid": "bin/nanoid.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^18 || >=20" } }, - "node_modules/npm/node_modules/which/node_modules/isexe": { - "version": "3.1.1", + "node_modules/natural-compare": { + "version": "1.4.0", "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=16" - } + "license": "MIT" }, - "node_modules/npm/node_modules/wrap-ansi": { - "version": "8.1.0", + "node_modules/neo-async": { + "version": "2.6.2", + "dev": true, + "license": "MIT" + }, + "node_modules/nerf-dart": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/nock": { + "version": "14.0.10", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "@mswjs/interceptors": "^0.39.5", + "json-stringify-safe": "^5.0.1", + "propagate": "^2.0.0" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">=18.20.0 <20 || >=20.12.1" } }, - "node_modules/npm/node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "dev": true, - "inBundle": true, + "node_modules/node-domexception": { + "version": "1.0.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">=10.5.0" } }, - "node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", + "node_modules/node-emoji": { + "version": "2.2.0", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "@sindresorhus/is": "^4.6.0", + "char-regex": "^1.0.2", + "emojilib": "^2.4.0", + "skin-tone": "^2.0.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=18" } }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.1.0", - "dev": true, - "inBundle": true, + "node_modules/node-fetch": { + "version": "3.3.2", "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, "engines": { - "node": ">=12" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" } }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "9.2.2", + "node_modules/node-releases": { + "version": "2.0.20", "dev": true, - "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { - "version": "5.1.2", + "node_modules/normalize-package-data": { + "version": "6.0.2", "dev": true, - "inBundle": true, - "license": "MIT", + "license": "BSD-2-Clause", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" }, "engines": { - "node": ">=12" + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "7.7.2", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=10" } }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.0", + "node_modules/normalize-url": { + "version": "8.0.2", "dev": true, - "inBundle": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, "engines": { - "node": ">=12" + "node": ">=14.16" }, "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/write-file-atomic": { - "version": "6.0.0", + "node_modules/npm": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/npm/-/npm-10.9.4.tgz", + "integrity": "sha512-OnUG836FwboQIbqtefDNlyR0gTHzIfwRfE3DuiNewBvnMnWEpB0VEXwBlFVgqpNzIgYo/MHh3d2Hel/pszapAA==", + "bundleDependencies": [ + "@isaacs/string-locale-compare", + "@npmcli/arborist", + "@npmcli/config", + "@npmcli/fs", + "@npmcli/map-workspaces", + "@npmcli/package-json", + "@npmcli/promise-spawn", + "@npmcli/redact", + "@npmcli/run-script", + "@sigstore/tuf", + "abbrev", + "archy", + "cacache", + "chalk", + "ci-info", + "cli-columns", + "fastest-levenshtein", + "fs-minipass", + "glob", + "graceful-fs", + "hosted-git-info", + "ini", + "init-package-json", + "is-cidr", + "json-parse-even-better-errors", + "libnpmaccess", + "libnpmdiff", + "libnpmexec", + "libnpmfund", + "libnpmhook", + "libnpmorg", + "libnpmpack", + "libnpmpublish", + "libnpmsearch", + "libnpmteam", + "libnpmversion", + "make-fetch-happen", + "minimatch", + "minipass", + "minipass-pipeline", + "ms", + "node-gyp", + "nopt", + "normalize-package-data", + "npm-audit-report", + "npm-install-checks", + "npm-package-arg", + "npm-pick-manifest", + "npm-profile", + "npm-registry-fetch", + "npm-user-validate", + "p-map", + "pacote", + "parse-conflict-json", + "proc-log", + "qrcode-terminal", + "read", + "semver", + "spdx-expression-parse", + "ssri", + "supports-color", + "tar", + "text-table", + "tiny-relative-date", + "treeverse", + "validate-npm-package-name", + "which", + "write-file-atomic" + ], "dev": true, - "inBundle": true, - "license": "ISC", + "license": "Artistic-2.0", + "workspaces": [ + "docs", + "smoke-tests", + "mock-globals", + "mock-registry", + "workspaces/*" + ], "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^8.0.1", + "@npmcli/config": "^9.0.0", + "@npmcli/fs": "^4.0.0", + "@npmcli/map-workspaces": "^4.0.2", + "@npmcli/package-json": "^6.2.0", + "@npmcli/promise-spawn": "^8.0.2", + "@npmcli/redact": "^3.2.2", + "@npmcli/run-script": "^9.1.0", + "@sigstore/tuf": "^3.1.1", + "abbrev": "^3.0.1", + "archy": "~1.0.0", + "cacache": "^19.0.1", + "chalk": "^5.4.1", + "ci-info": "^4.2.0", + "cli-columns": "^4.0.0", + "fastest-levenshtein": "^1.0.16", + "fs-minipass": "^3.0.3", + "glob": "^10.4.5", + "graceful-fs": "^4.2.11", + "hosted-git-info": "^8.1.0", + "ini": "^5.0.0", + "init-package-json": "^7.0.2", + "is-cidr": "^5.1.1", + "json-parse-even-better-errors": "^4.0.0", + "libnpmaccess": "^9.0.0", + "libnpmdiff": "^7.0.1", + "libnpmexec": "^9.0.1", + "libnpmfund": "^6.0.1", + "libnpmhook": "^11.0.0", + "libnpmorg": "^7.0.0", + "libnpmpack": "^8.0.1", + "libnpmpublish": "^10.0.1", + "libnpmsearch": "^8.0.0", + "libnpmteam": "^7.0.0", + "libnpmversion": "^7.0.0", + "make-fetch-happen": "^14.0.3", + "minimatch": "^9.0.5", + "minipass": "^7.1.1", + "minipass-pipeline": "^1.2.4", + "ms": "^2.1.2", + "node-gyp": "^11.2.0", + "nopt": "^8.1.0", + "normalize-package-data": "^7.0.0", + "npm-audit-report": "^6.0.0", + "npm-install-checks": "^7.1.1", + "npm-package-arg": "^12.0.2", + "npm-pick-manifest": "^10.0.0", + "npm-profile": "^11.0.1", + "npm-registry-fetch": "^18.0.2", + "npm-user-validate": "^3.0.0", + "p-map": "^7.0.3", + "pacote": "^19.0.1", + "parse-conflict-json": "^4.0.0", + "proc-log": "^5.0.0", + "qrcode-terminal": "^0.12.0", + "read": "^4.1.0", + "semver": "^7.7.2", + "spdx-expression-parse": "^4.0.0", + "ssri": "^12.0.0", + "supports-color": "^9.4.0", + "tar": "^6.2.1", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "treeverse": "^3.0.0", + "validate-npm-package-name": "^6.0.1", + "which": "^5.0.0", + "write-file-atomic": "^6.0.0" + }, + "bin": { + "npm": "bin/npm-cli.js", + "npx": "bin/npx-cli.js" }, "engines": { "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/npm/node_modules/yallist": { - "version": "4.0.0", + "node_modules/npm-run-path": { + "version": "4.0.1", "dev": true, - "inBundle": true, - "license": "ISC" + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } }, "node_modules/nth-check": { "version": "2.1.1", @@ -14755,6 +12161,8 @@ }, "node_modules/p-filter": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-4.1.0.tgz", + "integrity": "sha512-37/tPdZ3oJwHaS3gNJdenCDB3Tz26i9sjhnguBtvN0vYlRIiDNnvTWkuh+0hETV9rLPdJ3rlL3yVOYPIAnM8rw==", "dev": true, "license": "MIT", "dependencies": { @@ -14812,6 +12220,8 @@ }, "node_modules/p-map": { "version": "7.0.3", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", + "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", "dev": true, "license": "MIT", "engines": { @@ -15893,6 +13303,8 @@ }, "node_modules/semantic-release": { "version": "24.2.9", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.2.9.tgz", + "integrity": "sha512-phCkJ6pjDi9ANdhuF5ElS10GGdAKY6R1Pvt9lT3SFhOwM4T7QZE7MLpBDbNruUx/Q3gFD92/UOFringGipRqZA==", "dev": true, "license": "MIT", "dependencies": { @@ -16109,25 +13521,18 @@ }, "node_modules/semantic-release/node_modules/@semantic-release/error": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", + "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", "dev": true, "license": "MIT", "engines": { "node": ">=18" } }, - "node_modules/semantic-release/node_modules/@sindresorhus/merge-streams": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/semantic-release/node_modules/aggregate-error": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", + "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", "dev": true, "license": "MIT", "dependencies": { @@ -16142,7 +13547,9 @@ } }, "node_modules/semantic-release/node_modules/clean-stack": { - "version": "5.2.0", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.3.0.tgz", + "integrity": "sha512-9ngPTOhYGQqNVSfeJkYXHmF7AGWp4/nN5D/QqNQs3Dvxd1Kk/WpjHfNujKHYUQ/5CoGyOyFNoWSPk5afzP0QVg==", "dev": true, "license": "MIT", "dependencies": { @@ -16157,6 +13564,8 @@ }, "node_modules/semantic-release/node_modules/escape-string-regexp": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "dev": true, "license": "MIT", "engines": { @@ -16168,6 +13577,8 @@ }, "node_modules/semantic-release/node_modules/execa": { "version": "9.6.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.0.tgz", + "integrity": "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==", "dev": true, "license": "MIT", "dependencies": { @@ -16193,6 +13604,8 @@ }, "node_modules/semantic-release/node_modules/execa/node_modules/get-stream": { "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", "dev": true, "license": "MIT", "dependencies": { @@ -16208,6 +13621,8 @@ }, "node_modules/semantic-release/node_modules/hosted-git-info": { "version": "8.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", + "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", "dev": true, "license": "ISC", "dependencies": { @@ -16219,6 +13634,8 @@ }, "node_modules/semantic-release/node_modules/human-signals": { "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -16227,6 +13644,8 @@ }, "node_modules/semantic-release/node_modules/indent-string": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", "dev": true, "license": "MIT", "engines": { @@ -16238,6 +13657,8 @@ }, "node_modules/semantic-release/node_modules/is-stream": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", "dev": true, "license": "MIT", "engines": { @@ -16249,11 +13670,15 @@ }, "node_modules/semantic-release/node_modules/lru-cache": { "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC" }, "node_modules/semantic-release/node_modules/marked": { "version": "15.0.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", + "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", "dev": true, "license": "MIT", "bin": { @@ -16265,6 +13690,8 @@ }, "node_modules/semantic-release/node_modules/npm-run-path": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", "dev": true, "license": "MIT", "dependencies": { @@ -16280,6 +13707,8 @@ }, "node_modules/semantic-release/node_modules/p-reduce": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-3.0.0.tgz", + "integrity": "sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q==", "dev": true, "license": "MIT", "engines": { @@ -16291,6 +13720,8 @@ }, "node_modules/semantic-release/node_modules/path-key": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, "license": "MIT", "engines": { @@ -16302,6 +13733,8 @@ }, "node_modules/semantic-release/node_modules/resolve-from": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, "license": "MIT", "engines": { @@ -16309,7 +13742,9 @@ } }, "node_modules/semantic-release/node_modules/semver": { - "version": "7.7.2", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { @@ -16321,6 +13756,8 @@ }, "node_modules/semantic-release/node_modules/signal-exit": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", "engines": { @@ -16332,6 +13769,8 @@ }, "node_modules/semantic-release/node_modules/strip-final-newline": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", "dev": true, "license": "MIT", "engines": { @@ -16343,6 +13782,8 @@ }, "node_modules/semantic-release/node_modules/unicorn-magic": { "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", "dev": true, "license": "MIT", "engines": { @@ -16670,17 +14111,6 @@ "dev": true, "license": "MIT" }, - "node_modules/slash": { - "version": "5.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/slice-ansi": { "version": "5.0.0", "dev": true, @@ -17236,6 +14666,54 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "dev": true, @@ -17732,6 +15210,8 @@ }, "node_modules/universal-user-agent": { "version": "7.0.3", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", + "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", "dev": true, "license": "ISC" }, @@ -17786,6 +15266,8 @@ }, "node_modules/url-join": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz", + "integrity": "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==", "dev": true, "license": "MIT", "engines": { @@ -25159,6 +22641,69 @@ } } }, + "packages/spacecat-shared-content-client/node_modules/@adobe/spacecat-helix-content-sdk": { + "version": "1.4.24", + "resolved": "https://registry.npmjs.org/@adobe/spacecat-helix-content-sdk/-/spacecat-helix-content-sdk-1.4.24.tgz", + "integrity": "sha512-RzWPWv/uP5yhbCGGKKxf/oSVxG5GXTZ2b1DakxiQhGDRq5fZWblzrFCK2k9nfQ9dmAnuOql0sDsNGstbNod6cg==", + "license": "Apache-2.0", + "dependencies": { + "@adobe/fetch": "4.2.3", + "@adobe/helix-docx2md": "1.8.0", + "@adobe/helix-markdown-support": "7.1.12", + "@adobe/helix-md2docx": "2.2.19", + "@adobe/mdast-util-gridtables": "4.0.13", + "@azure/msal-node": "3.8.0", + "@googleapis/docs": "6.0.0", + "@googleapis/drive": "17.0.0", + "@googleapis/sheets": "12.0.0", + "@microsoft/microsoft-graph-client": "3.0.7", + "deep-equal": "2.2.3", + "github-slugger": "2.0.0", + "jszip": "3.10.1", + "mdast-util-to-string": "4.0.0", + "node-fetch": "3.3.2", + "unist-util-find-all-after": "5.0.0", + "unist-util-find-all-before": "5.0.0", + "unist-util-find-all-between": "2.1.0", + "unist-util-select": "5.1.0", + "unist-util-visit": "5.0.0" + }, + "engines": { + "node": ">=22.0.0 <23.0.0", + "npm": ">=10.9.0 <12.0.0" + } + }, + "packages/spacecat-shared-content-client/node_modules/@adobe/spacecat-helix-content-sdk/node_modules/@adobe/fetch": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@adobe/fetch/-/fetch-4.2.3.tgz", + "integrity": "sha512-Sn1oRY9WMnLWTIa0nibWJkuck/LWypnckZk1Ude/COAQbanI0mn3jLecJMP0DcGITsl7lWfdcoUpT+a5DpBy8g==", + "license": "Apache-2.0", + "dependencies": { + "debug": "4.4.3", + "http-cache-semantics": "4.2.0", + "lru-cache": "7.18.3" + }, + "engines": { + "node": ">=14.16" + } + }, + "packages/spacecat-shared-content-client/node_modules/@adobe/spacecat-helix-content-sdk/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "packages/spacecat-shared-content-client/node_modules/@adobe/spacecat-shared-data-access": { "version": "2.45.0", "license": "Apache-2.0", @@ -28150,6 +25695,30 @@ "node": ">=18.0.0" } }, + "packages/spacecat-shared-content-client/node_modules/@googleapis/docs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@googleapis/docs/-/docs-6.0.0.tgz", + "integrity": "sha512-ad3OCvucUmazbHnvAMMW1ViBknjPs78NBJrECo1TEDNPGoGa4KA6YdTp6RLMNUqLI46L2Zqw/goGOMxeXE/fCQ==", + "license": "Apache-2.0", + "dependencies": { + "googleapis-common": "^8.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "packages/spacecat-shared-content-client/node_modules/@googleapis/drive": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@googleapis/drive/-/drive-17.0.0.tgz", + "integrity": "sha512-HcjLweYgSO4GlU1qSH9uR5RVh/jgyKQU8zVuKlKVscop/CypAl4FIcZdJkG+UjbuBWyNk529YczCmRx+HLNjgg==", + "license": "Apache-2.0", + "dependencies": { + "googleapis-common": "^8.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "packages/spacecat-shared-content-client/node_modules/@hapi/hoek": { "version": "9.3.0", "license": "BSD-3-Clause" @@ -28208,7 +25777,7 @@ }, "packages/spacecat-shared-data-access": { "name": "@adobe/spacecat-shared-data-access", - "version": "2.72.1", + "version": "2.73.1", "license": "Apache-2.0", "dependencies": { "@adobe/spacecat-shared-utils": "1.49.0", @@ -40052,6 +37621,19 @@ "fxparser": "src/cli/cli.js" } }, + "packages/spacecat-shared-google-client/node_modules/googleapis": { + "version": "162.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-162.0.0.tgz", + "integrity": "sha512-dxHgGExRjbRLHxHZuPyiFB2692H9hP4Qg9OI6nhzw5OOFlxl6ufG5GMqraKv6HAFvHltz7Ytx/C1ndcM85USxA==", + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^10.2.0", + "googleapis-common": "^8.0.0" + }, + "engines": { + "node": ">=18" + } + }, "packages/spacecat-shared-google-client/node_modules/joi": { "version": "17.13.3", "license": "BSD-3-Clause", @@ -46596,6 +44178,12 @@ "aws4": "1.13.2" } }, + "packages/spacecat-shared-rum-api-client/node_modules/@adobe/rum-distiller": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/@adobe/rum-distiller/-/rum-distiller-1.20.0.tgz", + "integrity": "sha512-U8/i3MW0E7OYALOaHrMbRQWoxhDOCDHpLTFXXAnUTAYLFlyBePuWC3RHTGnUrTErI6B80DjTTo4ywj3Y8bazVg==", + "license": "Apache-2.0" + }, "packages/spacecat-shared-rum-api-client/node_modules/@adobe/spacecat-shared-data-access": { "version": "2.45.0", "license": "Apache-2.0", @@ -56225,7 +53813,7 @@ "version": "1.0.0", "license": "Apache-2.0", "dependencies": { - "@adobe/spacecat-shared-utils": "*", + "@adobe/spacecat-shared-utils": "1.59.4", "@aws-sdk/client-cloudfront": "3.893.0", "@aws-sdk/client-s3": "3.893.0" }, diff --git a/packages/spacecat-shared-tokowaka-client/README.md b/packages/spacecat-shared-tokowaka-client/README.md index f6721049f..53aba5156 100644 --- a/packages/spacecat-shared-tokowaka-client/README.md +++ b/packages/spacecat-shared-tokowaka-client/README.md @@ -10,363 +10,81 @@ npm install @adobe/spacecat-shared-tokowaka-client ## Usage -### Basic Usage - ```javascript import TokowakaClient from '@adobe/spacecat-shared-tokowaka-client'; -// Create client from context const tokowakaClient = TokowakaClient.createFrom(context); - -// Deploy suggestions to Tokowaka const result = await tokowakaClient.deploySuggestions(site, opportunity, suggestions); - -console.log('Deployed to S3:', result.s3Key); -``` - -### Manual Configuration Generation - -```javascript -// Generate configuration without uploading -const config = tokowakaClient.generateConfig(site, opportunity, suggestions); - -console.log('Generated config:', config); -/* -{ - siteId: '9ae8877a-bbf3-407d-9adb-d6a72ce3c5e3', - baseURL: 'https://example.com', - version: '1.0', - tokowakaForceFail: false, - tokowakaOptimizations: { - '/page1.html': { - prerender: true, - patches: [ - { - op: 'replace', - selector: 'h1.title', - value: 'Optimized Heading', - opportunityId: '...', - suggestionId: '...', - prerenderRequired: true, - lastUpdated: 1234567890 - } - ] - } - } -} -*/ - -// Upload config separately -const s3Key = await tokowakaClient.uploadConfig(tokowakaApiKey, config); ``` ## API Reference -### TokowakaClient - -#### Constructor - -```javascript -new TokowakaClient(config, log) -``` - -**Parameters:** -- `config.bucketName` (string): S3 bucket name for storing configurations -- `config.s3Client` (S3Client): AWS S3 client instance -- `log` (Object): Logger instance (console-compatible) +### TokowakaClient.createFrom(context) -#### Static Methods +Creates a client instance from a context object. -##### `TokowakaClient.createFrom(context)` - -Creates a client instance from a context object. Reuses existing client if available. - -**Parameters:** -- `context.env.TOKOWAKA_CONFIG_BUCKET` (string): S3 bucket name -- `context.s3Client` (S3Client): AWS S3 client +**Required context properties:** +- `context.s3.s3Client` (S3Client): AWS S3 client instance - `context.log` (Object, optional): Logger instance +- `context.env.TOKOWAKA_SITE_CONFIG_BUCKET` (string): S3 bucket name for configurations +- `context.env.TOKOWAKA_CDN_PROVIDER` (string): CDN provider for cache invalidation +- `context.env.TOKOWAKA_CDN_CONFIG` (string): JSON configuration for CDN client -**Returns:** `TokowakaClient` - -#### Instance Methods +## Environment Variables -##### `generateConfig(site, opportunity, suggestions)` +**Required:** +- `TOKOWAKA_SITE_CONFIG_BUCKET` - S3 bucket name for storing configurations -Generates Tokowaka configuration from opportunity suggestions. +**Optional (for CDN invalidation):** +- `TOKOWAKA_CDN_PROVIDER` - CDN provider name (e.g., "cloudfront") +- `TOKOWAKA_CDN_CONFIG` - JSON string with CDN-specific configuration. (e.g., { "cloudfront": { "distributionId": , "region": "us-east-1" }}) -**Parameters:** -- `site` (Object): Site entity with `getId()`, `getBaseURL()`, `getConfig()` methods -- `opportunity` (Object): Opportunity entity with `getId()`, `getType()` methods -- `suggestions` (Array): Array of suggestion entities with `getId()`, `getData()` methods +### Main Methods -**Returns:** `TokowakaConfig` object +#### `deploySuggestions(site, opportunity, suggestions)` -##### `uploadConfig(apiKey, config)` +Generates configuration and uploads to S3. **Automatically fetches existing configuration and merges** new suggestions with it. Invalidates CDN cache after upload. -Uploads configuration to S3. +**Returns:** `Promise` with: +- `s3Path` - S3 key where config was uploaded +- `cdnInvalidation` - CDN invalidation result (or error) +- `succeededSuggestions` - Array of deployed suggestions +- `failedSuggestions` - Array of `{suggestion, reason}` objects for ineligible suggestions -**Parameters:** -- `apiKey` (string): Tokowaka API key (used as S3 key prefix) -- `config` (Object): Tokowaka configuration object +#### `fetchConfig(apiKey)` -**Returns:** `Promise` - S3 key of uploaded configuration +Fetches existing Tokowaka configuration from S3. -##### `deploySuggestions(site, opportunity, suggestions)` +**Returns:** `Promise` - Configuration object or null if not found -Complete deployment flow: generates config and uploads to S3. +#### `mergeConfigs(existingConfig, newConfig)` -**Parameters:** -- `site` (Object): Site entity -- `opportunity` (Object): Opportunity entity -- `suggestions` (Array): Array of suggestion entities +Merges existing configuration with new configuration. For each URL path, checks if `opportunityId` + `suggestionId` combination exists and either updates or adds patches accordingly. -**Returns:** `Promise` +**Returns:** `TokowakaConfig` - Merged configuration -```typescript -interface DeploymentResult { - tokowakaApiKey: string; - s3Key: string; - config: TokowakaConfig; - cdnInvalidation: CdnInvalidationResult | null; -} - -interface CdnInvalidationResult { - status: string; - provider?: string; - purgeId?: string; - estimatedSeconds?: number; - paths?: number; - message?: string; -} -``` +#### `generateConfig(site, opportunity, suggestions)` -##### `invalidateCdnCache(site, s3Key)` +Generates Tokowaka configuration from opportunity suggestions without uploading. -Invalidates CDN cache for the Tokowaka configuration. +#### `uploadConfig(apiKey, config)` -**Parameters:** -- `site` (Object): Site entity with CDN configuration -- `s3Key` (string): S3 key of the uploaded configuration - -**Returns:** `Promise` - -This method is called automatically after S3 upload in `deploySuggestions()`. Failures are logged but don't block deployment. +Uploads configuration to S3. Returns S3 key of uploaded configuration. ## CDN Cache Invalidation -The Tokowaka client automatically invalidates CDN caches after uploading configurations to ensure fresh content is served immediately. This feature is: -- ✅ **Automatic**: Triggered after every successful S3 upload -- ✅ **Non-blocking**: Failures are logged but don't prevent deployment -- ✅ **Extensible**: Support for multiple CDN providers - -### Site Configuration - -Configure CDN invalidation in your site config: - -```javascript -{ - "tokowakaApiKey": "OCtrOiKqOxhg4Er3lzYDJS8FAeEUSriK", - "cdn": { - "provider": "akamai", - "config": { - "clientToken": "akab-xxxxx", - "clientSecret": "xxxxxx", - "accessToken": "akab-xxxxx", - "baseUrl": "https://akaa-baseurl-xxx.luna.akamaiapis.net" - } - } -} -``` - -### Supported CDN Providers - -| Provider | Status | Authentication Method | -|----------|--------|----------------------| -| **Akamai** | ✅ Supported | EdgeGrid (HMAC-SHA256) | -| Cloudflare | 🔜 Coming soon | API Token | -| Fastly | 🔜 Coming soon | API Key | -| AWS CloudFront | 🔜 Coming soon | AWS IAM | - -### Akamai CDN Configuration - -```typescript -{ - provider: 'akamai', - config: { - clientToken: string; // Akamai {OPEN} API client token - clientSecret: string; // Akamai client secret - accessToken: string; // Akamai access token - baseUrl?: string; // Optional: Akamai API base URL - } -} -``` - -**Getting Akamai credentials:** -1. Log in to Akamai Control Center -2. Navigate to Identity & Access Management -3. Create API client with CCU (Cache Control Utility) permissions -4. Copy credentials to site config - -### Custom CDN Providers - -You can add support for additional CDN providers by creating custom CDN clients: - -```javascript -import { BaseCdnClient } from '@adobe/spacecat-shared-tokowaka-client'; - -class CustomCdnClient extends BaseCdnClient { - getProviderName() { - return 'custom-cdn'; - } - - validateConfig() { - return !!(this.config.apiKey && this.config.apiSecret); - } - - async invalidateCache(paths) { - // Implement CDN-specific cache invalidation - const response = await fetch('https://api.custom-cdn.com/purge', { - method: 'POST', - headers: { - 'Authorization': `Bearer ${this.config.apiKey}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ urls: paths }), - }); - - if (!response.ok) { - throw new Error(`Purge failed: ${response.status}`); - } - - return { - status: 'success', - provider: 'custom-cdn', - paths: paths.length, - }; - } -} - -// Register the custom CDN client -const client = TokowakaClient.createFrom(context); -client.cdnClientRegistry.registerClient('custom-cdn', CustomCdnClient); -``` - -### Testing CDN Invalidation - -```javascript -const result = await tokowakaClient.deploySuggestions(site, opportunity, suggestions); - -if (result.cdnInvalidation) { - console.log('CDN cache invalidated:', result.cdnInvalidation); - console.log('Provider:', result.cdnInvalidation.provider); - console.log('Purge ID:', result.cdnInvalidation.purgeId); -} else { - console.log('CDN invalidation skipped (no CDN configured)'); -} -``` - -### Checking Invalidation Status (Akamai) - -```javascript -import { AkamaiCdnClient } from '@adobe/spacecat-shared-tokowaka-client'; - -const cdnClient = new AkamaiCdnClient(cdnConfig, log); -const status = await cdnClient.getInvalidationStatus(purgeId); - -console.log('Purge status:', status.status); -// Status values: 'In-Progress', 'Done', 'Unknown' -``` +The client invalidates CDN cache after uploading configurations. Failures are logged but don't block deployment. ## Supported Opportunity Types -### 1. Headings - -Optimizes heading elements (h1, h2, h3, etc.). - -**Required suggestion data:** -- `recommendedAction` (or fallbacks: `value`, `suggestedText`, `text`, `heading`): New heading text -- `headingTag` (or `selector`, `cssSelector`, `xpath`): Heading tag or CSS selector (e.g., "h1", "h2") - -**Example suggestion data:** -```json -{ - "url": "https://www.example.com/page.html", - "headingTag": "h1", - "recommendedAction": "Optimized Heading for SEO", - "checkType": "heading-empty" -} -``` - -**Generated patch:** -```json -{ - "op": "replace", - "selector": "h1", - "value": "Optimized Heading for SEO", - "opportunityId": "...", - "suggestionId": "...", - "prerenderRequired": true, - "lastUpdated": 1234567890 -} -``` - -### 2. Meta Tags +### Headings +Optimizes heading elements. Requires `recommendedAction` (new text) and `headingTag` (e.g., "h1", "h2"). -Updates meta tag content attributes. - -**Required suggestion data:** -- `metaName` or `name`: Meta tag name attribute -- `content` or `value`: New content value - -**Generated patch:** -```json -{ - "op": "replace", - "selector": "meta[name=\"description\"]", - "attribute": "content", - "value": "New meta description", - "prerenderRequired": false -} -``` - -### 3. Prerender - -Enables pre-rendering for client-side rendered pages. - -**Generated patch:** -```json -{ - "op": "prerender", - "prerenderRequired": true -} -``` - -### 4. Structured Data - -Adds JSON-LD structured data to pages. - -**Required suggestion data:** -- `script` or `structuredData`: JSON-LD script content - -**Generated patch:** -```json -{ - "op": "add", - "selector": "head", - "element": "script", - "attributes": { - "type": "application/ld+json" - }, - "value": "{ \"@context\": \"https://schema.org\", ... }", - "prerenderRequired": true -} -``` +**Deployment Eligibility:** Only suggestions with `checkType: 'heading-empty'` can be deployed currently. Other heading types (e.g., `heading-missing`) are filtered out during deployment. ## Extending with Custom Mappers -The Tokowaka client uses a **Strategy Pattern** with opportunity-specific mappers. You can easily add support for new opportunity types by creating custom mappers. - -### Creating a Custom Mapper +You can add support for new opportunity types by extending `BaseOpportunityMapper`: ```javascript import { BaseOpportunityMapper } from '@adobe/spacecat-shared-tokowaka-client'; @@ -377,25 +95,20 @@ class CustomOpportunityMapper extends BaseOpportunityMapper { } requiresPrerender() { - return true; // or false, depending on your needs + return true; } suggestionToPatch(suggestion, opportunityId) { const data = suggestion.getData(); - - // Validate data if (!this.validateSuggestionData(data)) { - this.log.warn(`Invalid suggestion data for ${suggestion.getId()}`); return null; } - // Convert suggestion data to patch format return { ...this.createBasePatch(suggestion.getId(), opportunityId), op: 'replace', selector: data.targetElement, value: data.newValue, - // Add any other custom fields }; } @@ -404,116 +117,51 @@ class CustomOpportunityMapper extends BaseOpportunityMapper { } } -// Register the custom mapper +// Register the mapper const client = TokowakaClient.createFrom(context); client.registerMapper(new CustomOpportunityMapper(context.log)); - -// Now the client can handle 'custom-opportunity' type -const result = await client.deploySuggestions(site, customOpportunity, suggestions); -``` - -### Architecture Benefits - -**Common for all opportunities:** -- ✅ Authentication & Authorization -- ✅ Validation logic -- ✅ S3 upload handling -- ✅ Site config generation structure - -**Opportunity-specific (via mappers):** -- 🔧 Suggestion data → Tokowaka patch conversion -- 🔧 Data field mapping -- 🔧 Prerender requirements -- 🔧 Custom validation rules - -### Getting Supported Types - -```javascript -const supportedTypes = client.getSupportedOpportunityTypes(); -console.log(supportedTypes); -// ['headings', 'meta-tags', 'prerender', 'structured-data', 'custom-opportunity'] ``` ## Configuration Format -### Tokowaka Site Config +### Tokowaka Config ```typescript interface TokowakaConfig { - siteId: string; // Site UUID - baseURL: string; // Site base URL - version: string; // Config version (currently "1.0") - tokowakaForceFail: boolean; // Force fail flag (for testing) - tokowakaOptimizations: { // Optimizations by URL path + siteId: string; + baseURL: string; + version: string; + tokowakaForceFail: boolean; + tokowakaOptimizations: { + "prerender": true, [urlPath: string]: { - prerender: boolean; // Whether to pre-render this URL - patches: TokawakaPatch[]; // Array of patches to apply + prerender: boolean; + patches: TokawakaPatch[]; } } } -``` - -### Patch Format -```typescript interface TokawakaPatch { - op: 'replace' | 'add' | 'prerender'; // Operation type - selector?: string; // CSS selector (for replace/add) - value?: string; // New value - attribute?: string; // Attribute to modify (optional) - element?: string; // Element type to add (for 'add') - attributes?: Record; // Element attributes (for 'add') - opportunityId: string; // Opportunity UUID - suggestionId: string; // Suggestion UUID - prerenderRequired: boolean; // Whether prerender is needed - lastUpdated: number; // Timestamp (milliseconds) + op: 'replace' | 'add' | 'prerender'; + selector?: string; + value?: string; + attribute?: string; + element?: string; + attributes?: Record; + opportunityId: string; + suggestionId: string; + prerenderRequired: boolean; + lastUpdated: number; } ``` -## S3 Storage Structure +## S3 Storage Configurations are stored at: - -``` -s3://{TOKOWAKA_CONFIG_BUCKET}/{tokowakaApiKey}/v1/tokowaka-site-config.json -``` - -**Example:** -``` -s3://spacecat-tokowaka-configs/OCtrOiKqOxhg4Er3lzYDJS8FAeEUSriK/v1/tokowaka-site-config.json ``` - -**Cache Control:** 24 hours (86400 seconds) - -## Environment Variables - -```bash -TOKOWAKA_CONFIG_BUCKET=spacecat-tokowaka-configs -``` - -## Error Handling - -The client throws errors with `status` property for HTTP-compatible error handling: - -```javascript -try { - await tokowakaClient.deploySuggestions(site, opportunity, suggestions); -} catch (error) { - if (error.status === 400) { - console.error('Bad request:', error.message); - } else if (error.status === 500) { - console.error('Server error:', error.message); - } -} -``` - -## Testing - -```bash -npm test +s3://{TOKOWAKA_SITE_CONFIG_BUCKET}/opportunities/{tokowakaApiKey} ``` -## License +**Note:** The configuration is stored as a JSON file containing the complete Tokowaka optimization config for the site. -Apache-2.0 diff --git a/packages/spacecat-shared-tokowaka-client/package.json b/packages/spacecat-shared-tokowaka-client/package.json index 0ddfda1a1..c8cd53079 100644 --- a/packages/spacecat-shared-tokowaka-client/package.json +++ b/packages/spacecat-shared-tokowaka-client/package.json @@ -31,7 +31,7 @@ "spec": "test/**/*.test.js" }, "dependencies": { - "@adobe/spacecat-shared-utils": "*", + "@adobe/spacecat-shared-utils": "1.59.4", "@aws-sdk/client-cloudfront": "3.893.0", "@aws-sdk/client-s3": "3.893.0" }, diff --git a/packages/spacecat-shared-tokowaka-client/src/index.d.ts b/packages/spacecat-shared-tokowaka-client/src/index.d.ts index 0d97383d6..20f06d937 100644 --- a/packages/spacecat-shared-tokowaka-client/src/index.d.ts +++ b/packages/spacecat-shared-tokowaka-client/src/index.d.ts @@ -186,6 +186,16 @@ export default class TokowakaClient { uploadConfig(apiKey: string, config: TokowakaConfig): Promise; + /** + * Fetches existing Tokowaka configuration from S3 + */ + fetchConfig(apiKey: string): Promise; + + /** + * Merges existing configuration with new configuration + */ + mergeConfigs(existingConfig: TokowakaConfig | null, newConfig: TokowakaConfig): TokowakaConfig; + invalidateCdnCache(site: Site, s3Key: string): Promise; deploySuggestions( diff --git a/packages/spacecat-shared-tokowaka-client/src/index.js b/packages/spacecat-shared-tokowaka-client/src/index.js index d2d2e64a0..5a4d97dfc 100644 --- a/packages/spacecat-shared-tokowaka-client/src/index.js +++ b/packages/spacecat-shared-tokowaka-client/src/index.js @@ -10,7 +10,7 @@ * governing permissions and limitations under the License. */ -import { PutObjectCommand } from '@aws-sdk/client-s3'; +import { GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3'; import { hasText, isNonEmptyObject } from '@adobe/spacecat-shared-utils'; import MapperRegistry from './mappers/mapper-registry.js'; import CdnClientRegistry from './cdn/cdn-client-registry.js'; @@ -152,9 +152,16 @@ class TokowakaClient { }; } + /** + * Gets list of supported opportunity types + * @returns {string[]} - Array of supported opportunity types + */ + getSupportedOpportunityTypes() { + return this.mapperRegistry.getSupportedOpportunityTypes(); + } + /** * Registers a custom mapper for an opportunity type - * This allows extending the client with new opportunity types * @param {BaseOpportunityMapper} mapper - Mapper instance */ registerMapper(mapper) { @@ -162,11 +169,117 @@ class TokowakaClient { } /** - * Gets list of supported opportunity types - * @returns {string[]} - Array of supported opportunity types + * Fetches existing Tokowaka configuration from S3 + * @param {string} siteTokowakaKey - Tokowaka API key (used as S3 key prefix) + * @returns {Promise} - Existing configuration object or null if not found */ - getSupportedOpportunityTypes() { - return this.mapperRegistry.getSupportedOpportunityTypes(); + async fetchConfig(siteTokowakaKey) { + if (!hasText(siteTokowakaKey)) { + throw this.#createError('Tokowaka API key is required', HTTP_BAD_REQUEST); + } + + const s3Path = `opportunities/${siteTokowakaKey}`; + + try { + const command = new GetObjectCommand({ + Bucket: this.bucketName, + Key: s3Path, + }); + + const response = await this.s3Client.send(command); + const bodyContents = await response.Body.transformToString(); + const config = JSON.parse(bodyContents); + + this.log.debug(`Successfully fetched existing Tokowaka config from s3://${this.bucketName}/${s3Path}`); + return config; + } catch (error) { + // If config doesn't exist (NoSuchKey), return null + if (error.name === 'NoSuchKey' || error.Code === 'NoSuchKey') { + this.log.debug(`No existing Tokowaka config found at s3://${this.bucketName}/${s3Path}`); + return null; + } + + // For other errors, log and throw + this.log.error(`Failed to fetch Tokowaka config from S3: ${error.message}`, error); + throw this.#createError(`S3 fetch failed: ${error.message}`, HTTP_INTERNAL_SERVER_ERROR); + } + } + + /** + * Merges existing configuration with new configuration + * For each URL path, checks if opportunityId+suggestionId combination exists: + * - If exists: updates the patch + * - If not exists: adds new patch to the array + * @param {Object} existingConfig - Existing configuration from S3 + * @param {Object} newConfig - New configuration generated from suggestions + * @returns {Object} - Merged configuration + */ + mergeConfigs(existingConfig, newConfig) { + if (!existingConfig) { + return newConfig; + } + + // Start with existing config structure + const mergedConfig = { + ...existingConfig, + baseURL: newConfig.baseURL, + version: newConfig.version, + tokowakaForceFail: newConfig.tokowakaForceFail, + }; + + // Merge optimizations for each URL path + Object.entries(newConfig.tokowakaOptimizations).forEach(([urlPath, newOptimization]) => { + const existingOptimization = mergedConfig.tokowakaOptimizations[urlPath]; + + if (!existingOptimization) { + // URL path doesn't exist in existing config, add it entirely + mergedConfig.tokowakaOptimizations[urlPath] = newOptimization; + this.log.debug(`Added new URL path: ${urlPath}`); + } else { + // URL path exists, merge patches + const existingPatches = existingOptimization.patches || []; + const newPatches = newOptimization.patches || []; + + // Create a map of existing patches by opportunityId+suggestionId + const patchMap = new Map(); + existingPatches.forEach((patch, index) => { + const key = `${patch.opportunityId}:${patch.suggestionId}`; + patchMap.set(key, { patch, index }); + }); + + // Process new patches + const mergedPatches = [...existingPatches]; + let updateCount = 0; + let addCount = 0; + + newPatches.forEach((newPatch) => { + const key = `${newPatch.opportunityId}:${newPatch.suggestionId}`; + const existing = patchMap.get(key); + + if (existing) { + // Update existing patch + mergedPatches[existing.index] = newPatch; + updateCount += 1; + this.log.debug(`Updated patch for ${key} at ${urlPath}`); + } else { + // Add new patch + mergedPatches.push(newPatch); + addCount += 1; + this.log.debug(`Added new patch for ${key} at ${urlPath}`); + } + }); + + mergedConfig.tokowakaOptimizations[urlPath] = { + ...existingOptimization, + prerender: newOptimization.prerender, + patches: mergedPatches, + }; + + this.log.debug(`Merged patches for ${urlPath}: ${updateCount} updated, ${addCount} added`); + } + }); + + return mergedConfig; } /** @@ -289,9 +402,18 @@ class TokowakaClient { }; } + // Fetch existing configuration from S3 + this.log.debug(`Fetching existing Tokowaka config for site ${site.getId()}`); + const existingConfig = await this.fetchConfig(apiKey); + // Generate configuration with eligible suggestions only - this.log.info(`Generating Tokowaka config for site ${site.getId()}, opportunity ${opportunity.getId()}`); - const config = this.generateConfig(site, opportunity, eligibleSuggestions); + this.log.debug(`Generating Tokowaka config for site ${site.getId()}, opportunity ${opportunity.getId()}`); + const newConfig = this.generateConfig(site, opportunity, eligibleSuggestions); + + // Merge with existing config if it exists + const config = existingConfig + ? this.mergeConfigs(existingConfig, newConfig) + : newConfig; // Upload to S3 this.log.info(`Uploading Tokowaka config for ${eligibleSuggestions.length} suggestions`); diff --git a/packages/spacecat-shared-tokowaka-client/test/index.test.js b/packages/spacecat-shared-tokowaka-client/test/index.test.js index 6fe988966..f631cf13b 100644 --- a/packages/spacecat-shared-tokowaka-client/test/index.test.js +++ b/packages/spacecat-shared-tokowaka-client/test/index.test.js @@ -370,6 +370,289 @@ describe('TokowakaClient', () => { }); }); + describe('fetchConfig', () => { + it('should fetch existing config from S3', async () => { + const existingConfig = { + siteId: 'site-123', + baseURL: 'https://example.com', + version: '1.0', + tokowakaForceFail: false, + tokowakaOptimizations: { + '/page1': { + prerender: true, + patches: [ + { + op: 'replace', + selector: 'h1', + value: 'Old Heading', + opportunityId: 'opp-123', + suggestionId: 'sugg-1', + prerenderRequired: true, + lastUpdated: 1234567890, + }, + ], + }, + }, + }; + + s3Client.send.resolves({ + Body: { + transformToString: async () => JSON.stringify(existingConfig), + }, + }); + + const config = await client.fetchConfig('test-api-key'); + + expect(config).to.deep.equal(existingConfig); + expect(s3Client.send).to.have.been.calledOnce; + + const command = s3Client.send.firstCall.args[0]; + expect(command.input.Bucket).to.equal('test-bucket'); + expect(command.input.Key).to.equal('opportunities/test-api-key'); + }); + + it('should return null if config does not exist', async () => { + const noSuchKeyError = new Error('NoSuchKey'); + noSuchKeyError.name = 'NoSuchKey'; + s3Client.send.rejects(noSuchKeyError); + + const config = await client.fetchConfig('test-api-key'); + + expect(config).to.be.null; + }); + + it('should return null if S3 returns NoSuchKey error code', async () => { + const noSuchKeyError = new Error('The specified key does not exist'); + noSuchKeyError.Code = 'NoSuchKey'; + s3Client.send.rejects(noSuchKeyError); + + const config = await client.fetchConfig('test-api-key'); + + expect(config).to.be.null; + }); + + it('should throw error if apiKey is missing', async () => { + try { + await client.fetchConfig(''); + expect.fail('Should have thrown error'); + } catch (error) { + expect(error.message).to.include('Tokowaka API key is required'); + expect(error.status).to.equal(400); + } + }); + + it('should handle S3 fetch failure', async () => { + s3Client.send.rejects(new Error('Network error')); + + try { + await client.fetchConfig('test-api-key'); + expect.fail('Should have thrown error'); + } catch (error) { + expect(error.message).to.include('S3 fetch failed'); + expect(error.status).to.equal(500); + } + }); + }); + + describe('mergeConfigs', () => { + let existingConfig; + let newConfig; + + beforeEach(() => { + existingConfig = { + siteId: 'site-123', + baseURL: 'https://example.com', + version: '1.0', + tokowakaForceFail: false, + tokowakaOptimizations: { + '/page1': { + prerender: true, + patches: [ + { + op: 'replace', + selector: 'h1', + value: 'Old Heading', + opportunityId: 'opp-123', + suggestionId: 'sugg-1', + prerenderRequired: true, + lastUpdated: 1234567890, + }, + { + op: 'replace', + selector: 'h2', + value: 'Old Subtitle', + opportunityId: 'opp-456', + suggestionId: 'sugg-2', + prerenderRequired: true, + lastUpdated: 1234567890, + }, + ], + }, + }, + }; + + newConfig = { + siteId: 'site-123', + baseURL: 'https://example.com', + version: '1.0', + tokowakaForceFail: false, + tokowakaOptimizations: { + '/page1': { + prerender: true, + patches: [ + { + op: 'replace', + selector: 'h1', + value: 'Updated Heading', + opportunityId: 'opp-123', + suggestionId: 'sugg-1', + prerenderRequired: true, + lastUpdated: 1234567900, + }, + ], + }, + }, + }; + }); + + it('should return new config if existing config is null', () => { + const merged = client.mergeConfigs(null, newConfig); + + expect(merged).to.deep.equal(newConfig); + }); + + it('should update existing patch with same opportunityId and suggestionId', () => { + const merged = client.mergeConfigs(existingConfig, newConfig); + + expect(merged.tokowakaOptimizations['/page1'].patches).to.have.length(2); + + // First patch should be updated + const updatedPatch = merged.tokowakaOptimizations['/page1'].patches[0]; + expect(updatedPatch.value).to.equal('Updated Heading'); + expect(updatedPatch.lastUpdated).to.equal(1234567900); + + // Second patch should remain unchanged + const unchangedPatch = merged.tokowakaOptimizations['/page1'].patches[1]; + expect(unchangedPatch.value).to.equal('Old Subtitle'); + expect(unchangedPatch.opportunityId).to.equal('opp-456'); + }); + + it('should add new patch if opportunityId and suggestionId do not exist', () => { + newConfig.tokowakaOptimizations['/page1'].patches.push({ + op: 'replace', + selector: 'h3', + value: 'New Section Title', + opportunityId: 'opp-789', + suggestionId: 'sugg-3', + prerenderRequired: true, + lastUpdated: 1234567900, + }); + + const merged = client.mergeConfigs(existingConfig, newConfig); + + expect(merged.tokowakaOptimizations['/page1'].patches).to.have.length(3); + + // New patch should be added at the end + const newPatch = merged.tokowakaOptimizations['/page1'].patches[2]; + expect(newPatch.value).to.equal('New Section Title'); + expect(newPatch.opportunityId).to.equal('opp-789'); + expect(newPatch.suggestionId).to.equal('sugg-3'); + }); + + it('should add new URL path if it does not exist in existing config', () => { + newConfig.tokowakaOptimizations['/page2'] = { + prerender: true, + patches: [ + { + op: 'replace', + selector: 'h1', + value: 'Page 2 Heading', + opportunityId: 'opp-999', + suggestionId: 'sugg-4', + prerenderRequired: true, + lastUpdated: 1234567900, + }, + ], + }; + + const merged = client.mergeConfigs(existingConfig, newConfig); + + expect(merged.tokowakaOptimizations).to.have.property('/page1'); + expect(merged.tokowakaOptimizations).to.have.property('/page2'); + expect(merged.tokowakaOptimizations['/page2'].patches).to.have.length(1); + expect(merged.tokowakaOptimizations['/page2'].patches[0].value).to.equal('Page 2 Heading'); + }); + + it('should preserve existing URL paths not present in new config', () => { + existingConfig.tokowakaOptimizations['/page3'] = { + prerender: false, + patches: [ + { + op: 'replace', + selector: 'h1', + value: 'Page 3 Heading', + opportunityId: 'opp-333', + suggestionId: 'sugg-5', + prerenderRequired: false, + lastUpdated: 1234567890, + }, + ], + }; + + const merged = client.mergeConfigs(existingConfig, newConfig); + + expect(merged.tokowakaOptimizations).to.have.property('/page1'); + expect(merged.tokowakaOptimizations).to.have.property('/page3'); + expect(merged.tokowakaOptimizations['/page3'].patches[0].value).to.equal('Page 3 Heading'); + }); + + it('should update config metadata from new config', () => { + newConfig.version = '2.0'; + newConfig.tokowakaForceFail = true; + + const merged = client.mergeConfigs(existingConfig, newConfig); + + expect(merged.version).to.equal('2.0'); + expect(merged.tokowakaForceFail).to.equal(true); + }); + + it('should handle empty patches array in existing config', () => { + existingConfig.tokowakaOptimizations['/page1'].patches = []; + + const merged = client.mergeConfigs(existingConfig, newConfig); + + expect(merged.tokowakaOptimizations['/page1'].patches).to.have.length(1); + expect(merged.tokowakaOptimizations['/page1'].patches[0].value).to.equal('Updated Heading'); + }); + + it('should handle empty patches array in new config', () => { + newConfig.tokowakaOptimizations['/page1'].patches = []; + + const merged = client.mergeConfigs(existingConfig, newConfig); + + expect(merged.tokowakaOptimizations['/page1'].patches).to.have.length(2); + expect(merged.tokowakaOptimizations['/page1'].patches[0].value).to.equal('Old Heading'); + }); + + it('should handle missing patches property in existing config', () => { + delete existingConfig.tokowakaOptimizations['/page1'].patches; + + const merged = client.mergeConfigs(existingConfig, newConfig); + + expect(merged.tokowakaOptimizations['/page1'].patches).to.have.length(1); + expect(merged.tokowakaOptimizations['/page1'].patches[0].value).to.equal('Updated Heading'); + }); + + it('should handle missing patches property in new config', () => { + delete newConfig.tokowakaOptimizations['/page1'].patches; + + const merged = client.mergeConfigs(existingConfig, newConfig); + + expect(merged.tokowakaOptimizations['/page1'].patches).to.have.length(2); + expect(merged.tokowakaOptimizations['/page1'].patches[0].value).to.equal('Old Heading'); + }); + }); + describe('deploySuggestions', () => { beforeEach(() => { // Stub CDN invalidation for deploy tests @@ -378,6 +661,8 @@ describe('TokowakaClient', () => { provider: 'cloudfront', invalidationId: 'I123', }); + // Stub fetchConfig to return null by default (no existing config) + sinon.stub(client, 'fetchConfig').resolves(null); }); it('should deploy suggestions successfully', async () => { @@ -405,14 +690,6 @@ describe('TokowakaClient', () => { } }); - it('should log progress during deployment', async () => { - await client.deploySuggestions(mockSite, mockOpportunity, mockSuggestions); - - expect(log.info).to.have.been.calledWith(sinon.match(/Generating Tokowaka config/)); - expect(log.info).to.have.been.calledWith(sinon.match(/Uploading Tokowaka config/)); - expect(log.info).to.have.been.calledWith(sinon.match(/Successfully uploaded/)); - }); - it('should handle suggestions that are not eligible for deployment', async () => { // Create suggestions with different checkTypes mockSuggestions = [ @@ -515,6 +792,165 @@ describe('TokowakaClient', () => { expect(result.failedSuggestions).to.have.length(2); expect(result.failedSuggestions[0].reason).to.equal('Suggestion cannot be deployed'); }); + + it('should fetch existing config and merge when deploying', async () => { + const existingConfig = { + siteId: 'site-123', + baseURL: 'https://example.com', + version: '1.0', + tokowakaForceFail: false, + tokowakaOptimizations: { + '/page1': { + prerender: true, + patches: [ + { + op: 'replace', + selector: 'h3', + value: 'Existing Heading', + opportunityId: 'opp-999', + suggestionId: 'sugg-999', + prerenderRequired: true, + lastUpdated: 1234567890, + }, + ], + }, + }, + }; + + client.fetchConfig.resolves(existingConfig); + + const result = await client.deploySuggestions( + mockSite, + mockOpportunity, + mockSuggestions, + ); + + expect(client.fetchConfig).to.have.been.calledWith('test-api-key-123'); + expect(result).to.have.property('s3Path', 'opportunities/test-api-key-123'); + + // Verify the uploaded config contains both existing and new patches + const uploadedConfig = JSON.parse(s3Client.send.firstCall.args[0].input.Body); + expect(uploadedConfig.tokowakaOptimizations['/page1'].patches).to.have.length(3); + }); + + it('should use new config when no existing config found', async () => { + client.fetchConfig.resolves(null); + + const result = await client.deploySuggestions( + mockSite, + mockOpportunity, + mockSuggestions, + ); + + expect(client.fetchConfig).to.have.been.calledWith('test-api-key-123'); + expect(result).to.have.property('s3Path', 'opportunities/test-api-key-123'); + + // Verify only new patches are in the config + const uploadedConfig = JSON.parse(s3Client.send.firstCall.args[0].input.Body); + expect(uploadedConfig.tokowakaOptimizations['/page1'].patches).to.have.length(2); + }); + + it('should update existing patch when deploying same opportunityId and suggestionId', async () => { + const existingConfig = { + siteId: 'site-123', + baseURL: 'https://example.com', + version: '1.0', + tokowakaForceFail: false, + tokowakaOptimizations: { + '/page1': { + prerender: true, + patches: [ + { + op: 'replace', + selector: 'h1', + value: 'Old Heading Value', + opportunityId: 'opp-123', + suggestionId: 'sugg-1', + prerenderRequired: true, + lastUpdated: 1234567890, + }, + ], + }, + }, + }; + + client.fetchConfig.resolves(existingConfig); + + const result = await client.deploySuggestions( + mockSite, + mockOpportunity, + mockSuggestions, + ); + + expect(result).to.have.property('s3Path', 'opportunities/test-api-key-123'); + + // Verify the patch was updated, not duplicated + const uploadedConfig = JSON.parse(s3Client.send.firstCall.args[0].input.Body); + expect(uploadedConfig.tokowakaOptimizations['/page1'].patches).to.have.length(2); + + // First patch should be updated with new value + const updatedPatch = uploadedConfig.tokowakaOptimizations['/page1'].patches[0]; + expect(updatedPatch.value).to.equal('New Heading'); + expect(updatedPatch.opportunityId).to.equal('opp-123'); + expect(updatedPatch.suggestionId).to.equal('sugg-1'); + expect(updatedPatch.lastUpdated).to.be.greaterThan(1234567890); + }); + + it('should preserve existing URL paths when merging', async () => { + const existingConfig = { + siteId: 'site-123', + baseURL: 'https://example.com', + version: '1.0', + tokowakaForceFail: false, + tokowakaOptimizations: { + '/page1': { + prerender: true, + patches: [ + { + op: 'replace', + selector: 'h1', + value: 'Page 1 Heading', + opportunityId: 'opp-123', + suggestionId: 'sugg-1', + prerenderRequired: true, + lastUpdated: 1234567890, + }, + ], + }, + '/other-page': { + prerender: false, + patches: [ + { + op: 'replace', + selector: 'h1', + value: 'Other Page Heading', + opportunityId: 'opp-888', + suggestionId: 'sugg-888', + prerenderRequired: false, + lastUpdated: 1234567890, + }, + ], + }, + }, + }; + + client.fetchConfig.resolves(existingConfig); + + const result = await client.deploySuggestions( + mockSite, + mockOpportunity, + mockSuggestions, + ); + + expect(result).to.have.property('s3Path', 'opportunities/test-api-key-123'); + + // Verify existing URL paths are preserved + const uploadedConfig = JSON.parse(s3Client.send.firstCall.args[0].input.Body); + expect(uploadedConfig.tokowakaOptimizations).to.have.property('/page1'); + expect(uploadedConfig.tokowakaOptimizations).to.have.property('/other-page'); + expect(uploadedConfig.tokowakaOptimizations['/other-page'].patches[0].value) + .to.equal('Other Page Heading'); + }); }); describe('invalidateCdnCache', () => { From 841347434b736f01cd9a5712f1d6d1d7f6bf1fe7 Mon Sep 17 00:00:00 2001 From: Divyansh Pratap Date: Sat, 25 Oct 2025 20:20:14 +0530 Subject: [PATCH 16/20] fix: refactor --- .../src/config-schema | 27 +++++++++++++++++++ .../src/index.js | 4 --- 2 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 packages/spacecat-shared-tokowaka-client/src/config-schema diff --git a/packages/spacecat-shared-tokowaka-client/src/config-schema b/packages/spacecat-shared-tokowaka-client/src/config-schema new file mode 100644 index 000000000..4516ebae8 --- /dev/null +++ b/packages/spacecat-shared-tokowaka-client/src/config-schema @@ -0,0 +1,27 @@ +example config: + +{ + siteId: 9ae8877a-bbf3-407d-9adb-d6a72ce3c5e3 + baseURL: "https://airindia.com" + version: "1.0", + tokowakaForceFail: false, + tokowakaOptimizations: { + "/in/en/book/exclusive-deals.html" : { + "prerender": true, + "patches": [ + { + "op": "replace", + "selector": "h2.offer-title", + "value": "Exclusive Flight Booking Offers & Partner Discounts", + "opportunityId": efb7277c-a00b-4f31-9fc6-10ac1a1659c2, + "suggestionId": 7d254b0c-7286-4929-9f04-0a73acdb1d4d, + "prerenderRequired": true, + "lastUpdated": 1760443321346 + } + ] + } + } +} + + +possible ops: '' \ No newline at end of file diff --git a/packages/spacecat-shared-tokowaka-client/src/index.js b/packages/spacecat-shared-tokowaka-client/src/index.js index 5a4d97dfc..e51e4b381 100644 --- a/packages/spacecat-shared-tokowaka-client/src/index.js +++ b/packages/spacecat-shared-tokowaka-client/src/index.js @@ -257,15 +257,11 @@ class TokowakaClient { const existing = patchMap.get(key); if (existing) { - // Update existing patch mergedPatches[existing.index] = newPatch; updateCount += 1; - this.log.debug(`Updated patch for ${key} at ${urlPath}`); } else { - // Add new patch mergedPatches.push(newPatch); addCount += 1; - this.log.debug(`Added new patch for ${key} at ${urlPath}`); } }); From 36a44b8ffa18bb85ed3cf03dca988a00f7461e29 Mon Sep 17 00:00:00 2001 From: Divyansh Pratap Date: Wed, 29 Oct 2025 00:09:47 +0530 Subject: [PATCH 17/20] fix: heading and summarization oppty --- package-lock.json | 313 ++++++++++++++- .../package.json | 5 +- .../src/cdn/base-cdn-client.js | 12 +- .../src/cdn/cloudfront-cdn-client.js | 4 +- .../src/config-schema | 27 -- .../src/constants.js | 17 + .../src/index.js | 21 +- .../src/mappers/base-mapper.js | 38 +- .../mappers/content-summarization-mapper.js | 106 ++++++ .../src/mappers/headings-mapper.js | 89 +++-- .../src/mappers/mapper-registry.js | 2 + .../test/cdn/base-cdn-client.test.js | 5 +- .../test/index.test.js | 120 +++++- .../test/mappers/base-mapper.test.js | 72 +++- .../test/mappers/content-mapper.test.js | 355 ++++++++++++++++++ .../test/mappers/headings-mapper.test.js | 317 ++++++++++++++-- 16 files changed, 1352 insertions(+), 151 deletions(-) delete mode 100644 packages/spacecat-shared-tokowaka-client/src/config-schema create mode 100644 packages/spacecat-shared-tokowaka-client/src/constants.js create mode 100644 packages/spacecat-shared-tokowaka-client/src/mappers/content-summarization-mapper.js create mode 100644 packages/spacecat-shared-tokowaka-client/test/mappers/content-mapper.test.js diff --git a/package-lock.json b/package-lock.json index b8ca8f354..ac2de3c49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10930,6 +10930,78 @@ "node": ">= 0.4" } }, + "node_modules/mdast-util-definitions": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", + "integrity": "sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-definitions/node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/mdast-util-definitions/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/mdast-util-definitions/node_modules/unist-util-is": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-definitions/node_modules/unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-definitions/node_modules/unist-util-visit-parents": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-find-and-replace": { "version": "3.0.2", "license": "MIT", @@ -11079,6 +11151,8 @@ }, "node_modules/mdast-util-to-hast": { "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -15636,6 +15710,8 @@ }, "node_modules/remark-parse": { "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -17718,6 +17794,8 @@ }, "node_modules/unified": { "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -17747,6 +17825,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/unist-builder": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-3.0.1.tgz", + "integrity": "sha512-gnpOw7DIpCA0vpr6NqdPvTWnlPTApCTRzr+38E6hCWx3rz/cjo83SsKIlS1Z+L5ttScQ2AwutNnb8+tAvpb6qQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-builder/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, "node_modules/unist-util-find": { "version": "3.0.0", "license": "MIT", @@ -17852,6 +17949,16 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/unist-util-generated": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.1.tgz", + "integrity": "sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unist-util-is": { "version": "6.0.0", "license": "MIT", @@ -56422,7 +56529,9 @@ "dependencies": { "@adobe/spacecat-shared-utils": "1.59.4", "@aws-sdk/client-cloudfront": "3.893.0", - "@aws-sdk/client-s3": "3.893.0" + "@aws-sdk/client-s3": "3.893.0", + "mdast-util-from-markdown": "2.0.2", + "mdast-util-to-hast": "12.1.0" }, "devDependencies": { "c8": "^10.1.3", @@ -59013,6 +59122,36 @@ "node": ">=14.0.0" } }, + "packages/spacecat-shared-tokowaka-client/node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2" + } + }, + "packages/spacecat-shared-tokowaka-client/node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2" + } + }, + "packages/spacecat-shared-tokowaka-client/node_modules/@types/mdurl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz", + "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==", + "license": "MIT" + }, + "packages/spacecat-shared-tokowaka-client/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, "packages/spacecat-shared-tokowaka-client/node_modules/aws-xray-sdk-core": { "version": "3.10.2", "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.10.2.tgz", @@ -59116,6 +59255,123 @@ "@sideway/pinpoint": "^2.0.0" } }, + "packages/spacecat-shared-tokowaka-client/node_modules/mdast-util-to-hast": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.1.0.tgz", + "integrity": "sha512-dHfCt9Yh05AXEeghoziB3DjJV8oCIKdQmBJOPoAT1NlgMDBy+/MQn7Pxfq0jI8YRO1IfzcnmA/OU3FVVn/E5Sg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "@types/mdurl": "^1.0.0", + "mdast-util-definitions": "^5.0.0", + "mdurl": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "unist-builder": "^3.0.0", + "unist-util-generated": "^2.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/spacecat-shared-tokowaka-client/node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "license": "MIT" + }, + "packages/spacecat-shared-tokowaka-client/node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "packages/spacecat-shared-tokowaka-client/node_modules/micromark-util-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "packages/spacecat-shared-tokowaka-client/node_modules/micromark-util-sanitize-uri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "packages/spacecat-shared-tokowaka-client/node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "packages/spacecat-shared-tokowaka-client/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, "packages/spacecat-shared-tokowaka-client/node_modules/semver": { "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", @@ -59140,6 +59396,61 @@ ], "license": "MIT" }, + "packages/spacecat-shared-tokowaka-client/node_modules/unist-util-is": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/spacecat-shared-tokowaka-client/node_modules/unist-util-position": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.4.tgz", + "integrity": "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/spacecat-shared-tokowaka-client/node_modules/unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "packages/spacecat-shared-tokowaka-client/node_modules/unist-util-visit-parents": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "packages/spacecat-shared-utils": { "name": "@adobe/spacecat-shared-utils", "version": "1.63.0", diff --git a/packages/spacecat-shared-tokowaka-client/package.json b/packages/spacecat-shared-tokowaka-client/package.json index c8cd53079..88f4e1ba2 100644 --- a/packages/spacecat-shared-tokowaka-client/package.json +++ b/packages/spacecat-shared-tokowaka-client/package.json @@ -33,7 +33,9 @@ "dependencies": { "@adobe/spacecat-shared-utils": "1.59.4", "@aws-sdk/client-cloudfront": "3.893.0", - "@aws-sdk/client-s3": "3.893.0" + "@aws-sdk/client-s3": "3.893.0", + "mdast-util-to-hast": "12.1.0", + "mdast-util-from-markdown": "2.0.2" }, "devDependencies": { "c8": "^10.1.3", @@ -45,4 +47,3 @@ "sinon-chai": "^4.0.1" } } - diff --git a/packages/spacecat-shared-tokowaka-client/src/cdn/base-cdn-client.js b/packages/spacecat-shared-tokowaka-client/src/cdn/base-cdn-client.js index a8351b9bc..7566bb748 100644 --- a/packages/spacecat-shared-tokowaka-client/src/cdn/base-cdn-client.js +++ b/packages/spacecat-shared-tokowaka-client/src/cdn/base-cdn-client.js @@ -24,8 +24,8 @@ export default class BaseCdnClient { * Returns the CDN provider name (e.g., 'akamai', 'cloudflare', 'fastly') * @returns {string} The CDN provider name */ - // eslint-disable-next-line class-methods-use-this getProviderName() { + this.log.error('getProviderName() must be implemented by subclass'); throw new Error('getProviderName() must be implemented by subclass'); } @@ -33,18 +33,18 @@ export default class BaseCdnClient { * Validates the CDN configuration * @returns {boolean} True if configuration is valid */ - // eslint-disable-next-line class-methods-use-this, no-unused-vars validateConfig() { - return true; // Override in subclass if needed + this.log.error('validateConfig() must be implemented by subclass'); + throw new Error('validateConfig() must be implemented by subclass'); } /** * Invalidates the CDN cache for the given paths - * @param {Array} paths - Array of URL paths to invalidate + * @param {Array} _ - Array of URL paths to invalidate * @returns {Promise} Result of the invalidation request */ - // eslint-disable-next-line class-methods-use-this, no-unused-vars - async invalidateCache(_paths) { + async invalidateCache(_) { + this.log.error('invalidateCache() must be implemented by subclass'); throw new Error('invalidateCache() must be implemented by subclass'); } } diff --git a/packages/spacecat-shared-tokowaka-client/src/cdn/cloudfront-cdn-client.js b/packages/spacecat-shared-tokowaka-client/src/cdn/cloudfront-cdn-client.js index e0cb8e20d..e8e17bd10 100644 --- a/packages/spacecat-shared-tokowaka-client/src/cdn/cloudfront-cdn-client.js +++ b/packages/spacecat-shared-tokowaka-client/src/cdn/cloudfront-cdn-client.js @@ -36,11 +36,11 @@ export default class CloudFrontCdnClient extends BaseCdnClient { this.cdnConfig = parsedConfig.cloudfront; this.client = null; + this.providerName = 'cloudfront'; } - // eslint-disable-next-line class-methods-use-this getProviderName() { - return 'cloudfront'; + return this.providerName; } validateConfig() { diff --git a/packages/spacecat-shared-tokowaka-client/src/config-schema b/packages/spacecat-shared-tokowaka-client/src/config-schema deleted file mode 100644 index 4516ebae8..000000000 --- a/packages/spacecat-shared-tokowaka-client/src/config-schema +++ /dev/null @@ -1,27 +0,0 @@ -example config: - -{ - siteId: 9ae8877a-bbf3-407d-9adb-d6a72ce3c5e3 - baseURL: "https://airindia.com" - version: "1.0", - tokowakaForceFail: false, - tokowakaOptimizations: { - "/in/en/book/exclusive-deals.html" : { - "prerender": true, - "patches": [ - { - "op": "replace", - "selector": "h2.offer-title", - "value": "Exclusive Flight Booking Offers & Partner Discounts", - "opportunityId": efb7277c-a00b-4f31-9fc6-10ac1a1659c2, - "suggestionId": 7d254b0c-7286-4929-9f04-0a73acdb1d4d, - "prerenderRequired": true, - "lastUpdated": 1760443321346 - } - ] - } - } -} - - -possible ops: '' \ No newline at end of file diff --git a/packages/spacecat-shared-tokowaka-client/src/constants.js b/packages/spacecat-shared-tokowaka-client/src/constants.js new file mode 100644 index 000000000..39c3f9386 --- /dev/null +++ b/packages/spacecat-shared-tokowaka-client/src/constants.js @@ -0,0 +1,17 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +export const TARGET_USER_AGENTS_CATEGORIES = { + AI_BOTS: 'ai-bots', + BOTS: 'bots', + ALL: 'all', +}; diff --git a/packages/spacecat-shared-tokowaka-client/src/index.js b/packages/spacecat-shared-tokowaka-client/src/index.js index e51e4b381..6240b14d8 100644 --- a/packages/spacecat-shared-tokowaka-client/src/index.js +++ b/packages/spacecat-shared-tokowaka-client/src/index.js @@ -11,7 +11,7 @@ */ import { GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3'; -import { hasText, isNonEmptyObject } from '@adobe/spacecat-shared-utils'; +import { hasText, isNonEmptyObject, isValidUrl } from '@adobe/spacecat-shared-utils'; import MapperRegistry from './mappers/mapper-registry.js'; import CdnClientRegistry from './cdn/cdn-client-registry.js'; @@ -89,7 +89,12 @@ class TokowakaClient { generateConfig(site, opportunity, suggestions) { const opportunityType = opportunity.getType(); const siteId = site.getId(); - const baseURL = site.getBaseURL(); + + // Get baseURL, respecting overrideBaseURL from fetchConfig if it exists + const overrideBaseURL = site.getConfig()?.getFetchConfig?.()?.overrideBaseURL; + const baseURL = (overrideBaseURL && isValidUrl(overrideBaseURL)) + ? overrideBaseURL + : site.getBaseURL(); // Get mapper for this opportunity type const mapper = this.mapperRegistry.getMapper(opportunityType); @@ -113,9 +118,9 @@ class TokowakaClient { let urlPath; try { - urlPath = new URL(url).pathname; + urlPath = new URL(url, baseURL).pathname; } catch (e) { - this.log.warn(`Invalid URL for suggestion ${suggestion.getId()}: ${url}`); + this.log.warn(`Failed to extract pathname from URL for suggestion ${suggestion.getId()}: ${url}`); return acc; } @@ -406,6 +411,14 @@ class TokowakaClient { this.log.debug(`Generating Tokowaka config for site ${site.getId()}, opportunity ${opportunity.getId()}`); const newConfig = this.generateConfig(site, opportunity, eligibleSuggestions); + if (Object.keys(newConfig.tokowakaOptimizations).length === 0) { + this.log.warn(''); + return { + succeededSuggestions: [], + failedSuggestions: suggestions, + }; + } + // Merge with existing config if it exists const config = existingConfig ? this.mergeConfigs(existingConfig, newConfig) diff --git a/packages/spacecat-shared-tokowaka-client/src/mappers/base-mapper.js b/packages/spacecat-shared-tokowaka-client/src/mappers/base-mapper.js index 0a2df28cc..2fded2166 100644 --- a/packages/spacecat-shared-tokowaka-client/src/mappers/base-mapper.js +++ b/packages/spacecat-shared-tokowaka-client/src/mappers/base-mapper.js @@ -24,8 +24,8 @@ export default class BaseOpportunityMapper { * @abstract * @returns {string} - Opportunity type */ - // eslint-disable-next-line class-methods-use-this getOpportunityType() { + this.log.error('getOpportunityType() must be implemented by subclass'); throw new Error('getOpportunityType() must be implemented by subclass'); } @@ -34,8 +34,8 @@ export default class BaseOpportunityMapper { * @abstract * @returns {boolean} - True if prerendering is required */ - // eslint-disable-next-line class-methods-use-this requiresPrerender() { + this.log.error('requiresPrerender() must be implemented by subclass'); throw new Error('requiresPrerender() must be implemented by subclass'); } @@ -43,48 +43,44 @@ export default class BaseOpportunityMapper { * Converts a suggestion to a Tokowaka patch * @abstract * @param {Object} _ - Suggestion entity with getId() and getData() methods - * @param {string} _ - Opportunity ID + * @param {string} __ - Opportunity ID * @returns {Object|null} - Patch object or null if conversion fails */ - // eslint-disable-next-line class-methods-use-this, no-unused-vars + // eslint-disable-next-line no-unused-vars suggestionToPatch(_, __) { + this.log.error('suggestionToPatch() must be implemented by subclass'); throw new Error('suggestionToPatch() must be implemented by subclass'); } - /** - * Validates suggestion data before conversion - * @param {Object} _ - Suggestion data - * @returns {boolean} - True if valid - */ - // eslint-disable-next-line class-methods-use-this, no-unused-vars - validateSuggestionData(_) { - return false; // Override in subclass if needed - } - /** * Checks if a suggestion can be deployed for this opportunity type - * Override this method to add custom deployment eligibility checks + * This method should validate all eligibility and data requirements + * @abstract * @param {Object} _ - Suggestion object * @returns {Object} - { eligible: boolean, reason?: string } */ - // eslint-disable-next-line class-methods-use-this, no-unused-vars + // eslint-disable-next-line no-unused-vars canDeploy(_) { - return { eligible: true }; + this.log.error('canDeploy() must be implemented by subclass'); + throw new Error('canDeploy() must be implemented by subclass'); } /** * Helper method to create base patch structure * @protected - * @param {string} suggestionId - Suggestion ID + * @param {Object} suggestion - Suggestion entity with getUpdatedAt() method * @param {string} opportunityId - Opportunity ID * @returns {Object} - Base patch object */ - createBasePatch(suggestionId, opportunityId) { + createBasePatch(suggestion, opportunityId) { + const updatedAt = suggestion.getUpdatedAt(); + const lastUpdated = updatedAt ? new Date(updatedAt).getTime() : Date.now(); + return { opportunityId, - suggestionId, + suggestionId: suggestion.getId(), prerenderRequired: this.requiresPrerender(), - lastUpdated: Date.now(), + lastUpdated, }; } } diff --git a/packages/spacecat-shared-tokowaka-client/src/mappers/content-summarization-mapper.js b/packages/spacecat-shared-tokowaka-client/src/mappers/content-summarization-mapper.js new file mode 100644 index 000000000..e406d80de --- /dev/null +++ b/packages/spacecat-shared-tokowaka-client/src/mappers/content-summarization-mapper.js @@ -0,0 +1,106 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import { toHast } from 'mdast-util-to-hast'; +import { fromMarkdown } from 'mdast-util-from-markdown'; +import { hasText } from '@adobe/spacecat-shared-utils'; +import { TARGET_USER_AGENTS_CATEGORIES } from '../constants.js'; +import BaseOpportunityMapper from './base-mapper.js'; + +/** + * Mapper for content opportunity + * Handles conversion of content summarization suggestions to Tokowaka patches + */ +export default class ContentSummarizationMapper extends BaseOpportunityMapper { + constructor(log) { + super(log); + this.opportunityType = 'summarization'; + this.prerenderRequired = true; + this.validActions = ['insertAfter', 'insertBefore', 'appendChild']; + } + + getOpportunityType() { + return this.opportunityType; + } + + requiresPrerender() { + return this.prerenderRequired; + } + + /** + * Converts markdown text to HAST (Hypertext Abstract Syntax Tree) format + * @param {string} markdown - Markdown text + * @returns {Object} - HAST object + */ + // eslint-disable-next-line class-methods-use-this + markdownToHast(markdown) { + const mdast = fromMarkdown(markdown); + return toHast(mdast); + } + + suggestionToPatch(suggestion, opportunityId) { + const eligibility = this.canDeploy(suggestion); + if (!eligibility.eligible) { + this.log.warn(`Content-Summarization suggestion ${suggestion.getId()} cannot be deployed: ${eligibility.reason}`); + return null; + } + + const data = suggestion.getData(); + const { summarizationText, transformRules } = data; + + // Convert markdown to HAST + let hastValue; + try { + hastValue = this.markdownToHast(summarizationText); + } catch (error) { + this.log.error(`Failed to convert markdown to HAST for suggestion ${suggestion.getId()}: ${error.message}`); + return null; + } + + return { + ...this.createBasePatch(suggestion, opportunityId), + op: transformRules.action, + selector: transformRules.selector, + value: hastValue, + valueFormat: 'hast', + target: TARGET_USER_AGENTS_CATEGORIES.AI_BOTS, + }; + } + + /** + * Checks if a content suggestion can be deployed + * @param {Object} suggestion - Suggestion object + * @returns {Object} { eligible: boolean, reason?: string } + */ + canDeploy(suggestion) { + const data = suggestion.getData(); + + // Validate required fields + if (!data?.summarizationText) { + return { eligible: false, reason: 'summarizationText is required' }; + } + + if (!data.transformRules) { + return { eligible: false, reason: 'transformRules is required' }; + } + + if (!hasText(data.transformRules.selector)) { + return { eligible: false, reason: 'transformRules.selector is required' }; + } + + if (!this.validActions.includes(data.transformRules.action)) { + return { eligible: false, reason: 'transformRules.action must be insertAfter, insertBefore, or appendChild' }; + } + + return { eligible: true }; + } +} diff --git a/packages/spacecat-shared-tokowaka-client/src/mappers/headings-mapper.js b/packages/spacecat-shared-tokowaka-client/src/mappers/headings-mapper.js index 951b95619..3c4af192f 100644 --- a/packages/spacecat-shared-tokowaka-client/src/mappers/headings-mapper.js +++ b/packages/spacecat-shared-tokowaka-client/src/mappers/headings-mapper.js @@ -10,6 +10,8 @@ * governing permissions and limitations under the License. */ +import { hasText } from '@adobe/spacecat-shared-utils'; +import { TARGET_USER_AGENTS_CATEGORIES } from '../constants.js'; import BaseOpportunityMapper from './base-mapper.js'; /** @@ -17,46 +19,49 @@ import BaseOpportunityMapper from './base-mapper.js'; * Handles conversion of heading suggestions to Tokowaka patches */ export default class HeadingsMapper extends BaseOpportunityMapper { - // eslint-disable-next-line class-methods-use-this + constructor(log) { + super(log); + this.opportunityType = 'headings'; + this.prerenderRequired = true; + } + getOpportunityType() { - return 'headings'; + return this.opportunityType; } - // eslint-disable-next-line class-methods-use-this requiresPrerender() { - return true; + return this.prerenderRequired; } suggestionToPatch(suggestion, opportunityId) { - const data = suggestion.getData(); - - if (!this.validateSuggestionData(data)) { - this.log.warn(`Headings suggestion ${suggestion.getId()} has invalid data`); + const eligibility = this.canDeploy(suggestion); + if (!eligibility.eligible) { + this.log.warn(`Headings suggestion ${suggestion.getId()} cannot be deployed: ${eligibility.reason}`); return null; } - // Use path if available, otherwise construct from headingTag - const selector = data.path || data.headingTag; + const data = suggestion.getData(); + const { checkType, transformRules } = data; - return { - ...this.createBasePatch(suggestion.getId(), opportunityId), - op: 'replace', - selector, + const patch = { + ...this.createBasePatch(suggestion, opportunityId), + op: transformRules.action, + selector: transformRules.selector, value: data.recommendedAction, + valueFormat: 'text', + ...(data.currentValue !== null && { currValue: data.currentValue }), + target: TARGET_USER_AGENTS_CATEGORIES.AI_BOTS, }; - } - // eslint-disable-next-line class-methods-use-this - validateSuggestionData(data) { - // At minimum, need heading selector (path or headingTag) and recommendedAction/value - const hasSelector = data?.path || data?.headingTag; - const hasValue = data?.recommendedAction; - return !!(hasSelector && hasValue); + if (checkType === 'heading-missing-h1' && transformRules.tag) { + patch.tag = transformRules.tag; + } + return patch; } /** * Checks if a heading suggestion can be deployed - * Only empty headings are eligible for deployment + * Supports: heading-empty, heading-missing-h1, heading-h1-length * @param {Object} suggestion - Suggestion object * @returns {Object} { eligible: boolean, reason?: string } */ @@ -65,13 +70,49 @@ export default class HeadingsMapper extends BaseOpportunityMapper { const data = suggestion.getData(); const checkType = data?.checkType; - if (checkType !== 'heading-empty') { + // Check if checkType is eligible + const eligibleCheckTypes = ['heading-empty', 'heading-missing-h1', 'heading-h1-length']; + if (!eligibleCheckTypes.includes(checkType)) { return { eligible: false, - reason: `Only empty headings can be deployed. This suggestion has checkType: ${checkType}`, + reason: `Only ${eligibleCheckTypes.join(', ')} can be deployed. This suggestion has checkType: ${checkType}`, }; } + // Validate required fields + if (!data?.recommendedAction) { + return { eligible: false, reason: 'recommendedAction is required' }; + } + + if (!hasText(data.transformRules?.selector)) { + return { eligible: false, reason: 'transformRules.selector is required' }; + } + + // Validate based on checkType + if (checkType === 'heading-missing-h1') { + if (!['insertBefore', 'insertAfter'].includes(data.transformRules?.action)) { + return { + eligible: false, + reason: 'transformRules.action must be insertBefore or insertAfter for heading-missing-h1', + }; + } + if (!hasText(data.transformRules?.tag)) { + return { + eligible: false, + reason: 'transformRules.tag is required for heading-missing-h1', + }; + } + } + + if (checkType === 'heading-h1-length' || checkType === 'heading-empty') { + if (data.transformRules?.action !== 'replace') { + return { + eligible: false, + reason: `transformRules.action must be replace for ${checkType}`, + }; + } + } + return { eligible: true }; } } diff --git a/packages/spacecat-shared-tokowaka-client/src/mappers/mapper-registry.js b/packages/spacecat-shared-tokowaka-client/src/mappers/mapper-registry.js index 9aef91b45..d4a2060c0 100644 --- a/packages/spacecat-shared-tokowaka-client/src/mappers/mapper-registry.js +++ b/packages/spacecat-shared-tokowaka-client/src/mappers/mapper-registry.js @@ -11,6 +11,7 @@ */ import HeadingsMapper from './headings-mapper.js'; +import ContentSummarizationMapper from './content-summarization-mapper.js'; /** * Registry for opportunity mappers @@ -30,6 +31,7 @@ export default class MapperRegistry { #registerDefaultMappers() { const defaultMappers = [ HeadingsMapper, + ContentSummarizationMapper, // more mappers here ]; diff --git a/packages/spacecat-shared-tokowaka-client/test/cdn/base-cdn-client.test.js b/packages/spacecat-shared-tokowaka-client/test/cdn/base-cdn-client.test.js index 4c4a30709..2ce9f3000 100644 --- a/packages/spacecat-shared-tokowaka-client/test/cdn/base-cdn-client.test.js +++ b/packages/spacecat-shared-tokowaka-client/test/cdn/base-cdn-client.test.js @@ -35,8 +35,9 @@ describe('BaseCdnClient', () => { .to.throw('getProviderName() must be implemented by subclass'); }); - it('validateConfig should return true by default', () => { - expect(client.validateConfig()).to.be.true; + it('validateConfig should throw error', () => { + expect(() => client.validateConfig()) + .to.throw('validateConfig() must be implemented by subclass'); }); it('invalidateCache should throw error', async () => { diff --git a/packages/spacecat-shared-tokowaka-client/test/index.test.js b/packages/spacecat-shared-tokowaka-client/test/index.test.js index f631cf13b..95b5bde61 100644 --- a/packages/spacecat-shared-tokowaka-client/test/index.test.js +++ b/packages/spacecat-shared-tokowaka-client/test/index.test.js @@ -70,20 +70,28 @@ describe('TokowakaClient', () => { mockSuggestions = [ { getId: () => 'sugg-1', + getUpdatedAt: () => '2025-01-15T10:00:00.000Z', getData: () => ({ url: 'https://example.com/page1', - headingTag: 'h1', recommendedAction: 'New Heading', checkType: 'heading-empty', + transformRules: { + action: 'replace', + selector: 'h1', + }, }), }, { getId: () => 'sugg-2', + getUpdatedAt: () => '2025-01-15T10:00:00.000Z', getData: () => ({ url: 'https://example.com/page1', - headingTag: 'h2', recommendedAction: 'New Subtitle', checkType: 'heading-empty', + transformRules: { + action: 'replace', + selector: 'h2', + }, }), }, ]; @@ -220,18 +228,28 @@ describe('TokowakaClient', () => { mockSuggestions = [ { getId: () => 'sugg-1', + getUpdatedAt: () => '2025-01-15T10:00:00.000Z', getData: () => ({ url: 'https://example.com/page1', - headingTag: 'h1', recommendedAction: 'Page 1 Heading', + checkType: 'heading-empty', + transformRules: { + action: 'replace', + selector: 'h1', + }, }), }, { getId: () => 'sugg-2', + getUpdatedAt: () => '2025-01-15T10:00:00.000Z', getData: () => ({ url: 'https://example.com/page2', - headingTag: 'h1', recommendedAction: 'Page 2 Heading', + checkType: 'heading-empty', + transformRules: { + action: 'replace', + selector: 'h1', + }, }), }, ]; @@ -243,6 +261,40 @@ describe('TokowakaClient', () => { expect(config.tokowakaOptimizations).to.have.property('/page2'); }); + it('should use overrideBaseURL from fetchConfig when available', () => { + // Set up mockSite with overrideBaseURL + mockSite.getConfig = () => ({ + getTokowakaConfig: () => ({ + apiKey: 'test-api-key-123', + cdnProvider: 'cloudfront', + }), + getFetchConfig: () => ({ + overrideBaseURL: 'https://override.example.com', + }), + }); + + mockSuggestions = [ + { + getId: () => 'sugg-override', + getUpdatedAt: () => '2025-01-15T10:00:00.000Z', + getData: () => ({ + url: '/relative-path', + recommendedAction: 'Heading', + checkType: 'heading-empty', + transformRules: { + action: 'replace', + selector: 'h1', + }, + }), + }, + ]; + + const config = client.generateConfig(mockSite, mockOpportunity, mockSuggestions); + + expect(Object.keys(config.tokowakaOptimizations)).to.have.length(1); + expect(config.tokowakaOptimizations).to.have.property('/relative-path'); + }); + it('should skip suggestions without URL', () => { mockSuggestions = [ { @@ -264,10 +316,15 @@ describe('TokowakaClient', () => { mockSuggestions = [ { getId: () => 'sugg-1', + getUpdatedAt: () => '2025-01-15T10:00:00.000Z', getData: () => ({ - url: 'not-a-valid-url', - selector: 'h1', - value: 'Heading', + url: 'http://invalid domain with spaces.com', + checkType: 'heading-empty', + recommendedAction: 'Heading', + transformRules: { + action: 'replace', + selector: 'h1', + }, }), }, ]; @@ -275,7 +332,7 @@ describe('TokowakaClient', () => { const config = client.generateConfig(mockSite, mockOpportunity, mockSuggestions); expect(Object.keys(config.tokowakaOptimizations)).to.have.length(0); - expect(log.warn).to.have.been.calledWith(sinon.match(/Invalid URL/)); + expect(log.warn).to.have.been.calledWith(sinon.match(/Failed to extract pathname from URL/)); }); it('should skip suggestions with missing required fields', () => { @@ -284,7 +341,7 @@ describe('TokowakaClient', () => { getId: () => 'sugg-1', getData: () => ({ url: 'https://example.com/page1', - // Missing headingTag and recommendedAction + // Missing required fields }), }, ]; @@ -292,7 +349,7 @@ describe('TokowakaClient', () => { const config = client.generateConfig(mockSite, mockOpportunity, mockSuggestions); expect(Object.keys(config.tokowakaOptimizations)).to.have.length(0); - expect(log.warn).to.have.been.calledWith(sinon.match(/has invalid data/)); + expect(log.warn).to.have.been.calledWith(sinon.match(/cannot be deployed/)); }); it('should handle unsupported opportunity types', () => { @@ -697,18 +754,21 @@ describe('TokowakaClient', () => { getId: () => 'sugg-1', getData: () => ({ url: 'https://example.com/page1', - headingTag: 'h1', recommendedAction: 'New Heading', - checkType: 'heading-missing', // Not eligible + checkType: 'heading-missing', // Not eligible (wrong checkType name) }), }, { getId: () => 'sugg-2', + getUpdatedAt: () => '2025-01-15T10:00:00.000Z', getData: () => ({ url: 'https://example.com/page1', - headingTag: 'h2', recommendedAction: 'New Subtitle', checkType: 'heading-empty', // Eligible + transformRules: { + action: 'replace', + selector: 'h2', + }, }), }, ]; @@ -721,7 +781,7 @@ describe('TokowakaClient', () => { expect(result.succeededSuggestions).to.have.length(1); expect(result.failedSuggestions).to.have.length(1); - expect(result.failedSuggestions[0].reason).to.include('Only empty headings can be deployed'); + expect(result.failedSuggestions[0].reason).to.include('can be deployed'); }); it('should return early when no eligible suggestions', async () => { @@ -731,9 +791,8 @@ describe('TokowakaClient', () => { getId: () => 'sugg-1', getData: () => ({ url: 'https://example.com/page1', - headingTag: 'h1', recommendedAction: 'New Heading', - checkType: 'heading-missing', + checkType: 'heading-missing', // Wrong checkType name, not eligible }), }, ]; @@ -750,6 +809,35 @@ describe('TokowakaClient', () => { expect(s3Client.send).to.not.have.been.called; }); + it('should return early when suggestions pass eligibility but fail during config generation', async () => { + // Suggestions pass canDeploy but have no URL (caught in generateConfig) + mockSuggestions = [ + { + getId: () => 'sugg-1', + getUpdatedAt: () => '2025-01-15T10:00:00.000Z', + getData: () => ({ + // Missing URL + recommendedAction: 'New Heading', + checkType: 'heading-empty', + transformRules: { + action: 'replace', + selector: 'h1', + }, + }), + }, + ]; + + const result = await client.deploySuggestions( + mockSite, + mockOpportunity, + mockSuggestions, + ); + + expect(result.succeededSuggestions).to.have.length(0); + expect(result.failedSuggestions).to.have.length(1); + expect(s3Client.send).to.not.have.been.called; + }); + it('should throw error for unsupported opportunity type', async () => { mockOpportunity.getType = () => 'unsupported-type'; diff --git a/packages/spacecat-shared-tokowaka-client/test/mappers/base-mapper.test.js b/packages/spacecat-shared-tokowaka-client/test/mappers/base-mapper.test.js index 4fddfb176..7d48d157d 100644 --- a/packages/spacecat-shared-tokowaka-client/test/mappers/base-mapper.test.js +++ b/packages/spacecat-shared-tokowaka-client/test/mappers/base-mapper.test.js @@ -11,15 +11,23 @@ */ /* eslint-env mocha */ +/* eslint-disable max-classes-per-file, class-methods-use-this */ import { expect } from 'chai'; import BaseOpportunityMapper from '../../src/mappers/base-mapper.js'; describe('BaseOpportunityMapper', () => { let mapper; + let log; beforeEach(() => { - mapper = new BaseOpportunityMapper(); + log = { + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, + }; + mapper = new BaseOpportunityMapper(log); }); describe('abstract methods', () => { @@ -38,15 +46,65 @@ describe('BaseOpportunityMapper', () => { .to.throw('suggestionToPatch() must be implemented by subclass'); }); - it('validateSuggestionData should return false by default', () => { - const result = mapper.validateSuggestionData({}); - expect(result).to.be.false; + it('canDeploy should throw error if not implemented', () => { + expect(() => mapper.canDeploy({})) + .to.throw('canDeploy() must be implemented by subclass'); }); + }); + + describe('createBasePatch', () => { + it('should use getUpdatedAt method when available', () => { + // Create a concrete subclass for testing + class TestMapper extends BaseOpportunityMapper { + getOpportunityType() { return 'test'; } + + requiresPrerender() { return true; } + + suggestionToPatch() { return {}; } + + canDeploy() { return { eligible: true }; } + } + + const testMapper = new TestMapper(); + const suggestion = { + getId: () => 'test-123', + getUpdatedAt: () => '2025-01-15T10:00:00.000Z', + }; + + const patch = testMapper.createBasePatch(suggestion, 'opp-456'); + + expect(patch.suggestionId).to.equal('test-123'); + expect(patch.opportunityId).to.equal('opp-456'); + expect(patch.lastUpdated).to.equal(new Date('2025-01-15T10:00:00.000Z').getTime()); + expect(patch.prerenderRequired).to.be.true; + }); + + it('should use Date.now() when getUpdatedAt returns null', () => { + class TestMapper extends BaseOpportunityMapper { + getOpportunityType() { return 'test'; } + + requiresPrerender() { return true; } + + suggestionToPatch() { return {}; } + + canDeploy() { return { eligible: true }; } + } + + const testMapper = new TestMapper(); + const suggestion = { + getId: () => 'test-no-date', + getUpdatedAt: () => null, // Returns null + }; - it('canDeploy should return eligible by default', () => { - const result = mapper.canDeploy({}); + const beforeTime = Date.now(); + const patch = testMapper.createBasePatch(suggestion, 'opp-fallback'); + const afterTime = Date.now(); - expect(result).to.deep.equal({ eligible: true }); + expect(patch.suggestionId).to.equal('test-no-date'); + expect(patch.opportunityId).to.equal('opp-fallback'); + expect(patch.lastUpdated).to.be.at.least(beforeTime); + expect(patch.lastUpdated).to.be.at.most(afterTime); + expect(patch.prerenderRequired).to.be.true; }); }); }); diff --git a/packages/spacecat-shared-tokowaka-client/test/mappers/content-mapper.test.js b/packages/spacecat-shared-tokowaka-client/test/mappers/content-mapper.test.js new file mode 100644 index 000000000..2a98a97bb --- /dev/null +++ b/packages/spacecat-shared-tokowaka-client/test/mappers/content-mapper.test.js @@ -0,0 +1,355 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +/* eslint-env mocha */ + +import { expect } from 'chai'; +import sinon from 'sinon'; +import ContentMapper from '../../src/mappers/content-summarization-mapper.js'; + +describe('ContentMapper', () => { + let mapper; + let log; + + beforeEach(() => { + log = { + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, + }; + mapper = new ContentMapper(log); + }); + + describe('getOpportunityType', () => { + it('should return summarization', () => { + expect(mapper.getOpportunityType()).to.equal('summarization'); + }); + }); + + describe('requiresPrerender', () => { + it('should return true', () => { + expect(mapper.requiresPrerender()).to.be.true; + }); + }); + + describe('canDeploy', () => { + it('should return eligible for valid content suggestion', () => { + const suggestion = { + getData: () => ({ + summarizationText: 'Some content', + transformRules: { + action: 'insertAfter', + selector: '#text-85a9876220 > h1:nth-of-type(1)', + }, + }), + }; + + const result = mapper.canDeploy(suggestion); + + expect(result).to.deep.equal({ eligible: true }); + }); + + it('should return ineligible when summarizationText is missing', () => { + const suggestion = { + getData: () => ({ + transformRules: { + action: 'insertAfter', + selector: '#selector', + }, + }), + }; + + const result = mapper.canDeploy(suggestion); + + expect(result).to.deep.equal({ + eligible: false, + reason: 'summarizationText is required', + }); + }); + + it('should return ineligible when transformRules are missing', () => { + const suggestion = { + getData: () => ({ + summarizationText: 'Some content', + }), + }; + + const result = mapper.canDeploy(suggestion); + + expect(result).to.deep.equal({ + eligible: false, + reason: 'transformRules is required', + }); + }); + + it('should return ineligible when transformRules action is missing', () => { + const suggestion = { + getData: () => ({ + summarizationText: 'Some content', + transformRules: { + selector: '#selector', + }, + }), + }; + + const result = mapper.canDeploy(suggestion); + + expect(result).to.deep.equal({ + eligible: false, + reason: 'transformRules.action must be insertAfter, insertBefore, or appendChild', + }); + }); + + it('should return ineligible when transformRules selector is missing', () => { + const suggestion = { + getData: () => ({ + summarizationText: 'Some content', + transformRules: { + action: 'insertAfter', + }, + }), + }; + + const result = mapper.canDeploy(suggestion); + + expect(result).to.deep.equal({ + eligible: false, + reason: 'transformRules.selector is required', + }); + }); + + it('should return ineligible when data is null', () => { + const suggestion = { + getData: () => null, + }; + + const result = mapper.canDeploy(suggestion); + + expect(result).to.deep.equal({ + eligible: false, + reason: 'summarizationText is required', + }); + }); + }); + + describe('suggestionToPatch', () => { + it('should create patch with HAST value from markdown', () => { + const suggestion = { + getId: () => 'sugg-content-123', + getUpdatedAt: () => '2025-01-15T10:00:00.000Z', + getData: () => ({ + summarizationText: 'Enter your name exactly as it appears on your **government ID**.', + transformRules: { + action: 'insertAfter', + selector: '#text-85a9876220 > h1:nth-of-type(1)', + }, + }), + }; + + const patch = mapper.suggestionToPatch(suggestion, 'opp-content-123'); + + expect(patch).to.exist; + expect(patch.op).to.equal('insertAfter'); + expect(patch.selector).to.equal('#text-85a9876220 > h1:nth-of-type(1)'); + expect(patch.valueFormat).to.equal('hast'); + expect(patch.opportunityId).to.equal('opp-content-123'); + expect(patch.suggestionId).to.equal('sugg-content-123'); + expect(patch.prerenderRequired).to.be.true; + expect(patch.lastUpdated).to.be.a('number'); + + // Verify HAST structure + expect(patch.value).to.be.an('object'); + expect(patch.value.type).to.equal('root'); + expect(patch.value.children).to.be.an('array'); + expect(patch.value.children.length).to.be.greaterThan(0); + }); + + it('should convert markdown with bold text to HAST', () => { + const suggestion = { + getId: () => 'sugg-bold', + getUpdatedAt: () => '2025-01-15T10:00:00.000Z', + getData: () => ({ + summarizationText: 'This is **bold** text.', + transformRules: { + action: 'insertAfter', + selector: '.content', + }, + }), + }; + + const patch = mapper.suggestionToPatch(suggestion, 'opp-bold'); + + expect(patch).to.exist; + expect(patch.value.type).to.equal('root'); + expect(patch.value.children).to.be.an('array'); + + // Find the paragraph + const paragraph = patch.value.children.find((child) => child.type === 'element' && child.tagName === 'p'); + expect(paragraph).to.exist; + expect(paragraph.children).to.be.an('array'); + + // Should contain text and strong elements + const hasStrong = paragraph.children.some((child) => child.type === 'element' && child.tagName === 'strong'); + expect(hasStrong).to.be.true; + }); + + it('should convert markdown with headings to HAST', () => { + const suggestion = { + getId: () => 'sugg-heading', + getUpdatedAt: () => '2025-01-15T10:00:00.000Z', + getData: () => ({ + summarizationText: '## Key Points\n\nImportant information.', + transformRules: { + action: 'insertAfter', + selector: '#intro', + }, + }), + }; + + const patch = mapper.suggestionToPatch(suggestion, 'opp-heading'); + + expect(patch).to.exist; + expect(patch.value.children).to.be.an('array'); + + // Should have h2 and paragraph + const hasH2 = patch.value.children.some((child) => child.type === 'element' && child.tagName === 'h2'); + const hasP = patch.value.children.some((child) => child.type === 'element' && child.tagName === 'p'); + expect(hasH2).to.be.true; + expect(hasP).to.be.true; + }); + + it('should convert markdown with lists to HAST', () => { + const suggestion = { + getId: () => 'sugg-list', + getUpdatedAt: () => '2025-01-15T10:00:00.000Z', + getData: () => ({ + summarizationText: '- Item 1\n- Item 2\n- Item 3', + transformRules: { + action: 'insertAfter', + selector: '#list-section', + }, + }), + }; + + const patch = mapper.suggestionToPatch(suggestion, 'opp-list'); + + expect(patch).to.exist; + + // Should have ul element + const hasList = patch.value.children.some((child) => child.type === 'element' && child.tagName === 'ul'); + expect(hasList).to.be.true; + }); + + it('should return null when summarizationText is missing', () => { + const suggestion = { + getId: () => 'sugg-invalid', + getUpdatedAt: () => '2025-01-15T10:00:00.000Z', + getData: () => ({ + transformRules: { + action: 'insertAfter', + selector: '#selector', + }, + }), + }; + + const patch = mapper.suggestionToPatch(suggestion, 'opp-invalid'); + + expect(patch).to.be.null; + }); + + it('should return null when transformRules are incomplete', () => { + const suggestion = { + getId: () => 'sugg-invalid-2', + getUpdatedAt: () => '2025-01-15T10:00:00.000Z', + getData: () => ({ + summarizationText: 'Some content', + transformRules: { + selector: '#selector', + }, + }), + }; + + const patch = mapper.suggestionToPatch(suggestion, 'opp-invalid-2'); + + expect(patch).to.be.null; + }); + + it('should handle complex markdown with multiple elements', () => { + const markdownText = `Enter your name exactly as it appears on your **government ID** when booking flights. The same name must be on your **ticket and travel documents**. Rules for names may be different for some countries, such as **Canada, UAE, Australia, and New Zealand**. If your name is spelled wrong, contact support before you travel. There may be a fee to make changes. + +**Key Points** + +- Name on booking must match your government-issued ID or passport. +- Exact spelling is required for both ticket and travel documents. +- Special requirements may apply for Canada, UAE, Australia, and New Zealand.`; + + const suggestion = { + getId: () => 'sugg-complex', + getUpdatedAt: () => '2025-01-15T10:00:00.000Z', + getData: () => ({ + summarizationText: markdownText, + transformRules: { + action: 'insertAfter', + selector: '#content-section', + }, + }), + }; + + const patch = mapper.suggestionToPatch(suggestion, 'opp-complex'); + + expect(patch).to.exist; + expect(patch.value.children).to.be.an('array'); + expect(patch.value.children.length).to.be.greaterThan(2); + + // Should have paragraphs and list + const hasP = patch.value.children.some((child) => child.type === 'element' && child.tagName === 'p'); + const hasList = patch.value.children.some((child) => child.type === 'element' && child.tagName === 'ul'); + expect(hasP).to.be.true; + expect(hasList).to.be.true; + }); + + it('should handle markdown parsing errors gracefully', () => { + let errorMessage = ''; + const errorLog = { + debug: () => {}, + info: () => {}, + warn: () => {}, + error: (msg) => { errorMessage = msg; }, + }; + + const errorMapper = new ContentMapper(errorLog); + + // Stub the markdownToHast method to throw an error + const stub = sinon.stub(errorMapper, 'markdownToHast').throws(new Error('Markdown parsing failed')); + + const suggestion = { + getId: () => 'sugg-error', + getData: () => ({ + summarizationText: 'Some content', + transformRules: { + action: 'insertAfter', + selector: '#selector', + }, + }), + }; + + const patch = errorMapper.suggestionToPatch(suggestion, 'opp-error'); + + expect(patch).to.be.null; + expect(errorMessage).to.include('Failed to convert markdown to HAST'); + expect(errorMessage).to.include('Markdown parsing failed'); + + stub.restore(); + }); + }); +}); diff --git a/packages/spacecat-shared-tokowaka-client/test/mappers/headings-mapper.test.js b/packages/spacecat-shared-tokowaka-client/test/mappers/headings-mapper.test.js index 8795e4a6d..98c45f9bc 100644 --- a/packages/spacecat-shared-tokowaka-client/test/mappers/headings-mapper.test.js +++ b/packages/spacecat-shared-tokowaka-client/test/mappers/headings-mapper.test.js @@ -17,9 +17,16 @@ import HeadingsMapper from '../../src/mappers/headings-mapper.js'; describe('HeadingsMapper', () => { let mapper; + let log; beforeEach(() => { - mapper = new HeadingsMapper(); + log = { + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, + }; + mapper = new HeadingsMapper(log); }); describe('getOpportunityType', () => { @@ -37,7 +44,14 @@ describe('HeadingsMapper', () => { describe('canDeploy', () => { it('should return eligible for heading-empty checkType', () => { const suggestion = { - getData: () => ({ checkType: 'heading-empty' }), + getData: () => ({ + checkType: 'heading-empty', + recommendedAction: 'New Heading', + transformRules: { + action: 'replace', + selector: 'h1', + }, + }), }; const result = mapper.canDeploy(suggestion); @@ -45,17 +59,39 @@ describe('HeadingsMapper', () => { expect(result).to.deep.equal({ eligible: true }); }); - it('should return ineligible for heading-missing checkType', () => { + it('should return eligible for heading-missing-h1 checkType', () => { const suggestion = { - getData: () => ({ checkType: 'heading-missing' }), + getData: () => ({ + checkType: 'heading-missing-h1', + recommendedAction: 'New H1', + transformRules: { + action: 'insertAfter', + selector: '#header', + tag: 'h1', + }, + }), }; const result = mapper.canDeploy(suggestion); - expect(result).to.deep.equal({ - eligible: false, - reason: 'Only empty headings can be deployed. This suggestion has checkType: heading-missing', - }); + expect(result).to.deep.equal({ eligible: true }); + }); + + it('should return eligible for heading-h1-length checkType', () => { + const suggestion = { + getData: () => ({ + checkType: 'heading-h1-length', + recommendedAction: 'Better H1', + transformRules: { + action: 'replace', + selector: 'h1', + }, + }), + }; + + const result = mapper.canDeploy(suggestion); + + expect(result).to.deep.equal({ eligible: true }); }); it('should return ineligible for unknown checkType', () => { @@ -67,7 +103,7 @@ describe('HeadingsMapper', () => { expect(result).to.deep.equal({ eligible: false, - reason: 'Only empty headings can be deployed. This suggestion has checkType: unknown-type', + reason: 'Only heading-empty, heading-missing-h1, heading-h1-length can be deployed. This suggestion has checkType: unknown-type', }); }); @@ -80,7 +116,7 @@ describe('HeadingsMapper', () => { expect(result).to.deep.equal({ eligible: false, - reason: 'Only empty headings can be deployed. This suggestion has checkType: undefined', + reason: 'Only heading-empty, heading-missing-h1, heading-h1-length can be deployed. This suggestion has checkType: undefined', }); }); @@ -93,18 +129,122 @@ describe('HeadingsMapper', () => { expect(result).to.deep.equal({ eligible: false, - reason: 'Only empty headings can be deployed. This suggestion has checkType: undefined', + reason: 'Only heading-empty, heading-missing-h1, heading-h1-length can be deployed. This suggestion has checkType: undefined', + }); + }); + + it('should return ineligible when recommendedAction is missing', () => { + const suggestion = { + getData: () => ({ + checkType: 'heading-empty', + transformRules: { + action: 'replace', + selector: 'h1', + }, + }), + }; + + const result = mapper.canDeploy(suggestion); + + expect(result).to.deep.equal({ + eligible: false, + reason: 'recommendedAction is required', + }); + }); + + it('should return ineligible when transformRules.selector is missing', () => { + const suggestion = { + getData: () => ({ + checkType: 'heading-empty', + recommendedAction: 'New Heading', + transformRules: { + action: 'replace', + }, + }), + }; + + const result = mapper.canDeploy(suggestion); + + expect(result).to.deep.equal({ + eligible: false, + reason: 'transformRules.selector is required', + }); + }); + + it('should return ineligible for heading-missing-h1 with invalid action', () => { + const suggestion = { + getData: () => ({ + checkType: 'heading-missing-h1', + recommendedAction: 'New H1', + transformRules: { + action: 'replace', + selector: '#header', + tag: 'h1', + }, + }), + }; + + const result = mapper.canDeploy(suggestion); + + expect(result).to.deep.equal({ + eligible: false, + reason: 'transformRules.action must be insertBefore or insertAfter for heading-missing-h1', + }); + }); + + it('should return ineligible for heading-missing-h1 without tag', () => { + const suggestion = { + getData: () => ({ + checkType: 'heading-missing-h1', + recommendedAction: 'New H1', + transformRules: { + action: 'insertAfter', + selector: '#header', + }, + }), + }; + + const result = mapper.canDeploy(suggestion); + + expect(result).to.deep.equal({ + eligible: false, + reason: 'transformRules.tag is required for heading-missing-h1', + }); + }); + + it('should return ineligible for heading-h1-length with invalid action', () => { + const suggestion = { + getData: () => ({ + checkType: 'heading-h1-length', + recommendedAction: 'New H1', + transformRules: { + action: 'insertAfter', + selector: 'h1', + }, + }), + }; + + const result = mapper.canDeploy(suggestion); + + expect(result).to.deep.equal({ + eligible: false, + reason: 'transformRules.action must be replace for heading-h1-length', }); }); }); describe('suggestionToPatch', () => { - it('should create patch with headingTag selector', () => { + it('should create patch for heading-empty with transformRules', () => { const suggestion = { getId: () => 'sugg-123', + getUpdatedAt: () => '2025-01-15T10:00:00.000Z', getData: () => ({ - headingTag: 'h1', + checkType: 'heading-empty', recommendedAction: 'New Heading', + transformRules: { + action: 'replace', + selector: 'h1', + }, }), }; @@ -121,12 +261,17 @@ describe('HeadingsMapper', () => { expect(patch.lastUpdated).to.be.a('number'); }); - it('should create patch with path selector when headingTag is missing', () => { + it('should create patch with custom selector', () => { const suggestion = { getId: () => 'sugg-123', + getUpdatedAt: () => '2025-01-15T10:00:00.000Z', getData: () => ({ - path: 'body > h1', + checkType: 'heading-empty', recommendedAction: 'New Heading', + transformRules: { + action: 'replace', + selector: 'body > h1', + }, }), }; @@ -141,49 +286,143 @@ describe('HeadingsMapper', () => { prerenderRequired: true, }); }); - }); - describe('validateSuggestionData', () => { - it('should return true for valid data with headingTag', () => { - const data = { - headingTag: 'h1', - recommendedAction: 'New Heading', + it('should create patch for heading-missing-h1 with transformRules', () => { + const suggestion = { + getId: () => 'sugg-456', + getUpdatedAt: () => '2025-01-15T10:00:00.000Z', + getData: () => ({ + checkType: 'heading-missing-h1', + recommendedAction: 'Exclusive Flight Booking Deals & Partner Discounts.', + transformRules: { + action: 'insertAfter', + selector: '#text-85a9876220 > h2:nth-of-type(1)', + tag: 'h1', + }, + }), }; - expect(mapper.validateSuggestionData(data)).to.be.true; + const patch = mapper.suggestionToPatch(suggestion, 'opp-456'); + + expect(patch).to.deep.include({ + op: 'insertAfter', + selector: '#text-85a9876220 > h2:nth-of-type(1)', + value: 'Exclusive Flight Booking Deals & Partner Discounts.', + tag: 'h1', + opportunityId: 'opp-456', + suggestionId: 'sugg-456', + prerenderRequired: true, + }); + expect(patch.lastUpdated).to.be.a('number'); }); - it('should return true for valid data with path', () => { - const data = { - path: 'body > h1', - recommendedAction: 'New Heading', + it('should create patch for heading-h1-length with transformRules', () => { + const suggestion = { + getId: () => 'sugg-789', + getUpdatedAt: () => '2025-01-15T10:00:00.000Z', + getData: () => ({ + checkType: 'heading-h1-length', + recommendedAction: 'New H1 Heading', + transformRules: { + action: 'replace', + selector: 'body > h1', + }, + }), }; - expect(mapper.validateSuggestionData(data)).to.be.true; + const patch = mapper.suggestionToPatch(suggestion, 'opp-789'); + + expect(patch).to.deep.include({ + op: 'replace', + selector: 'body > h1', + value: 'New H1 Heading', + opportunityId: 'opp-789', + suggestionId: 'sugg-789', + prerenderRequired: true, + }); + expect(patch.lastUpdated).to.be.a('number'); + expect(patch.tag).to.be.undefined; }); - it('should return false if both headingTag and path are missing', () => { - const data = { - recommendedAction: 'New Heading', + it('should return null for heading-missing-h1 without transformRules', () => { + const suggestion = { + getId: () => 'sugg-999', + getData: () => ({ + checkType: 'heading-missing-h1', + recommendedAction: 'New Heading', + }), }; - expect(mapper.validateSuggestionData(data)).to.be.false; + const patch = mapper.suggestionToPatch(suggestion, 'opp-999'); + + expect(patch).to.be.null; }); - it('should return false if recommendedAction is missing', () => { - const data = { - headingTag: 'h1', + it('should return null for heading-h1-length without selector in transformRules', () => { + const suggestion = { + getId: () => 'sugg-888', + getData: () => ({ + checkType: 'heading-h1-length', + recommendedAction: 'New Heading', + transformRules: { + action: 'insertAt', + }, + }), }; - expect(mapper.validateSuggestionData(data)).to.be.false; + const patch = mapper.suggestionToPatch(suggestion, 'opp-888'); + + expect(patch).to.be.null; }); - it('should return false if data is empty', () => { - expect(mapper.validateSuggestionData({})).to.be.false; + it('should log warning for heading-missing-h1 with missing transformRules - validation path', () => { + let warnLogged = false; + const warnLog = { + debug: () => {}, + info: () => {}, + warn: () => { warnLogged = true; }, + error: () => {}, + }; + const warnMapper = new HeadingsMapper(warnLog); + + const suggestion = { + getId: () => 'sugg-warn', + getData: () => ({ + checkType: 'heading-missing-h1', + recommendedAction: 'New Heading', + }), + }; + + const patch = warnMapper.suggestionToPatch(suggestion, 'opp-warn'); + + expect(patch).to.be.null; + expect(warnLogged).to.be.true; }); - it('should return false if data is null', () => { - expect(mapper.validateSuggestionData(null)).to.be.false; + it('should log warning for heading-missing-h1 with invalid transformRules', () => { + let warnMessage = ''; + const warnLog = { + debug: () => {}, + info: () => {}, + warn: (msg) => { warnMessage = msg; }, + error: () => {}, + }; + const warnMapper = new HeadingsMapper(warnLog); + + const suggestion = { + getId: () => 'sugg-defensive', + getUpdatedAt: () => '2025-01-15T10:00:00.000Z', + getData: () => ({ + checkType: 'heading-missing-h1', + recommendedAction: 'New Heading', + // Missing transformRules + }), + }; + + const patch = warnMapper.suggestionToPatch(suggestion, 'opp-defensive'); + + expect(patch).to.be.null; + expect(warnMessage).to.include('cannot be deployed'); }); }); }); From 73864e4c28fb2831357870ca48bf510dfe794f4d Mon Sep 17 00:00:00 2001 From: Divyansh Pratap Date: Wed, 29 Oct 2025 00:43:43 +0530 Subject: [PATCH 18/20] fix: update readme --- .../spacecat-shared-tokowaka-client/README.md | 80 ++----------------- 1 file changed, 7 insertions(+), 73 deletions(-) diff --git a/packages/spacecat-shared-tokowaka-client/README.md b/packages/spacecat-shared-tokowaka-client/README.md index 53aba5156..341ed4fc1 100644 --- a/packages/spacecat-shared-tokowaka-client/README.md +++ b/packages/spacecat-shared-tokowaka-client/README.md @@ -78,82 +78,12 @@ The client invalidates CDN cache after uploading configurations. Failures are lo ## Supported Opportunity Types ### Headings -Optimizes heading elements. Requires `recommendedAction` (new text) and `headingTag` (e.g., "h1", "h2"). -**Deployment Eligibility:** Only suggestions with `checkType: 'heading-empty'` can be deployed currently. Other heading types (e.g., `heading-missing`) are filtered out during deployment. +**Deployment Eligibility:** Only suggestions with `checkType: 'heading-empty'`, `checkType: 'heading-missing-h1'` and `checkType: 'heading-h1-length'` can be deployed currently. -## Extending with Custom Mappers +### Content Summarization -You can add support for new opportunity types by extending `BaseOpportunityMapper`: - -```javascript -import { BaseOpportunityMapper } from '@adobe/spacecat-shared-tokowaka-client'; - -class CustomOpportunityMapper extends BaseOpportunityMapper { - getOpportunityType() { - return 'custom-opportunity'; - } - - requiresPrerender() { - return true; - } - - suggestionToPatch(suggestion, opportunityId) { - const data = suggestion.getData(); - if (!this.validateSuggestionData(data)) { - return null; - } - - return { - ...this.createBasePatch(suggestion.getId(), opportunityId), - op: 'replace', - selector: data.targetElement, - value: data.newValue, - }; - } - - validateSuggestionData(data) { - return !!(data?.targetElement && data?.newValue); - } -} - -// Register the mapper -const client = TokowakaClient.createFrom(context); -client.registerMapper(new CustomOpportunityMapper(context.log)); -``` - -## Configuration Format - -### Tokowaka Config - -```typescript -interface TokowakaConfig { - siteId: string; - baseURL: string; - version: string; - tokowakaForceFail: boolean; - tokowakaOptimizations: { - "prerender": true, - [urlPath: string]: { - prerender: boolean; - patches: TokawakaPatch[]; - } - } -} - -interface TokawakaPatch { - op: 'replace' | 'add' | 'prerender'; - selector?: string; - value?: string; - attribute?: string; - element?: string; - attributes?: Record; - opportunityId: string; - suggestionId: string; - prerenderRequired: boolean; - lastUpdated: number; -} -``` +**Deployment Eligibility:** Currently all suggestions for `summarization` opportunity can be deployed. ## S3 Storage @@ -165,3 +95,7 @@ s3://{TOKOWAKA_SITE_CONFIG_BUCKET}/opportunities/{tokowakaApiKey} **Note:** The configuration is stored as a JSON file containing the complete Tokowaka optimization config for the site. +## Reference Material + +https://wiki.corp.adobe.com/display/AEMSites/Tokowaka+-+Spacecat+Integration +https://wiki.corp.adobe.com/display/AEMSites/Tokowaka+Patch+Format From e1a62fc1cc8196113a9bbeb431675c96c9405624 Mon Sep 17 00:00:00 2001 From: Divyansh Pratap Date: Wed, 29 Oct 2025 14:40:47 +0530 Subject: [PATCH 19/20] fix: address review comments --- package-lock.json | 109 +++++++++++++- .../package.json | 5 +- .../src/cdn/cloudfront-cdn-client.js | 11 +- .../src/index.d.ts | 138 +++++++++++++----- .../test/cdn/cloudfront-cdn-client.test.js | 52 ++++--- 5 files changed, 250 insertions(+), 65 deletions(-) diff --git a/package-lock.json b/package-lock.json index 76ce37935..d017d5dae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5011,6 +5011,13 @@ "node": ">=4" } }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", + "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", + "dev": true, + "license": "(Unlicense OR Apache-2.0)" + }, "node_modules/@slack/logger": { "version": "4.0.0", "license": "MIT", @@ -5978,6 +5985,23 @@ "@types/send": "*" } }, + "node_modules/@types/sinon": { + "version": "17.0.4", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.4.tgz", + "integrity": "sha512-RHnIrhfPO3+tJT0s7cFaXGZvsL4bbR3/k7z3P312qMS4JaS2Tk+KiwiLx1S0rQ56ERj00u1/BtdyVd0FY+Pdew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/sinonjs__fake-timers": "*" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-15.0.1.tgz", + "integrity": "sha512-Ko2tjWJq8oozHzHV+reuvS5KYIRAokHnGbDwGh/J64LntgpbuylF74ipEL24HCyRjf9FOlBiBHWBR1RlVKsI1w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/unist": { "version": "3.0.3", "license": "MIT" @@ -6528,6 +6552,57 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/aws-sdk-client-mock": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/aws-sdk-client-mock/-/aws-sdk-client-mock-4.1.0.tgz", + "integrity": "sha512-h/tOYTkXEsAcV3//6C1/7U4ifSpKyJvb6auveAepqqNJl6TdZaPFEtKjBQNf8UxQdDP850knB2i/whq4zlsxJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/sinon": "^17.0.3", + "sinon": "^18.0.1", + "tslib": "^2.1.0" + } + }, + "node_modules/aws-sdk-client-mock/node_modules/@sinonjs/fake-timers": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/aws-sdk-client-mock/node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/aws-sdk-client-mock/node_modules/sinon": { + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-18.0.1.tgz", + "integrity": "sha512-a2N2TDY1uGviajJ6r4D1CyRAkzE9NNVlYOV1wX5xQDuAk0ONgzgRl0EjCQuRCPxOwp13ghsMwt9Gdldujs39qw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "11.2.2", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.2.0", + "nise": "^6.0.0", + "supports-color": "^7" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, "node_modules/aws-xray-sdk": { "version": "3.11.0", "resolved": "https://registry.npmjs.org/aws-xray-sdk/-/aws-xray-sdk-3.11.0.tgz", @@ -11122,6 +11197,13 @@ "setimmediate": "^1.0.5" } }, + "node_modules/just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", + "dev": true, + "license": "MIT" + }, "node_modules/jwa": { "version": "2.0.1", "license": "MIT", @@ -12559,6 +12641,20 @@ "dev": true, "license": "MIT" }, + "node_modules/nise": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/nise/-/nise-6.1.1.tgz", + "integrity": "sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^13.0.1", + "@sinonjs/text-encoding": "^0.7.3", + "just-extend": "^6.2.0", + "path-to-regexp": "^8.1.0" + } + }, "node_modules/nock": { "version": "14.0.10", "dev": true, @@ -15740,6 +15836,17 @@ "dev": true, "license": "ISC" }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/path-type": { "version": "4.0.0", "dev": true, @@ -63865,7 +63972,6 @@ "version": "3.917.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.917.0.tgz", "integrity": "sha512-pZncQhFbwW04pB0jcD5OFv3x2gAddDYCVxyJVixgyhSw7bKCYxqu6ramfq1NxyVpmm+qsw+ijwi/3cCmhUHF/A==", - "deprecated": "This version contains a compilation TypeScript error https://github.com/aws/aws-sdk-js-v3/issues/7457 - please use @aws-sdk/credential-providers@3.918.0 or higher", "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.916.0", @@ -68893,6 +68999,7 @@ "mdast-util-to-hast": "12.1.0" }, "devDependencies": { + "aws-sdk-client-mock": "4.1.0", "c8": "^10.1.3", "chai": "^6.0.1", "eslint": "^9.36.0", diff --git a/packages/spacecat-shared-tokowaka-client/package.json b/packages/spacecat-shared-tokowaka-client/package.json index 3c00729c5..248e9195f 100644 --- a/packages/spacecat-shared-tokowaka-client/package.json +++ b/packages/spacecat-shared-tokowaka-client/package.json @@ -34,10 +34,11 @@ "@adobe/spacecat-shared-utils": "1.66.1", "@aws-sdk/client-cloudfront": "3.893.0", "@aws-sdk/client-s3": "3.893.0", - "mdast-util-to-hast": "12.1.0", - "mdast-util-from-markdown": "2.0.2" + "mdast-util-from-markdown": "2.0.2", + "mdast-util-to-hast": "12.1.0" }, "devDependencies": { + "aws-sdk-client-mock": "4.1.0", "c8": "^10.1.3", "chai": "^6.0.1", "eslint": "^9.36.0", diff --git a/packages/spacecat-shared-tokowaka-client/src/cdn/cloudfront-cdn-client.js b/packages/spacecat-shared-tokowaka-client/src/cdn/cloudfront-cdn-client.js index e8e17bd10..5ddf48475 100644 --- a/packages/spacecat-shared-tokowaka-client/src/cdn/cloudfront-cdn-client.js +++ b/packages/spacecat-shared-tokowaka-client/src/cdn/cloudfront-cdn-client.js @@ -58,13 +58,11 @@ export default class CloudFrontCdnClient extends BaseCdnClient { * @private */ #initializeClient() { - /* c8 ignore start */ if (!this.client) { this.client = new CloudFrontClient({ region: this.cdnConfig.region, }); } - /* c8 ignore stop */ } /** @@ -105,13 +103,14 @@ export default class CloudFrontCdnClient extends BaseCdnClient { }, }); - try { - this.log.debug(`Initiating CloudFront cache invalidation for ${JSON.stringify(formattedPaths)} paths`); + this.log.debug(`Initiating CloudFront cache invalidation for ${JSON.stringify(formattedPaths)} paths`); + const startTime = Date.now(); + try { const response = await this.client.send(command); const invalidation = response.Invalidation; - this.log.info(`CloudFront cache invalidation initiated: ${invalidation.Id}`); + this.log.info(`CloudFront cache invalidation initiated: ${invalidation.Id} (took ${Date.now() - startTime}ms)`); return { status: 'success', @@ -122,7 +121,7 @@ export default class CloudFrontCdnClient extends BaseCdnClient { paths: formattedPaths.length, }; } catch (error) { - this.log.error(`Failed to invalidate CloudFront cache: ${error.message}`, error); + this.log.error(`Failed to invalidate CloudFront cache after ${Date.now() - startTime}ms: ${error.message}`, error); throw error; } } diff --git a/packages/spacecat-shared-tokowaka-client/src/index.d.ts b/packages/spacecat-shared-tokowaka-client/src/index.d.ts index 20f06d937..9742e9dac 100644 --- a/packages/spacecat-shared-tokowaka-client/src/index.d.ts +++ b/packages/spacecat-shared-tokowaka-client/src/index.d.ts @@ -13,18 +13,25 @@ import { S3Client } from '@aws-sdk/client-s3'; export interface TokawakaPatch { - op: 'replace' | 'add' | 'prerender'; - selector?: string; - value?: string; - attribute?: string; - element?: string; - attributes?: Record; + op: 'replace' | 'insertAfter' | 'insertBefore' | 'appendChild'; + selector: string; + value: string | object; + valueFormat: 'text' | 'hast'; + tag?: string; + currValue?: string; + target: 'ai-bots' | 'bots' | 'all'; opportunityId: string; suggestionId: string; prerenderRequired: boolean; lastUpdated: number; } +export const TARGET_USER_AGENTS_CATEGORIES: { + AI_BOTS: 'ai-bots'; + BOTS: 'bots'; + ALL: 'all'; +}; + export interface TokowakaUrlOptimization { prerender: boolean; patches: TokawakaPatch[]; @@ -48,18 +55,26 @@ export interface CdnInvalidationResult { } export interface DeploymentResult { - tokowakaApiKey: string; - s3Key: string | null; - config: TokowakaConfig | null; + s3Path: string; cdnInvalidation: CdnInvalidationResult | null; succeededSuggestions: Array; failedSuggestions: Array<{ suggestion: any; reason: string }>; } +export interface SiteConfig { + getTokowakaConfig(): { + apiKey: string; + cdnProvider?: string; + }; + getFetchConfig?(): { + overrideBaseURL?: string; + }; +} + export interface Site { getId(): string; getBaseURL(): string; - getConfig(): Record; + getConfig(): SiteConfig; } export interface Opportunity { @@ -70,6 +85,7 @@ export interface Opportunity { export interface Suggestion { getId(): string; getData(): Record; + getUpdatedAt(): string; } /** @@ -77,6 +93,8 @@ export interface Suggestion { * Extend this class to create custom mappers for new opportunity types */ export abstract class BaseOpportunityMapper { + protected log: any; + constructor(log: any); /** @@ -98,25 +116,74 @@ export abstract class BaseOpportunityMapper { ): TokawakaPatch | null; /** - * Validates suggestion data before conversion + * Checks if a suggestion can be deployed for this opportunity type + * This method should validate all eligibility and data requirements */ - validateSuggestionData(data: Record): boolean; + abstract canDeploy(suggestion: Suggestion): { + eligible: boolean; + reason?: string; + }; /** * Helper method to create base patch structure */ protected createBasePatch( - suggestionId: string, + suggestion: Suggestion, opportunityId: string ): Partial; } +/** + * Headings opportunity mapper + * Handles conversion of heading suggestions (heading-empty, heading-missing-h1, heading-h1-length) to Tokowaka patches + */ +export class HeadingsMapper extends BaseOpportunityMapper { + constructor(log: any); + + getOpportunityType(): string; + requiresPrerender(): boolean; + suggestionToPatch(suggestion: Suggestion, opportunityId: string): TokawakaPatch | null; + canDeploy(suggestion: Suggestion): { eligible: boolean; reason?: string }; +} + +/** + * Content summarization opportunity mapper + * Handles conversion of content summarization suggestions to Tokowaka patches with HAST format + */ +export class ContentSummarizationMapper extends BaseOpportunityMapper { + constructor(log: any); + + getOpportunityType(): string; + requiresPrerender(): boolean; + suggestionToPatch(suggestion: Suggestion, opportunityId: string): TokawakaPatch | null; + canDeploy(suggestion: Suggestion): { eligible: boolean; reason?: string }; + + /** + * Converts markdown text to HAST (Hypertext Abstract Syntax Tree) format + */ + markdownToHast(markdown: string): object; +} + +/** + * Registry for opportunity mappers + */ +export class MapperRegistry { + constructor(log: any); + + registerMapper(MapperClass: typeof BaseOpportunityMapper): void; + getMapper(opportunityType: string): BaseOpportunityMapper | null; + getSupportedOpportunityTypes(): string[]; +} + /** * Base class for CDN clients * Extend this class to create custom CDN clients for different providers */ export abstract class BaseCdnClient { - constructor(config: Record, log: any); + protected env: any; + protected log: any; + + constructor(env: any, log: any); /** * Returns the CDN provider name @@ -126,34 +193,25 @@ export abstract class BaseCdnClient { /** * Validates the CDN configuration */ - validateConfig(): boolean; + abstract validateConfig(): boolean; /** * Invalidates the CDN cache for the given paths */ abstract invalidateCache(paths: string[]): Promise; - - /** - * Checks the status of an invalidation request - */ - getInvalidationStatus(requestId: string): Promise; } /** - * Akamai CDN client implementation + * CloudFront CDN client implementation */ -export class AkamaiCdnClient extends BaseCdnClient { - constructor(config: { - clientToken: string; - clientSecret: string; - accessToken: string; - baseUrl?: string; +export class CloudFrontCdnClient extends BaseCdnClient { + constructor(env: { + TOKOWAKA_CDN_CONFIG: string; // JSON string with cloudfront config }, log: any); getProviderName(): string; validateConfig(): boolean; invalidateCache(paths: string[]): Promise; - getInvalidationStatus(purgeId: string): Promise; } /** @@ -168,13 +226,24 @@ export class CdnClientRegistry { isProviderSupported(provider: string): boolean; } +/** + * Main Tokowaka Client for managing edge optimization configurations + */ export default class TokowakaClient { - constructor(config: { bucketName: string; s3Client: S3Client }, log: any); + constructor(config: { + bucketName: string; + s3Client: S3Client; + env?: Record; + }, log: any); static createFrom(context: { - env: { TOKOWAKA_CONFIG_BUCKET: string }; + env: { + TOKOWAKA_SITE_CONFIG_BUCKET: string; + TOKOWAKA_CDN_PROVIDER?: string; + TOKOWAKA_CDN_CONFIG?: string; + }; log?: any; - s3Client: S3Client; + s3: { s3Client: S3Client }; tokowakaClient?: TokowakaClient; }): TokowakaClient; @@ -194,9 +263,12 @@ export default class TokowakaClient { /** * Merges existing configuration with new configuration */ - mergeConfigs(existingConfig: TokowakaConfig | null, newConfig: TokowakaConfig): TokowakaConfig; + mergeConfigs(existingConfig: TokowakaConfig, newConfig: TokowakaConfig): TokowakaConfig; - invalidateCdnCache(site: Site, s3Key: string): Promise; + /** + * Invalidates CDN cache + */ + invalidateCdnCache(apiKey: string, cdnProvider?: string): Promise; deploySuggestions( site: Site, diff --git a/packages/spacecat-shared-tokowaka-client/test/cdn/cloudfront-cdn-client.test.js b/packages/spacecat-shared-tokowaka-client/test/cdn/cloudfront-cdn-client.test.js index 08a3386f0..c57e9c69a 100644 --- a/packages/spacecat-shared-tokowaka-client/test/cdn/cloudfront-cdn-client.test.js +++ b/packages/spacecat-shared-tokowaka-client/test/cdn/cloudfront-cdn-client.test.js @@ -15,6 +15,8 @@ import { expect, use } from 'chai'; import sinon from 'sinon'; import sinonChai from 'sinon-chai'; +import { mockClient } from 'aws-sdk-client-mock'; +import { CloudFrontClient, CreateInvalidationCommand } from '@aws-sdk/client-cloudfront'; import CloudFrontCdnClient from '../../src/cdn/cloudfront-cdn-client.js'; use(sinonChai); @@ -22,7 +24,7 @@ use(sinonChai); describe('CloudFrontCdnClient', () => { let client; let log; - let mockCloudFrontClient; + let cloudFrontMock; beforeEach(() => { log = { @@ -32,12 +34,13 @@ describe('CloudFrontCdnClient', () => { debug: sinon.stub(), }; - mockCloudFrontClient = { - send: sinon.stub(), - }; + // Mock the CloudFront SDK client + cloudFrontMock = mockClient(CloudFrontClient); }); afterEach(() => { + // Reset all mocks + cloudFrontMock.reset(); sinon.restore(); }); @@ -142,8 +145,6 @@ describe('CloudFrontCdnClient', () => { }), }; client = new CloudFrontCdnClient(env, log); - // Mock the internal client - client.client = mockCloudFrontClient; }); it('should invalidate cache successfully', async () => { @@ -154,7 +155,7 @@ describe('CloudFrontCdnClient', () => { CreateTime: new Date('2025-01-15T10:30:00.000Z'), }, }; - mockCloudFrontClient.send.resolves(mockResponse); + cloudFrontMock.on(CreateInvalidationCommand).resolves(mockResponse); const paths = ['/path1', '/path2']; const result = await client.invalidateCache(paths); @@ -168,9 +169,9 @@ describe('CloudFrontCdnClient', () => { paths: 2, }); - expect(mockCloudFrontClient.send).to.have.been.calledOnce; expect(log.debug).to.have.been.calledWith(sinon.match(/Initiating CloudFront cache invalidation/)); expect(log.info).to.have.been.calledWith(sinon.match(/CloudFront cache invalidation initiated/)); + expect(log.info).to.have.been.calledWith(sinon.match(/took \d+ms/)); }); it('should format paths to start with /', async () => { @@ -181,13 +182,14 @@ describe('CloudFrontCdnClient', () => { CreateTime: new Date(), }, }; - mockCloudFrontClient.send.resolves(mockResponse); + cloudFrontMock.on(CreateInvalidationCommand).resolves(mockResponse); const paths = ['path1', '/path2', 'path3']; await client.invalidateCache(paths); - const command = mockCloudFrontClient.send.firstCall.args[0]; - expect(command.input.InvalidationBatch.Paths.Items).to.deep.equal([ + const calls = cloudFrontMock.commandCalls(CreateInvalidationCommand); + expect(calls).to.have.length(1); + expect(calls[0].args[0].input.InvalidationBatch.Paths.Items).to.deep.equal([ '/path1', '/path2', '/path3', @@ -213,7 +215,10 @@ describe('CloudFrontCdnClient', () => { message: 'No paths to invalidate', }); expect(log.warn).to.have.been.calledWith('No paths provided for cache invalidation'); - expect(mockCloudFrontClient.send).to.not.have.been.called; + + // Verify no CloudFront commands were sent + const calls = cloudFrontMock.commandCalls(CreateInvalidationCommand); + expect(calls).to.have.length(0); }); it('should return skipped result if paths is not an array', async () => { @@ -227,14 +232,14 @@ describe('CloudFrontCdnClient', () => { }); it('should throw error on CloudFront API failure', async () => { - mockCloudFrontClient.send.rejects(new Error('CloudFront API error')); + cloudFrontMock.on(CreateInvalidationCommand).rejects(new Error('CloudFront API error')); try { await client.invalidateCache(['/path1']); expect.fail('Should have thrown error'); } catch (error) { expect(error.message).to.include('CloudFront API error'); - expect(log.error).to.have.been.calledWith(sinon.match(/Failed to invalidate CloudFront cache/)); + expect(log.error).to.have.been.calledWith(sinon.match(/Failed to invalidate CloudFront cache after \d+ms/)); } }); }); @@ -257,9 +262,8 @@ describe('CloudFrontCdnClient', () => { // Client should be null initially expect(client.client).to.be.null; - // Mock CloudFront client for this test - client.client = mockCloudFrontClient; - mockCloudFrontClient.send.resolves({ + // Mock successful invalidation + cloudFrontMock.on(CreateInvalidationCommand).resolves({ Invalidation: { Id: 'I123', Status: 'InProgress', @@ -269,8 +273,9 @@ describe('CloudFrontCdnClient', () => { await client.invalidateCache(['/test']); - // Verify it was called - expect(mockCloudFrontClient.send).to.have.been.called; + // Verify the command was called + const calls = cloudFrontMock.commandCalls(CreateInvalidationCommand); + expect(calls).to.have.length(1); }); it('should initialize client without credentials (Lambda role)', async () => { @@ -287,9 +292,8 @@ describe('CloudFrontCdnClient', () => { // Client should be null initially expect(client.client).to.be.null; - // Mock for test - client.client = mockCloudFrontClient; - mockCloudFrontClient.send.resolves({ + // Mock successful invalidation + cloudFrontMock.on(CreateInvalidationCommand).resolves({ Invalidation: { Id: 'I123', Status: 'InProgress', @@ -299,7 +303,9 @@ describe('CloudFrontCdnClient', () => { await client.invalidateCache(['/test']); - expect(mockCloudFrontClient.send).to.have.been.called; + // Verify the command was called + const calls = cloudFrontMock.commandCalls(CreateInvalidationCommand); + expect(calls).to.have.length(1); }); it('should lazy-initialize CloudFront client on first use', () => { From 341362df64d11c4a5f76b72ac0964a8d3c91d292 Mon Sep 17 00:00:00 2001 From: Divyansh Pratap Date: Wed, 29 Oct 2025 15:28:48 +0530 Subject: [PATCH 20/20] fix: address review comments --- packages/spacecat-shared-tokowaka-client/src/index.js | 4 ++-- packages/spacecat-shared-tokowaka-client/test/index.test.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/spacecat-shared-tokowaka-client/src/index.js b/packages/spacecat-shared-tokowaka-client/src/index.js index 6240b14d8..4f727b2d0 100644 --- a/packages/spacecat-shared-tokowaka-client/src/index.js +++ b/packages/spacecat-shared-tokowaka-client/src/index.js @@ -340,7 +340,7 @@ class TokowakaClient { this.log.info(`CDN cache invalidation completed: ${JSON.stringify(result)}`); return result; } catch (error) { - this.log.error(`Failed to invalidate CDN cache: ${error.message}`, error); + this.log.error(`Failed to invalidate Tokowaka CDN cache: ${error.message}`, error); return { status: 'error', provider: 'cloudfront', @@ -412,7 +412,7 @@ class TokowakaClient { const newConfig = this.generateConfig(site, opportunity, eligibleSuggestions); if (Object.keys(newConfig.tokowakaOptimizations).length === 0) { - this.log.warn(''); + this.log.warn('No eligible suggestions to deploy'); return { succeededSuggestions: [], failedSuggestions: suggestions, diff --git a/packages/spacecat-shared-tokowaka-client/test/index.test.js b/packages/spacecat-shared-tokowaka-client/test/index.test.js index 95b5bde61..172ba7a56 100644 --- a/packages/spacecat-shared-tokowaka-client/test/index.test.js +++ b/packages/spacecat-shared-tokowaka-client/test/index.test.js @@ -1122,7 +1122,7 @@ describe('TokowakaClient', () => { provider: 'cloudfront', message: 'No CDN client available for provider: cloudfront', }); - expect(log.error).to.have.been.calledWith(sinon.match(/Failed to invalidate CDN cache/)); + expect(log.error).to.have.been.calledWith(sinon.match(/Failed to invalidate Tokowaka CDN cache/)); }); it('should return error object if CDN invalidation fails', async () => { @@ -1136,7 +1136,7 @@ describe('TokowakaClient', () => { message: 'CDN API error', }); - expect(log.error).to.have.been.calledWith(sinon.match(/Failed to invalidate CDN cache/)); + expect(log.error).to.have.been.calledWith(sinon.match(/Failed to invalidate Tokowaka CDN cache/)); }); }); });