From c324d153d3a1c1f635ee7c35c9d3832dcc918117 Mon Sep 17 00:00:00 2001 From: Divyansh Pratap Date: Tue, 27 Aug 2024 20:51:45 +0530 Subject: [PATCH] feat: seo tags audit --- package-lock.json | 998 ++++++++++++++++++++++++++------- package.json | 1 + src/index.js | 4 + src/metatags/constants.js | 35 ++ src/metatags/handler.js | 121 ++++ src/metatags/seo-checks.js | 221 ++++++++ src/support/s3-client.js | 27 + src/utils/s3-utils.js | 43 ++ test/audits/metatags.test.js | 474 ++++++++++++++++ test/support/s3-client.test.js | 75 +++ test/utils/s3-utils.test.js | 112 ++++ 11 files changed, 1918 insertions(+), 193 deletions(-) create mode 100644 src/metatags/constants.js create mode 100644 src/metatags/handler.js create mode 100644 src/metatags/seo-checks.js create mode 100644 src/support/s3-client.js create mode 100644 src/utils/s3-utils.js create mode 100644 test/audits/metatags.test.js create mode 100644 test/support/s3-client.test.js create mode 100644 test/utils/s3-utils.test.js diff --git a/package-lock.json b/package-lock.json index 4ba0ec59..fad93c8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "@adobe/spacecat-shared-rum-api-client-v1": "npm:@adobe/spacecat-shared-rum-api-client@1.8.4", "@adobe/spacecat-shared-utils": "1.19.6", "@aws-sdk/client-lambda": "3.637.0", + "@aws-sdk/client-s3": "3.627.0", "@aws-sdk/client-secrets-manager": "3.637.0", "@aws-sdk/client-sqs": "3.637.0", "@aws-sdk/credential-provider-node": "3.637.0", @@ -3889,6 +3890,32 @@ "@json2csv/plainjs": "7.0.6" } }, + "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "dependencies": { + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@aws-crypto/sha256-browser": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", @@ -4130,6 +4157,216 @@ "node": ">=16.0.0" } }, + "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@aws-sdk/client-s3": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.620.0.tgz", + "integrity": "sha512-kf3Lqvuq/ciUn4myQjd1a9nhVg95+FEWkIq7pdkgxFoKow8HKj3nuiwI7zYBRTfk0RKXRkJca3GE+3RXpeZSiA==", + "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.620.0", + "@aws-sdk/client-sts": "3.620.0", + "@aws-sdk/core": "3.620.0", + "@aws-sdk/credential-provider-node": "3.620.0", + "@aws-sdk/middleware-bucket-endpoint": "3.620.0", + "@aws-sdk/middleware-expect-continue": "3.620.0", + "@aws-sdk/middleware-flexible-checksums": "3.620.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-location-constraint": "3.609.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-sdk-s3": "3.620.0", + "@aws-sdk/middleware-signing": "3.620.0", + "@aws-sdk/middleware-ssec": "3.609.0", + "@aws-sdk/middleware-user-agent": "3.620.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/signature-v4-multi-region": "3.620.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@aws-sdk/xml-builder": "3.609.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.0", + "@smithy/eventstream-serde-browser": "^3.0.5", + "@smithy/eventstream-serde-config-resolver": "^3.0.3", + "@smithy/eventstream-serde-node": "^3.0.4", + "@smithy/fetch-http-handler": "^3.2.3", + "@smithy/hash-blob-browser": "^3.1.2", + "@smithy/hash-node": "^3.0.3", + "@smithy/hash-stream-node": "^3.1.2", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/md5-js": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.12", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.10", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.12", + "@smithy/util-defaults-mode-node": "^3.0.12", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-stream": "^3.1.2", + "@smithy/util-utf8": "^3.0.0", + "@smithy/util-waiter": "^3.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/client-sso": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.620.0.tgz", + "integrity": "sha512-J1CvF7u39XwtCK9rPlkW2AA631EPqkb4PjOOj9aZ9LjQmkJ0DkL+9tEqU2XIWcjDd2Z3hS3LBuS8uN7upIkEnQ==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.620.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.620.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.0", + "@smithy/fetch-http-handler": "^3.2.3", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.12", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.10", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.12", + "@smithy/util-defaults-mode-node": "^3.0.12", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.620.0.tgz", + "integrity": "sha512-BI2BdrSKDmB/2ouB/NJR0PT0x/+5fmoF6XOE78hFBb4F5w/yynGgcJY936dF+oREfpME6ehjB2b0okGg78Scpw==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/fetch-http-handler": "^3.2.3", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.10", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.620.0.tgz", + "integrity": "sha512-or8ahy4ysURcWgKX00367DMDTTyMynDEl+FQh4wce66fMyePhFVuoPcRgXzWsi8KYmL95sPCfJFNqBMyFNcgvQ==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.609.0", + "@aws-sdk/credential-provider-http": "3.620.0", + "@aws-sdk/credential-provider-ini": "3.620.0", + "@aws-sdk/credential-provider-process": "3.614.0", + "@aws-sdk/credential-provider-sso": "3.620.0", + "@aws-sdk/credential-provider-web-identity": "3.609.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.620.0.tgz", + "integrity": "sha512-P9fYi6dzZIl8ITC7qAPf5DX9omI3LfA91g3KH+0OUmS3ctP7tN+gNo3HmqlzoqnwPe0pXn1FumYAe1qFl6Yjjg==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.609.0", + "@aws-sdk/credential-provider-http": "3.620.0", + "@aws-sdk/credential-provider-process": "3.614.0", + "@aws-sdk/credential-provider-sso": "3.620.0", + "@aws-sdk/credential-provider-web-identity": "3.609.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.620.0" + } + }, + "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.620.0.tgz", + "integrity": "sha512-xtIj2hmq3jcKwvGmqhoYapbWeQfFyoQgKBtwD6nx0M6oS5lbFH4rzHhj0gBwatZDjMa35cWtcYVUJCv2/9mWvA==", + "dependencies": { + "@aws-sdk/client-sso": "3.620.0", + "@aws-sdk/token-providers": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@aws-sdk/client-sqs": { "version": "3.620.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sqs/-/client-sqs-3.620.0.tgz", @@ -4882,16 +5119,33 @@ "@aws-sdk/client-dynamodb": "^3.624.0" } }, - "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@aws-sdk/middleware-sdk-sqs": { + "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.609.0.tgz", + "integrity": "sha512-xzsdoTkszGVqGVPjUmgoP7TORiByLueMHieI1fhQL888WPdqctwAx3ES6d/bA9Q/i8jnc6hs+Fjhy8UvBTkE9A==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@aws-sdk/middleware-sdk-s3": { "version": "3.620.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sqs/-/middleware-sdk-sqs-3.620.0.tgz", - "integrity": "sha512-Zv3sdnD03vWjX4dDjbcwW0IOweraKQagcPBugXCIPssOHantuMvdeKOneIiNehluRclzdlfQMdvTdUdt1YL9Mg==", - "license": "Apache-2.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.620.0.tgz", + "integrity": "sha512-AAZ6NLVOx/bP97PYj/afCMeySzxOHocgJG3ZXh6f8MnJcGpZgx8NyRm0vtiYUTFrS2JtU4xV05Dl3j4afV3s4A==", "dependencies": { "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-arn-parser": "3.568.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", "@smithy/smithy-client": "^3.1.10", "@smithy/types": "^3.3.0", - "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-stream": "^3.1.2", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, @@ -4899,11 +5153,10 @@ "node": ">=16.0.0" } }, - "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@aws-sdk/middleware-sdk-sqs/node_modules/@smithy/util-utf8": { + "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/util-utf8": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", - "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" @@ -4912,8 +5165,67 @@ "node": ">=16.0.0" } }, - "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@aws-sdk/types": { - "version": "3.609.0", + "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@aws-sdk/middleware-sdk-sqs": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sqs/-/middleware-sdk-sqs-3.620.0.tgz", + "integrity": "sha512-Zv3sdnD03vWjX4dDjbcwW0IOweraKQagcPBugXCIPssOHantuMvdeKOneIiNehluRclzdlfQMdvTdUdt1YL9Mg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/smithy-client": "^3.1.10", + "@smithy/types": "^3.3.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@aws-sdk/middleware-sdk-sqs/node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@aws-sdk/middleware-ssec": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.609.0.tgz", + "integrity": "sha512-GZSD1s7+JswWOTamVap79QiDaIV7byJFssBW68GYjyRS5EBjNfwA/8s+6uE6g39R3ojyTbYOmvcANoZEhSULXg==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.620.0.tgz", + "integrity": "sha512-yu1pTCqIbkSdaOvmyfW9vV9jWe3pDApkQPZLg4VEN5dXDWRtgQ/amv88myyCEoG14irUN1tsbvytcKzGyEXnhA==", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.620.0", + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@aws-sdk/types": { + "version": "3.609.0", "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", "license": "Apache-2.0", @@ -4940,6 +5252,18 @@ "@aws-sdk/client-dynamodb": "^3.624.0" } }, + "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@aws-sdk/xml-builder": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.609.0.tgz", + "integrity": "sha512-l9XxNcA4HX98rwCC2/KoiWcmEiRfZe4G+mYwDbCFT87JIMj6GBhLDkAzr/W8KAaA2IDr8Vc6J8fZPgVulxxfMA==", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@smithy/abort-controller": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz", @@ -4953,6 +5277,23 @@ "node": ">=16.0.0" } }, + "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@smithy/chunked-blob-reader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-3.0.0.tgz", + "integrity": "sha512-sbnURCwjF0gSToGlsBiAmd1lRCmSn72nu9axfJu5lIx6RUEgHu6GwTMbqCdhQSi0Pumcm5vFxsi9XWXb2mTaoA==", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@smithy/chunked-blob-reader-native": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-3.0.0.tgz", + "integrity": "sha512-VDkpCYW+peSuM4zJip5WDfqvg2Mo/e8yxOv3VF1m11y7B8KKMKVFtmZWDe36Fvk8rGuWrPZHHXZ7rR7uM5yWyg==", + "dependencies": { + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@smithy/config-resolver": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.5.tgz", @@ -4985,6 +5326,55 @@ "node": ">=16.0.0" } }, + "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@smithy/eventstream-codec": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.2.tgz", + "integrity": "sha512-0mBcu49JWt4MXhrhRAlxASNy0IjDRFU+aWNDRal9OtUJvJNiwDuyKMUONSOjLjSCeGwZaE0wOErdqULer8r7yw==", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-hex-encoding": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.3.tgz", + "integrity": "sha512-NVTYjOuYpGfrN/VbRQgn31x73KDLfCXCsFdad8DiIc3IcdxL+dYA9zEQPyOP7Fy2QL8CPy2WE4WCUD+ZsLNfaQ==", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@smithy/eventstream-serde-node": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.5.tgz", + "integrity": "sha512-+upXvnHNyZP095s11jF5dhGw/Ihzqwl5G+/KtMnoQOpdfC3B5HYCcDVG9EmgkhJMXJlM64PyN5gjJl0uXFQehQ==", + "dependencies": { + "@smithy/eventstream-serde-universal": "^3.0.5", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@smithy/eventstream-serde-universal": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.5.tgz", + "integrity": "sha512-5u/nXbyoh1s4QxrvNre9V6vfyoLWuiVvvd5TlZjGThIikc3G+uNiG9uOTCWweSRjv1asdDIWK7nOmN7le4RYHQ==", + "dependencies": { + "@smithy/eventstream-codec": "^3.1.2", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@smithy/fetch-http-handler": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.4.tgz", @@ -4998,6 +5388,17 @@ "tslib": "^2.6.2" } }, + "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@smithy/hash-blob-browser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-3.1.2.tgz", + "integrity": "sha512-hAbfqN2UbISltakCC2TP0kx4LqXBttEv2MqSPE98gVuDFMf05lU+TpC41QtqGP3Ff5A3GwZMPfKnEy0VmEUpmg==", + "dependencies": { + "@smithy/chunked-blob-reader": "^3.0.0", + "@smithy/chunked-blob-reader-native": "^3.0.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@smithy/hash-node": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.3.tgz", @@ -5026,6 +5427,31 @@ "node": ">=16.0.0" } }, + "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@smithy/hash-stream-node": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-3.1.2.tgz", + "integrity": "sha512-PBgDMeEdDzi6JxKwbfBtwQG9eT9cVwsf0dZzLXoJF4sHKHs5HEo/3lJWpn6jibfJwT34I1EBXpBnZE8AxAft6g==", + "dependencies": { + "@smithy/types": "^3.3.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@smithy/hash-stream-node/node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@adobe/spacecat-shared-http-utils/node_modules/@smithy/invalid-dependency": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.3.tgz", @@ -9550,18 +9976,17 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.620.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.620.0.tgz", - "integrity": "sha512-kf3Lqvuq/ciUn4myQjd1a9nhVg95+FEWkIq7pdkgxFoKow8HKj3nuiwI7zYBRTfk0RKXRkJca3GE+3RXpeZSiA==", - "license": "Apache-2.0", + "version": "3.627.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.627.0.tgz", + "integrity": "sha512-XTbtRLPVfq2lHo0SUP6HJb6HgBsKsJR54bhhVTwj5SZ4G26KOmx2iFOz9SgHie5apU7vWIhijb48LIhbLArgGg==", "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.620.0", - "@aws-sdk/client-sts": "3.620.0", - "@aws-sdk/core": "3.620.0", - "@aws-sdk/credential-provider-node": "3.620.0", + "@aws-sdk/client-sso-oidc": "3.624.0", + "@aws-sdk/client-sts": "3.624.0", + "@aws-sdk/core": "3.624.0", + "@aws-sdk/credential-provider-node": "3.624.0", "@aws-sdk/middleware-bucket-endpoint": "3.620.0", "@aws-sdk/middleware-expect-continue": "3.620.0", "@aws-sdk/middleware-flexible-checksums": "3.620.0", @@ -9569,23 +9994,22 @@ "@aws-sdk/middleware-location-constraint": "3.609.0", "@aws-sdk/middleware-logger": "3.609.0", "@aws-sdk/middleware-recursion-detection": "3.620.0", - "@aws-sdk/middleware-sdk-s3": "3.620.0", - "@aws-sdk/middleware-signing": "3.620.0", + "@aws-sdk/middleware-sdk-s3": "3.626.0", "@aws-sdk/middleware-ssec": "3.609.0", "@aws-sdk/middleware-user-agent": "3.620.0", "@aws-sdk/region-config-resolver": "3.614.0", - "@aws-sdk/signature-v4-multi-region": "3.620.0", + "@aws-sdk/signature-v4-multi-region": "3.626.0", "@aws-sdk/types": "3.609.0", "@aws-sdk/util-endpoints": "3.614.0", "@aws-sdk/util-user-agent-browser": "3.609.0", "@aws-sdk/util-user-agent-node": "3.614.0", "@aws-sdk/xml-builder": "3.609.0", "@smithy/config-resolver": "^3.0.5", - "@smithy/core": "^2.3.0", + "@smithy/core": "^2.3.2", "@smithy/eventstream-serde-browser": "^3.0.5", "@smithy/eventstream-serde-config-resolver": "^3.0.3", "@smithy/eventstream-serde-node": "^3.0.4", - "@smithy/fetch-http-handler": "^3.2.3", + "@smithy/fetch-http-handler": "^3.2.4", "@smithy/hash-blob-browser": "^3.1.2", "@smithy/hash-node": "^3.0.3", "@smithy/hash-stream-node": "^3.1.2", @@ -9593,23 +10017,24 @@ "@smithy/md5-js": "^3.0.3", "@smithy/middleware-content-length": "^3.0.5", "@smithy/middleware-endpoint": "^3.1.0", - "@smithy/middleware-retry": "^3.0.12", + "@smithy/middleware-retry": "^3.0.14", "@smithy/middleware-serde": "^3.0.3", "@smithy/middleware-stack": "^3.0.3", "@smithy/node-config-provider": "^3.1.4", "@smithy/node-http-handler": "^3.1.4", "@smithy/protocol-http": "^4.1.0", - "@smithy/smithy-client": "^3.1.10", + "@smithy/smithy-client": "^3.1.12", "@smithy/types": "^3.3.0", "@smithy/url-parser": "^3.0.3", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.12", - "@smithy/util-defaults-mode-node": "^3.0.12", + "@smithy/util-defaults-mode-browser": "^3.0.14", + "@smithy/util-defaults-mode-node": "^3.0.14", "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", "@smithy/util-retry": "^3.0.3", - "@smithy/util-stream": "^3.1.2", + "@smithy/util-stream": "^3.1.3", "@smithy/util-utf8": "^3.0.0", "@smithy/util-waiter": "^3.1.2", "tslib": "^2.6.2" @@ -9622,7 +10047,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", - "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", @@ -9637,7 +10061,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" @@ -9650,7 +10073,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", - "license": "Apache-2.0", "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", @@ -9694,14 +10116,13 @@ } }, "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/client-sso": { - "version": "3.620.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.620.0.tgz", - "integrity": "sha512-J1CvF7u39XwtCK9rPlkW2AA631EPqkb4PjOOj9aZ9LjQmkJ0DkL+9tEqU2XIWcjDd2Z3hS3LBuS8uN7upIkEnQ==", - "license": "Apache-2.0", + "version": "3.624.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.624.0.tgz", + "integrity": "sha512-EX6EF+rJzMPC5dcdsu40xSi2To7GSvdGQNIpe97pD9WvZwM9tRNQnNM4T6HA4gjV1L6Jwk8rBlG/CnveXtLEMw==", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.620.0", + "@aws-sdk/core": "3.624.0", "@aws-sdk/middleware-host-header": "3.620.0", "@aws-sdk/middleware-logger": "3.609.0", "@aws-sdk/middleware-recursion-detection": "3.620.0", @@ -9712,26 +10133,26 @@ "@aws-sdk/util-user-agent-browser": "3.609.0", "@aws-sdk/util-user-agent-node": "3.614.0", "@smithy/config-resolver": "^3.0.5", - "@smithy/core": "^2.3.0", - "@smithy/fetch-http-handler": "^3.2.3", + "@smithy/core": "^2.3.2", + "@smithy/fetch-http-handler": "^3.2.4", "@smithy/hash-node": "^3.0.3", "@smithy/invalid-dependency": "^3.0.3", "@smithy/middleware-content-length": "^3.0.5", "@smithy/middleware-endpoint": "^3.1.0", - "@smithy/middleware-retry": "^3.0.12", + "@smithy/middleware-retry": "^3.0.14", "@smithy/middleware-serde": "^3.0.3", "@smithy/middleware-stack": "^3.0.3", "@smithy/node-config-provider": "^3.1.4", "@smithy/node-http-handler": "^3.1.4", "@smithy/protocol-http": "^4.1.0", - "@smithy/smithy-client": "^3.1.10", + "@smithy/smithy-client": "^3.1.12", "@smithy/types": "^3.3.0", "@smithy/url-parser": "^3.0.3", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.12", - "@smithy/util-defaults-mode-node": "^3.0.12", + "@smithy/util-defaults-mode-browser": "^3.0.14", + "@smithy/util-defaults-mode-node": "^3.0.14", "@smithy/util-endpoints": "^2.0.5", "@smithy/util-middleware": "^3.0.3", "@smithy/util-retry": "^3.0.3", @@ -9742,85 +10163,276 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.624.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.624.0.tgz", + "integrity": "sha512-Ki2uKYJKKtfHxxZsiMTOvJoVRP6b2pZ1u3rcUb2m/nVgBPUfLdl8ZkGpqE29I+t5/QaS/sEdbn6cgMUZwl+3Dg==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.624.0", + "@aws-sdk/credential-provider-node": "3.624.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.620.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.2", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.14", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.14", + "@smithy/util-defaults-mode-node": "^3.0.14", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.624.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/client-sts": { + "version": "3.624.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.624.0.tgz", + "integrity": "sha512-k36fLZCb2nfoV/DKK3jbRgO/Yf7/R80pgYfMiotkGjnZwDmRvNN08z4l06L9C+CieazzkgRxNUzyppsYcYsQaw==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.624.0", + "@aws-sdk/core": "3.624.0", + "@aws-sdk/credential-provider-node": "3.624.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.620.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.2", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.14", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.14", + "@smithy/util-defaults-mode-node": "^3.0.14", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/core": { + "version": "3.624.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.624.0.tgz", + "integrity": "sha512-WyFmPbhRIvtWi7hBp8uSFy+iPpj8ccNV/eX86hwF4irMjfc/FtsGVIAeBXxXM/vGCjkdfEzOnl+tJ2XACD4OXg==", + "dependencies": { + "@smithy/core": "^2.3.2", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.620.1.tgz", + "integrity": "sha512-ExuILJ2qLW5ZO+rgkNRj0xiAipKT16Rk77buvPP8csR7kkCflT/gXTyzRe/uzIiETTxM7tr8xuO9MP/DQXqkfg==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.620.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.620.0.tgz", - "integrity": "sha512-BI2BdrSKDmB/2ouB/NJR0PT0x/+5fmoF6XOE78hFBb4F5w/yynGgcJY936dF+oREfpME6ehjB2b0okGg78Scpw==", - "license": "Apache-2.0", + "version": "3.622.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.622.0.tgz", + "integrity": "sha512-VUHbr24Oll1RK3WR8XLUugLpgK9ZuxEm/NVeVqyFts1Ck9gsKpRg1x4eH7L7tW3SJ4TDEQNMbD7/7J+eoL2svg==", "dependencies": { "@aws-sdk/types": "3.609.0", - "@smithy/fetch-http-handler": "^3.2.3", + "@smithy/fetch-http-handler": "^3.2.4", "@smithy/node-http-handler": "^3.1.4", "@smithy/property-provider": "^3.1.3", "@smithy/protocol-http": "^4.1.0", - "@smithy/smithy-client": "^3.1.10", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.624.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.624.0.tgz", + "integrity": "sha512-mMoNIy7MO2WTBbdqMyLpbt6SZpthE6e0GkRYpsd0yozPt0RZopcBhEh+HG1U9Y1PVODo+jcMk353vAi61CfnhQ==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.622.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.624.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.624.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.624.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.624.0.tgz", + "integrity": "sha512-vYyGK7oNpd81BdbH5IlmQ6zfaQqU+rPwsKTDDBeLRjshtrGXOEpfoahVpG9PX0ibu32IOWp4ZyXBNyVrnvcMOw==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.622.0", + "@aws-sdk/credential-provider-ini": "3.624.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.624.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.620.1.tgz", + "integrity": "sha512-hWqFMidqLAkaV9G460+1at6qa9vySbjQKKc04p59OT7lZ5cO5VH5S4aI05e+m4j364MBROjjk2ugNvfNf/8ILg==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.624.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.624.0.tgz", + "integrity": "sha512-A02bayIjU9APEPKr3HudrFHEx0WfghoSPsPopckDkW7VBqO4wizzcxr75Q9A3vNX+cwg0wCN6UitTNe6pVlRaQ==", + "dependencies": { + "@aws-sdk/client-sso": "3.624.0", + "@aws-sdk/token-providers": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", "@smithy/types": "^3.3.0", - "@smithy/util-stream": "^3.1.2", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.620.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.620.0.tgz", - "integrity": "sha512-or8ahy4ysURcWgKX00367DMDTTyMynDEl+FQh4wce66fMyePhFVuoPcRgXzWsi8KYmL95sPCfJFNqBMyFNcgvQ==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.621.0.tgz", + "integrity": "sha512-w7ASSyfNvcx7+bYGep3VBgC3K6vEdLmlpjT7nSIHxxQf+WSdvy+HynwJosrpZax0sK5q0D1Jpn/5q+r5lwwW6w==", "dependencies": { - "@aws-sdk/credential-provider-env": "3.609.0", - "@aws-sdk/credential-provider-http": "3.620.0", - "@aws-sdk/credential-provider-ini": "3.620.0", - "@aws-sdk/credential-provider-process": "3.614.0", - "@aws-sdk/credential-provider-sso": "3.620.0", - "@aws-sdk/credential-provider-web-identity": "3.609.0", "@aws-sdk/types": "3.609.0", - "@smithy/credential-provider-imds": "^3.2.0", "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.621.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.620.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.620.0.tgz", - "integrity": "sha512-P9fYi6dzZIl8ITC7qAPf5DX9omI3LfA91g3KH+0OUmS3ctP7tN+gNo3HmqlzoqnwPe0pXn1FumYAe1qFl6Yjjg==", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.609.0.tgz", + "integrity": "sha512-xzsdoTkszGVqGVPjUmgoP7TORiByLueMHieI1fhQL888WPdqctwAx3ES6d/bA9Q/i8jnc6hs+Fjhy8UvBTkE9A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.609.0", - "@aws-sdk/credential-provider-http": "3.620.0", - "@aws-sdk/credential-provider-process": "3.614.0", - "@aws-sdk/credential-provider-sso": "3.620.0", - "@aws-sdk/credential-provider-web-identity": "3.609.0", "@aws-sdk/types": "3.609.0", - "@smithy/credential-provider-imds": "^3.2.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.620.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.620.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.620.0.tgz", - "integrity": "sha512-xtIj2hmq3jcKwvGmqhoYapbWeQfFyoQgKBtwD6nx0M6oS5lbFH4rzHhj0gBwatZDjMa35cWtcYVUJCv2/9mWvA==", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-ssec": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.609.0.tgz", + "integrity": "sha512-GZSD1s7+JswWOTamVap79QiDaIV7byJFssBW68GYjyRS5EBjNfwA/8s+6uE6g39R3ojyTbYOmvcANoZEhSULXg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.620.0", - "@aws-sdk/token-providers": "3.614.0", "@aws-sdk/types": "3.609.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, @@ -9858,7 +10470,6 @@ "version": "3.0.5", "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.5.tgz", "integrity": "sha512-SkW5LxfkSI1bUC74OtfBbdz+grQXYiPYolyu8VfpLIjEoN/sHVBlLeGXMQ1vX4ejkgfv6sxVbQJ32yF2cl1veA==", - "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^3.1.4", "@smithy/types": "^3.3.0", @@ -9874,7 +10485,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.0.tgz", "integrity": "sha512-0SCIzgd8LYZ9EJxUjLXBmEKSZR/P/w6l7Rz/pab9culE/RWuqelAKGJvn5qUOl8BgX8Yj5HWM50A5hiB/RzsgA==", - "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^3.1.4", "@smithy/property-provider": "^3.1.3", @@ -9890,7 +10500,6 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.4.tgz", "integrity": "sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==", - "license": "Apache-2.0", "dependencies": { "@smithy/protocol-http": "^4.1.0", "@smithy/querystring-builder": "^3.0.3", @@ -9903,7 +10512,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.3.tgz", "integrity": "sha512-2ctBXpPMG+B3BtWSGNnKELJ7SH9e4TNefJS0cd2eSkOOROeBnnVBnAy9LtJ8tY4vUEoe55N4CNPxzbWvR39iBw==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", "@smithy/util-buffer-from": "^3.0.0", @@ -9918,7 +10526,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", - "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -9931,7 +10538,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.3.tgz", "integrity": "sha512-ID1eL/zpDULmHJbflb864k72/SNOZCADRc9i7Exq3RUNJw6raWUSlFEQ+3PX3EYs++bTxZB2dE9mEHTQLv61tw==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -9953,7 +10559,6 @@ "version": "3.0.5", "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.5.tgz", "integrity": "sha512-ILEzC2eyxx6ncej3zZSwMpB5RJ0zuqH7eMptxC4KN3f+v9bqT8ohssKbhNR78k/2tWW+KS5Spw+tbPF4Ejyqvw==", - "license": "Apache-2.0", "dependencies": { "@smithy/protocol-http": "^4.1.0", "@smithy/types": "^3.3.0", @@ -9967,7 +10572,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.0.tgz", "integrity": "sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==", - "license": "Apache-2.0", "dependencies": { "@smithy/middleware-serde": "^3.0.3", "@smithy/node-config-provider": "^3.1.4", @@ -9982,15 +10586,14 @@ } }, "node_modules/@aws-sdk/client-s3/node_modules/@smithy/middleware-retry": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.13.tgz", - "integrity": "sha512-zvCLfaRYCaUmjbF2yxShGZdolSHft7NNCTA28HVN9hKcEbOH+g5irr1X9s+in8EpambclGnevZY4A3lYpvDCFw==", - "license": "Apache-2.0", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.15.tgz", + "integrity": "sha512-iTMedvNt1ApdvkaoE8aSDuwaoc+BhvHqttbA/FO4Ty+y/S5hW6Ci/CTScG7vam4RYJWZxdTElc3MEfHRVH6cgQ==", "dependencies": { "@smithy/node-config-provider": "^3.1.4", "@smithy/protocol-http": "^4.1.0", "@smithy/service-error-classification": "^3.0.3", - "@smithy/smithy-client": "^3.1.11", + "@smithy/smithy-client": "^3.2.0", "@smithy/types": "^3.3.0", "@smithy/util-middleware": "^3.0.3", "@smithy/util-retry": "^3.0.3", @@ -10005,7 +10608,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -10018,7 +10620,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz", "integrity": "sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -10031,7 +10632,6 @@ "version": "3.1.4", "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.4.tgz", "integrity": "sha512-YvnElQy8HR4vDcAjoy7Xkx9YT8xZP4cBXcbJSgm/kxmiQu08DwUwj8rkGnyoJTpfl/3xYHH+d8zE+eHqoDCSdQ==", - "license": "Apache-2.0", "dependencies": { "@smithy/property-provider": "^3.1.3", "@smithy/shared-ini-file-loader": "^3.1.4", @@ -10046,7 +10646,6 @@ "version": "3.1.4", "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.4.tgz", "integrity": "sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==", - "license": "Apache-2.0", "dependencies": { "@smithy/abort-controller": "^3.1.1", "@smithy/protocol-http": "^4.1.0", @@ -10062,7 +10661,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.3.tgz", "integrity": "sha512-zahyOVR9Q4PEoguJ/NrFP4O7SMAfYO1HLhB18M+q+Z4KFd4V2obiMnlVoUFzFLSPeVt1POyNWneHHrZaTMoc/g==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -10075,7 +10673,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -10088,7 +10685,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz", "integrity": "sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", "@smithy/util-uri-escape": "^3.0.0", @@ -10102,7 +10698,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz", "integrity": "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -10115,7 +10710,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.3.tgz", "integrity": "sha512-Jn39sSl8cim/VlkLsUhRFq/dKDnRUFlfRkvhOJaUbLBXUsLRLNf9WaxDv/z9BjuQ3A6k/qE8af1lsqcwm7+DaQ==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0" }, @@ -10127,7 +10721,6 @@ "version": "3.1.4", "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.4.tgz", "integrity": "sha512-qMxS4hBGB8FY2GQqshcRUy1K6k8aBWP5vwm8qKkCT3A9K2dawUwOIJfqh9Yste/Bl0J2lzosVyrXDj68kLcHXQ==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -10136,11 +10729,28 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/signature-v4": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.1.0.tgz", + "integrity": "sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag==", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-uri-escape": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/client-s3/node_modules/@smithy/smithy-client": { - "version": "3.1.11", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.1.11.tgz", - "integrity": "sha512-l0BpyYkciNyMaS+PnFFz4aO5sBcXvGLoJd7mX9xrMBIm2nIQBVvYgp2ZpPDMzwjKCavsXu06iuCm0F6ZJZc6yQ==", - "license": "Apache-2.0", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.2.0.tgz", + "integrity": "sha512-pDbtxs8WOhJLJSeaF/eAbPgXg4VVYFlRcL/zoNYA5WbG3wBL06CHtBSg53ppkttDpAJ/hdiede+xApip1CwSLw==", "dependencies": { "@smithy/middleware-endpoint": "^3.1.0", "@smithy/middleware-stack": "^3.0.3", @@ -10169,7 +10779,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz", "integrity": "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==", - "license": "Apache-2.0", "dependencies": { "@smithy/querystring-parser": "^3.0.3", "@smithy/types": "^3.3.0", @@ -10207,7 +10816,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz", "integrity": "sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==", - "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" } @@ -10216,7 +10824,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz", "integrity": "sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==", - "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -10228,7 +10835,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz", "integrity": "sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==", - "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -10237,13 +10843,12 @@ } }, "node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-defaults-mode-browser": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.13.tgz", - "integrity": "sha512-ZIRSUsnnMRStOP6OKtW+gCSiVFkwnfQF2xtf32QKAbHR6ACjhbAybDvry+3L5qQYdh3H6+7yD/AiUE45n8mTTw==", - "license": "Apache-2.0", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.15.tgz", + "integrity": "sha512-FZ4Psa3vjp8kOXcd3HJOiDPBCWtiilLl57r0cnNtq/Ga9RSDrM5ERL6xt+tO43+2af6Pn5Yp92x2n5vPuduNfg==", "dependencies": { "@smithy/property-provider": "^3.1.3", - "@smithy/smithy-client": "^3.1.11", + "@smithy/smithy-client": "^3.2.0", "@smithy/types": "^3.3.0", "bowser": "^2.11.0", "tslib": "^2.6.2" @@ -10253,16 +10858,15 @@ } }, "node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-defaults-mode-node": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.13.tgz", - "integrity": "sha512-voUa8TFJGfD+U12tlNNLCDlXibt9vRdNzRX45Onk/WxZe7TS+hTOZouEZRa7oARGicdgeXvt1A0W45qLGYdy+g==", - "license": "Apache-2.0", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.15.tgz", + "integrity": "sha512-KSyAAx2q6d0t6f/S4XB2+3+6aQacm3aLMhs9aLMqn18uYGUepbdssfogW5JQZpc6lXNBnp0tEnR5e9CEKmEd7A==", "dependencies": { "@smithy/config-resolver": "^3.0.5", "@smithy/credential-provider-imds": "^3.2.0", "@smithy/node-config-provider": "^3.1.4", "@smithy/property-provider": "^3.1.3", - "@smithy/smithy-client": "^3.1.11", + "@smithy/smithy-client": "^3.2.0", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, @@ -10274,7 +10878,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.0.5.tgz", "integrity": "sha512-ReQP0BWihIE68OAblC/WQmDD40Gx+QY1Ez8mTdFMXpmjfxSyz2fVQu3A4zXRfQU9sZXtewk3GmhfOHswvX+eNg==", - "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^3.1.4", "@smithy/types": "^3.3.0", @@ -10300,7 +10903,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -10313,7 +10915,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.3.tgz", "integrity": "sha512-AFw+hjpbtVApzpNDhbjNG5NA3kyoMs7vx0gsgmlJF4s+yz1Zlepde7J58zpIRIsdjc+emhpAITxA88qLkPF26w==", - "license": "Apache-2.0", "dependencies": { "@smithy/service-error-classification": "^3.0.3", "@smithy/types": "^3.3.0", @@ -10327,7 +10928,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.3.tgz", "integrity": "sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==", - "license": "Apache-2.0", "dependencies": { "@smithy/fetch-http-handler": "^3.2.4", "@smithy/node-http-handler": "^3.1.4", @@ -10346,7 +10946,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", - "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -10359,7 +10958,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", - "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -10407,6 +11005,27 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/client-s3/node_modules/fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/@aws-sdk/client-secrets-manager": { "version": "3.637.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.637.0.tgz", @@ -18071,20 +18690,22 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.620.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.620.0.tgz", - "integrity": "sha512-AAZ6NLVOx/bP97PYj/afCMeySzxOHocgJG3ZXh6f8MnJcGpZgx8NyRm0vtiYUTFrS2JtU4xV05Dl3j4afV3s4A==", - "license": "Apache-2.0", + "version": "3.626.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.626.0.tgz", + "integrity": "sha512-frFh6GQ1OEGueB0fL6Ft5rdHF+eu8JZUREjeBNEcg1qRqtMpPOlYkKzJ434d4zo+JHSK5xKFeb/Gu/kvB4LxEA==", "dependencies": { + "@aws-sdk/core": "3.624.0", "@aws-sdk/types": "3.609.0", "@aws-sdk/util-arn-parser": "3.568.0", + "@smithy/core": "^2.3.2", "@smithy/node-config-provider": "^3.1.4", "@smithy/protocol-http": "^4.1.0", "@smithy/signature-v4": "^4.1.0", - "@smithy/smithy-client": "^3.1.10", + "@smithy/smithy-client": "^3.1.12", "@smithy/types": "^3.3.0", "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-stream": "^3.1.2", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-stream": "^3.1.3", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, @@ -18092,11 +18713,29 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@aws-sdk/core": { + "version": "3.624.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.624.0.tgz", + "integrity": "sha512-WyFmPbhRIvtWi7hBp8uSFy+iPpj8ccNV/eX86hwF4irMjfc/FtsGVIAeBXxXM/vGCjkdfEzOnl+tJ2XACD4OXg==", + "dependencies": { + "@smithy/core": "^2.3.2", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@aws-sdk/types": { "version": "3.609.0", "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -18109,7 +18748,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz", "integrity": "sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -18122,7 +18760,6 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.4.tgz", "integrity": "sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==", - "license": "Apache-2.0", "dependencies": { "@smithy/protocol-http": "^4.1.0", "@smithy/querystring-builder": "^3.0.3", @@ -18135,7 +18772,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", - "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -18147,7 +18783,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.0.tgz", "integrity": "sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==", - "license": "Apache-2.0", "dependencies": { "@smithy/middleware-serde": "^3.0.3", "@smithy/node-config-provider": "^3.1.4", @@ -18165,7 +18800,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -18178,7 +18812,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz", "integrity": "sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -18191,7 +18824,6 @@ "version": "3.1.4", "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.4.tgz", "integrity": "sha512-YvnElQy8HR4vDcAjoy7Xkx9YT8xZP4cBXcbJSgm/kxmiQu08DwUwj8rkGnyoJTpfl/3xYHH+d8zE+eHqoDCSdQ==", - "license": "Apache-2.0", "dependencies": { "@smithy/property-provider": "^3.1.3", "@smithy/shared-ini-file-loader": "^3.1.4", @@ -18206,7 +18838,6 @@ "version": "3.1.4", "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.4.tgz", "integrity": "sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==", - "license": "Apache-2.0", "dependencies": { "@smithy/abort-controller": "^3.1.1", "@smithy/protocol-http": "^4.1.0", @@ -18222,7 +18853,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.3.tgz", "integrity": "sha512-zahyOVR9Q4PEoguJ/NrFP4O7SMAfYO1HLhB18M+q+Z4KFd4V2obiMnlVoUFzFLSPeVt1POyNWneHHrZaTMoc/g==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -18235,7 +18865,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -18248,7 +18877,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz", "integrity": "sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", "@smithy/util-uri-escape": "^3.0.0", @@ -18262,7 +18890,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz", "integrity": "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -18275,7 +18902,6 @@ "version": "3.1.4", "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.4.tgz", "integrity": "sha512-qMxS4hBGB8FY2GQqshcRUy1K6k8aBWP5vwm8qKkCT3A9K2dawUwOIJfqh9Yste/Bl0J2lzosVyrXDj68kLcHXQ==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -18288,7 +18914,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.1.0.tgz", "integrity": "sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag==", - "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "@smithy/protocol-http": "^4.1.0", @@ -18304,10 +18929,9 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/smithy-client": { - "version": "3.1.11", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.1.11.tgz", - "integrity": "sha512-l0BpyYkciNyMaS+PnFFz4aO5sBcXvGLoJd7mX9xrMBIm2nIQBVvYgp2ZpPDMzwjKCavsXu06iuCm0F6ZJZc6yQ==", - "license": "Apache-2.0", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.2.0.tgz", + "integrity": "sha512-pDbtxs8WOhJLJSeaF/eAbPgXg4VVYFlRcL/zoNYA5WbG3wBL06CHtBSg53ppkttDpAJ/hdiede+xApip1CwSLw==", "dependencies": { "@smithy/middleware-endpoint": "^3.1.0", "@smithy/middleware-stack": "^3.0.3", @@ -18324,7 +18948,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", - "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -18336,7 +18959,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz", "integrity": "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==", - "license": "Apache-2.0", "dependencies": { "@smithy/querystring-parser": "^3.0.3", "@smithy/types": "^3.3.0", @@ -18347,7 +18969,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", - "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "@smithy/util-utf8": "^3.0.0", @@ -18361,7 +18982,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", - "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -18374,7 +18994,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz", "integrity": "sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==", - "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -18386,7 +19005,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", - "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -18398,7 +19016,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -18411,7 +19028,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.3.tgz", "integrity": "sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==", - "license": "Apache-2.0", "dependencies": { "@smithy/fetch-http-handler": "^3.2.4", "@smithy/node-http-handler": "^3.1.4", @@ -18430,7 +19046,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", - "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -18442,7 +19057,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", - "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" @@ -18451,6 +19065,27 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/@aws-sdk/middleware-sdk-sqs": { "version": "3.552.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sqs/-/middleware-sdk-sqs-3.552.0.tgz", @@ -18514,7 +19149,6 @@ "version": "3.620.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.620.0.tgz", "integrity": "sha512-gxI7rubiaanUXaLfJ4NybERa9MGPNg2Ycl/OqANsozrBnR3Pw8vqy3EuVImQOyn2pJ2IFvl8ZPoSMHf4pX56FQ==", - "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/property-provider": "^3.1.3", @@ -18532,7 +19166,6 @@ "version": "3.609.0", "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -18545,7 +19178,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", - "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -18557,7 +19189,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.3.tgz", "integrity": "sha512-zahyOVR9Q4PEoguJ/NrFP4O7SMAfYO1HLhB18M+q+Z4KFd4V2obiMnlVoUFzFLSPeVt1POyNWneHHrZaTMoc/g==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -18570,7 +19201,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -18583,7 +19213,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.1.0.tgz", "integrity": "sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag==", - "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "@smithy/protocol-http": "^4.1.0", @@ -18602,7 +19231,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", - "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -18614,7 +19242,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", - "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -18627,7 +19254,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", - "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -18639,7 +19265,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -18652,7 +19277,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", - "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -18664,7 +19288,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", - "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" @@ -18875,12 +19498,11 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.620.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.620.0.tgz", - "integrity": "sha512-yu1pTCqIbkSdaOvmyfW9vV9jWe3pDApkQPZLg4VEN5dXDWRtgQ/amv88myyCEoG14irUN1tsbvytcKzGyEXnhA==", - "license": "Apache-2.0", + "version": "3.626.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.626.0.tgz", + "integrity": "sha512-n3yN668b2XLY6155y2KRCCDfA67Acxf/wUS60wGPNrJKk9O5AZzGQzZF8tLfMSng5YBS/CCHN40ooMhRwSLWUg==", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.620.0", + "@aws-sdk/middleware-sdk-s3": "3.626.0", "@aws-sdk/types": "3.609.0", "@smithy/protocol-http": "^4.1.0", "@smithy/signature-v4": "^4.1.0", @@ -18895,7 +19517,6 @@ "version": "3.609.0", "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -18908,7 +19529,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", - "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -18920,7 +19540,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -18933,7 +19552,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.1.0.tgz", "integrity": "sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag==", - "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "@smithy/protocol-http": "^4.1.0", @@ -18952,7 +19570,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", - "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -18964,7 +19581,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", - "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -18977,7 +19593,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", - "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -18989,7 +19604,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -19002,7 +19616,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", - "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -19014,7 +19627,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", - "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" diff --git a/package.json b/package.json index 54322bbe..e5510c8f 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "@adobe/spacecat-shared-http-utils": "1.6.8", "@adobe/spacecat-shared-rum-api-client": "2.8.0", "@adobe/spacecat-shared-rum-api-client-v1": "npm:@adobe/spacecat-shared-rum-api-client@1.8.4", + "@aws-sdk/client-s3": "3.627.0", "@aws-sdk/client-lambda": "3.637.0", "@aws-sdk/credential-provider-node": "3.637.0", "@adobe/spacecat-shared-utils": "1.19.6", diff --git a/src/index.js b/src/index.js index a99c7086..f021da39 100644 --- a/src/index.js +++ b/src/index.js @@ -17,6 +17,7 @@ import { resolveSecretsName, sqsEventAdapter } from '@adobe/spacecat-shared-util import { internalServerError, notFound, ok } from '@adobe/spacecat-shared-http-utils'; import sqs from './support/sqs.js'; +import s3Client from './support/s3-client.js'; import apex from './apex/handler.js'; import cwv from './cwv/handler.js'; import lhsDesktop from './lhs/handler-desktop.js'; @@ -29,6 +30,7 @@ import experimentation from './experimentation/handler.js'; import conversion from './conversion/handler.js'; import essExperimentationDaily from './experimentation-ess/daily.js'; import essExperimentationAll from './experimentation-ess/all.js'; +import metaTags from './metatags/handler.js'; import opportunities from './opportunities/opportunities.js'; import costs from './costs/handler.js'; import structuredData from './structured-data/handler.js'; @@ -46,6 +48,7 @@ const HANDLERS = { conversion, 'experimentation-ess-daily': essExperimentationDaily, 'experimentation-ess-all': essExperimentationAll, + metaTags, opportunities, costs, 'structured-data': structuredData, @@ -95,5 +98,6 @@ export const main = wrap(run) .with(dataAccess) .with(sqsEventAdapter) .with(sqs) + .with(s3Client) .with(secrets, { name: resolveSecretsName }) .with(helixStatus); diff --git a/src/metatags/constants.js b/src/metatags/constants.js new file mode 100644 index 00000000..ed50aee1 --- /dev/null +++ b/src/metatags/constants.js @@ -0,0 +1,35 @@ +/* + * Copyright 2024 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. + */ + +// Tag Names +export const TITLE = 'title'; +export const DESCRIPTION = 'description'; +export const H1 = 'h1'; + +// SEO impact category +export const HIGH = 'High'; +export const MODERATE = 'Moderate'; + +// Tags lengths +export const TAG_LENGTHS = { + [TITLE]: { + minLength: 40, + maxLength: 60, + }, + [DESCRIPTION]: { + minLength: 140, + maxLength: 160, + }, + [H1]: { + maxLength: 60, + }, +}; diff --git a/src/metatags/handler.js b/src/metatags/handler.js new file mode 100644 index 00000000..d95a6976 --- /dev/null +++ b/src/metatags/handler.js @@ -0,0 +1,121 @@ +/* + * Copyright 2024 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 { + internalServerError, noContent, notFound, ok, +} from '@adobe/spacecat-shared-http-utils'; +import { JSDOM } from 'jsdom'; +import { retrieveSiteBySiteId } from '../utils/data-access.js'; +import { getObjectFromKey, getObjectKeysUsingPrefix } from '../utils/s3-utils.js'; +import { DESCRIPTION, H1, TITLE } from './constants.js'; +import SeoChecks from './seo-checks.js'; + +function extractTagsFromHtml(htmlContent) { + const dom = new JSDOM(htmlContent); + const doc = dom.window.document; + + const title = doc.querySelector('title')?.textContent; + const description = doc.querySelector('meta[name="description"]')?.getAttribute('content'); + const h1Tags = Array.from(doc.querySelectorAll('h1')).map((h1) => h1.textContent); + return { + [TITLE]: title, + [DESCRIPTION]: description, + [H1]: h1Tags, + }; +} + +async function fetchAndProcessPageObject(s3Client, bucketName, key, prefix, log) { + const object = await getObjectFromKey(s3Client, bucketName, key, log); + if (!object?.Body?.rawBody || typeof object.Body.rawBody !== 'string') { + log.error(`No Scraped html found in S3 ${key} object`); + return null; + } + const tags = extractTagsFromHtml(object.Body.rawBody); + const pageUrl = key.slice(prefix.length - 1).replace('.json', ''); // Remove the prefix and .json suffix + return { + [pageUrl]: tags, + }; +} + +export default async function auditMetaTags(message, context) { + const { type, url: siteId } = message; + const { + dataAccess, log, s3Client, + } = context; + + try { + log.info(`Received ${type} audit request for siteId: ${siteId}`); + const site = await retrieveSiteBySiteId(dataAccess, siteId, log); + if (!site) { + return notFound('Site not found'); + } + if (!site.isLive()) { + log.info(`Site ${siteId} is not live`); + return ok(); + } + const configuration = await dataAccess.getConfiguration(); + if (!configuration.isHandlerEnabledForSite(type, site)) { + log.info(`Audit type ${type} disabled for site ${siteId}`); + return ok(); + } + // Fetch site's scraped content from S3 + const bucketName = context.env.S3_BUCKET_NAME; + const prefix = `scrapes/${siteId}/`; + const scrapedObjectKeys = await getObjectKeysUsingPrefix(s3Client, bucketName, prefix); + const extractedTags = {}; + for (const key of scrapedObjectKeys) { + // eslint-disable-next-line no-await-in-loop + const pageMetadata = await fetchAndProcessPageObject(s3Client, bucketName, key, prefix, log); + if (pageMetadata) { + Object.assign(extractedTags, pageMetadata); + } + } + if (Object.entries(extractedTags).length === 0) { + log.error(`Failed to extract tags from scraped content for bucket ${bucketName} and prefix ${prefix}`); + return notFound('Site tags data not available'); + } + // Fetch keywords for top pages + const topPages = await dataAccess.getTopPagesForSite(siteId, 'ahrefs', 'global'); + const keywords = {}; + topPages.forEach((page) => { + const endpoint = new URL(page.getURL).pathname; + keywords[endpoint] = page.getTopKeyword(); + }); + // Perform SEO checks + const seoChecks = new SeoChecks(log, keywords); + for (const [pageUrl, pageTags] of Object.entries(extractedTags)) { + seoChecks.performChecks(pageUrl, pageTags); + } + const detectedTags = seoChecks.getDetectedTags(); + // Prepare Audit result + const auditResult = { + detectedTags, + sourceS3Folder: `${bucketName}/${prefix}`, + fullAuditRef: 'na', + }; + const auditData = { + siteId: site.getId(), + isLive: site.isLive(), + auditedAt: new Date().toISOString(), + auditType: type, + fullAuditRef: auditResult?.fullAuditRef, + auditResult, + }; + // Persist Audit result + await dataAccess.addAudit(auditData); + log.info(`Successfully audited ${siteId} for ${type} type audit`); + return noContent(); + } catch (e) { + log.error(`${type} type audit for ${siteId} failed with error: ${e.message}`, e); + return internalServerError(`Internal server error: ${e.message}`); + } +} diff --git a/src/metatags/seo-checks.js b/src/metatags/seo-checks.js new file mode 100644 index 00000000..361cb9df --- /dev/null +++ b/src/metatags/seo-checks.js @@ -0,0 +1,221 @@ +/* + * Copyright 2023 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 { + DESCRIPTION, + TITLE, + H1, + TAG_LENGTHS, + HIGH, + MODERATE, +} from './constants.js'; + +class SeoChecks { + constructor(log, keywords) { + this.log = log; + this.keywords = keywords; + this.detectedTags = { + [TITLE]: [], + [DESCRIPTION]: [], + [H1]: [], + }; + this.allTags = { + [TITLE]: {}, + [DESCRIPTION]: {}, + [H1]: {}, + }; + } + + /** + * Adds an entry to the detected tags array. + * @param {string} pageUrl - The URL of the page. + * @param {string} tagName - The name of the tag (e.g., 'title', 'description', 'h1'). + * @param {string} tagContent - The content of the tag. + * @param {string} seoImpact - The impact level of the issue (e.g., 'High', 'Moderate'). + * @param {string} seoOpportunityText - The text describing the SEO opportunity or issue. + */ + addDetectedTagEntry(pageUrl, tagName, tagContent, seoImpact, seoOpportunityText) { + this.detectedTags[tagName].push({ + pageUrl, + tagName, + tagContent, + seoImpact, + seoOpportunityText, + }); + } + + /** + * Creates a message for length checks. + * @param {string} tagName - The name of the tag (e.g., 'title', 'description', 'h1'). + * @param {string} tagContent - The content of the tag. + * @returns {string} - The message indicating the tag length issue. + */ + static createLengthCheckText(tagName, tagContent = '') { + let status = 'within'; + if (tagContent.length < TAG_LENGTHS[tagName].minLength) { + status = 'below'; + } else if (tagContent.length > TAG_LENGTHS[tagName].maxLength) { + status = 'above'; + } + const minLength = TAG_LENGTHS[tagName].minLength ? `${TAG_LENGTHS[tagName].minLength}-` : ''; + return `The ${tagName} tag on this page has a length of ${tagContent.length} characters, which is ${status} the recommended length of ${minLength}${TAG_LENGTHS[tagName].maxLength} characters.`; + } + + /** + * Checks for missing tags on the page and adds to detected tags array if found lacking. + * @param {string} url - The URL of the page. + * @param {object} pageTags - An object containing the tags of the page. + */ + checkForMissingTags(url, pageTags) { + [TITLE, DESCRIPTION, H1].forEach((tagName) => { + if (pageTags[tagName] === undefined + || (Array.isArray(pageTags[tagName]) && pageTags[tagName].length === 0)) { + this.addDetectedTagEntry( + url, + tagName, + '', + HIGH, + `The ${tagName} tag on this page is missing. It's recommended to have a ${tagName} tag on each page.`, + ); + } + }); + } + + /** + * Checks if tag lengths are within recommended limits + * and adds to detected tags array if found lacking. + * @param {string} url - The URL of the page. + * @param {object} tags - An object containing the tags of the page. + */ + checkForTagsLength(url, tags) { + [TITLE, DESCRIPTION].forEach((tagName) => { + if (tags[tagName]?.length > TAG_LENGTHS[tagName].maxLength + || tags[tagName]?.length < TAG_LENGTHS[tagName].minLength) { + this.addDetectedTagEntry( + url, + tagName, + tags[tagName], + MODERATE, + SeoChecks.createLengthCheckText(tagName, tags[tagName]), + ); + } + }); + + if (Array.isArray(tags[H1]) && tags[H1][0]?.length > TAG_LENGTHS[H1].maxLength) { + this.addDetectedTagEntry( + url, + H1, + tags[H1][0], + MODERATE, + SeoChecks.createLengthCheckText(H1, tags[H1][0]), + ); + } + } + + /** + * Checks if there are more than one H1 tags and adds to detected tags array if found lacking. + * @param {string} url - The URL of the page. + * @param {object} pageTags - An object containing the tags of the page. + */ + checkForH1Count(url, pageTags) { + if (pageTags[H1]?.length > 1) { + this.addDetectedTagEntry( + url, + H1, + pageTags[H1], + MODERATE, + `There are ${pageTags[H1].length} H1 tags on this page, which is more than the recommended count of 1.`, + ); + } + } + + /** + * Checks for keyword inclusion in the tags and adds to detected tags array if found lacking. + * @param {string} url - The URL of the page. + * @param {object} pageTags - An object containing the tags of the page. + */ + checkForKeywordInclusion(url, pageTags) { + if (!this.keywords[url]) { + this.log.warn(`Keyword Inclusion check failed, keyword not found for ${url}`); + return; + } + const keyword = this.keywords[url].toLowerCase(); + + const tags = { + [TITLE]: pageTags[TITLE], + [DESCRIPTION]: pageTags[DESCRIPTION], + [H1]: pageTags[H1][0], + }; + + Object.entries(tags).forEach(([tagName, tagContent]) => { + if (!tagContent?.toLowerCase().includes(keyword)) { + this.addDetectedTagEntry( + url, + tagName, + tagContent, + HIGH, + `The ${tagName} tag on this page is missing the page's top keyword '${keyword}'. ` + + `It's recommended to include the primary keyword in the ${tagName} tag.`, + ); + } + }); + } + + /** + * Checks for tag uniqueness and adds to detected tags array if found lacking. + * @param {object} pageTags - An object containing the tags of the page. + * @param {string} url - The URL of the page. + */ + checkForUniqueness(url, pageTags) { + const tags = { + [TITLE]: pageTags[TITLE], + [DESCRIPTION]: pageTags[DESCRIPTION], + [H1]: Array.isArray(pageTags[H1]) ? pageTags[H1][0] : '', + }; + Object.entries(tags).forEach(([tagName, tagContent = '']) => { + if (tagContent && this.allTags[tagName][tagContent.toLowerCase()]) { + this.addDetectedTagEntry( + url, + tagName, + tagContent, + HIGH, + `The ${tagName} tag on this page is identical to the one on ${this.allTags[tagName][tagContent.toLowerCase()]}. ` + + `It's recommended to have unique ${tagName} tags for each page.`, + ); + } + this.allTags[tagName][tagContent.toLowerCase()] = url; + }); + } + + /** + * Performs all SEO checks on the provided tags. + * @param {string} url - The URL of the page. + * @param {object} pageTags - An object containing the tags of the page. + */ + performChecks(url, pageTags) { + this.checkForMissingTags(url, pageTags); + this.checkForTagsLength(url, pageTags); + this.checkForH1Count(url, pageTags); + this.checkForKeywordInclusion(url, pageTags); + this.checkForUniqueness(url, pageTags); + } + + /** + * Gets the detected tags for the site. + * @returns {object} - The detected tags object. + */ + getDetectedTags() { + return this.detectedTags; + } +} + +export default SeoChecks; diff --git a/src/support/s3-client.js b/src/support/s3-client.js new file mode 100644 index 00000000..45065342 --- /dev/null +++ b/src/support/s3-client.js @@ -0,0 +1,27 @@ +/* + * Copyright 2024 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'; + +/** + * Adds an S3Client instance to the context. + * + * @returns {function(object, UniversalContext): Promise} + */ +export default function s3Client(fn) { + return async (request, context) => { + if (!context.s3Client) { + context.s3Client = new S3Client(); + } + return fn(request, context); + }; +} diff --git a/src/utils/s3-utils.js b/src/utils/s3-utils.js new file mode 100644 index 00000000..f2d4f6e4 --- /dev/null +++ b/src/utils/s3-utils.js @@ -0,0 +1,43 @@ +/* + * Copyright 2024 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 { ListObjectsV2Command } from '@aws-sdk/client-s3'; + +export async function getObjectKeysUsingPrefix(s3Client, bucketName, prefix, log) { + const objectKeys = []; + try { + const params = { + Bucket: bucketName, + Prefix: prefix, + MaxKeys: 1000, + }; + const data = await s3Client.send(new ListObjectsV2Command(params)); + data?.Contents?.forEach((obj) => { + objectKeys.push(obj.Key); + }); + } catch (err) { + log.error(`Error while fetching S3 object keys using bucket ${bucketName} and prefix ${prefix}`, err); + } + return objectKeys; +} + +export async function getObjectFromKey(s3Client, bucketName, key, log) { + const params = { + Bucket: bucketName, + Key: key, + }; + try { + return await s3Client.getObject(params).promise(); + } catch (err) { + log.error(`Error while fetching S3 object from bucket ${bucketName} using key ${key}`, err); + return null; + } +} diff --git a/test/audits/metatags.test.js b/test/audits/metatags.test.js new file mode 100644 index 00000000..e51b800c --- /dev/null +++ b/test/audits/metatags.test.js @@ -0,0 +1,474 @@ +/* + * Copyright 2024 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 chaiAsPromised from 'chai-as-promised'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import { + ok, + noContent, + notFound, + internalServerError, +} from '@adobe/spacecat-shared-http-utils'; +import { ListObjectsV2Command } from '@aws-sdk/client-s3'; +import { + TITLE, + DESCRIPTION, + H1, + HIGH, + MODERATE, +} from '../../src/metatags/constants.js'; +import SeoChecks from '../../src/metatags/seo-checks.js'; +import auditMetaTags from '../../src/metatags/handler.js'; + +use(sinonChai); +use(chaiAsPromised); + +describe('SeoTags', () => { + describe('SeoChecks', () => { + let seoChecks; + let logMock; + let keywordsMock; + + beforeEach(() => { + logMock = { + warn: () => { + }, + }; + keywordsMock = { + 'https://example.com': 'example', + }; + seoChecks = new SeoChecks(logMock, keywordsMock); + }); + + describe('addDetectedTagEntry', () => { + it('should add a detected tag entry to the detectedTags object', () => { + seoChecks.addDetectedTagEntry('https://example.com', TITLE, 'Example Title', HIGH, 'SEO opportunity text'); + + expect(seoChecks.detectedTags[TITLE]).to.have.lengthOf(1); + expect(seoChecks.detectedTags[TITLE][0]).to.deep.equal({ + pageUrl: 'https://example.com', + tagName: TITLE, + tagContent: 'Example Title', + seoImpact: HIGH, + seoOpportunityText: 'SEO opportunity text', + }); + }); + }); + + describe('createLengthCheckText', () => { + it('should create the correct length check message for a tag within the limit', () => { + const message = SeoChecks.createLengthCheckText(TITLE, 'This should a valid Title, this should a valid title.'); + + expect(message).to.equal('The title tag on this page has a length of 53 characters, which is within the recommended length of 40-60 characters.'); + }); + + it('should create the correct length check message for a tag below the limit', () => { + const message = SeoChecks.createLengthCheckText(TITLE, 'Short'); + + expect(message).to.equal('The title tag on this page has a length of 5 characters, which is below the recommended length of 40-60 characters.'); + }); + + it('should create the correct length check message for a tag above the limit', () => { + const longTitle = 'L'.repeat(70); // 70 characters long title + const message = SeoChecks.createLengthCheckText(TITLE, longTitle); + + expect(message).to.equal('The title tag on this page has a length of 70 characters, which is above the recommended length of 40-60 characters.'); + }); + }); + + describe('checkForMissingTags', () => { + it('should detect and log missing tags', () => { + const pageTags = {}; + + seoChecks.checkForMissingTags('https://example.com', pageTags); + + expect(seoChecks.detectedTags[TITLE]).to.have.lengthOf(1); + expect(seoChecks.detectedTags[DESCRIPTION]).to.have.lengthOf(1); + expect(seoChecks.detectedTags[H1]).to.have.lengthOf(1); + }); + }); + + describe('checkForTagsLength', () => { + it('should detect tags that are too short or too long', () => { + const pageTags = { + [TITLE]: 'Short', + [DESCRIPTION]: 'D'.repeat(200), // too long + [H1]: ['Valid H1'], + }; + + seoChecks.checkForTagsLength('https://example.com', pageTags); + + expect(seoChecks.detectedTags[TITLE]).to.have.lengthOf(1); + expect(seoChecks.detectedTags[DESCRIPTION]).to.have.lengthOf(1); + expect(seoChecks.detectedTags[H1]).to.have.lengthOf(0); + }); + }); + + describe('checkForH1Count', () => { + it('should detect multiple H1 tags', () => { + const pageTags = { + [H1]: ['First H1', 'Second H1'], + }; + + seoChecks.checkForH1Count('https://example.com', pageTags); + + expect(seoChecks.detectedTags[H1]).to.have.lengthOf(1); + expect(seoChecks.detectedTags[H1][0]).to.deep.equal({ + pageUrl: 'https://example.com', + tagName: H1, + tagContent: ['First H1', 'Second H1'], + seoImpact: MODERATE, + seoOpportunityText: 'There are 2 H1 tags on this page, which is more than the recommended count of 1.', + }); + }); + }); + + describe('checkForKeywordInclusion', () => { + it('should detect missing keywords in tags', () => { + const pageTags = { + [TITLE]: 'Some other title', + [DESCRIPTION]: 'Some other description', + [H1]: ['Some other H1'], + }; + + seoChecks.checkForKeywordInclusion('https://example.com', pageTags); + + expect(seoChecks.detectedTags[TITLE]).to.have.lengthOf(1); + expect(seoChecks.detectedTags[DESCRIPTION]).to.have.lengthOf(1); + expect(seoChecks.detectedTags[H1]).to.have.lengthOf(1); + }); + + it('should log a warning if the keyword is not found for the URL', () => { + const logSpy = sinon.spy(logMock, 'warn'); + seoChecks.checkForKeywordInclusion('https://unknown.com', {}); + + expect(logSpy.calledOnce).to.be.true; + expect(logSpy.firstCall.args[0]).to.equal('Keyword Inclusion check failed, keyword not found for https://unknown.com'); + }); + }); + + describe('checkForUniqueness', () => { + it('should detect duplicate tags', () => { + const pageTags1 = { + [TITLE]: 'Duplicate Title', + }; + const pageTags2 = { + [TITLE]: 'Duplicate Title', + }; + + seoChecks.checkForUniqueness('https://page1.com', pageTags1); + seoChecks.checkForUniqueness('https://page2.com', pageTags2); + + expect(seoChecks.detectedTags[TITLE]).to.have.lengthOf(1); + expect(seoChecks.detectedTags[TITLE][0]).to.deep.equal({ + pageUrl: 'https://page2.com', + tagName: TITLE, + tagContent: 'Duplicate Title', + seoImpact: HIGH, + seoOpportunityText: 'The title tag on this page is identical to the one on https://page1.com. It\'s recommended to have unique title tags for each page.', + }); + }); + }); + }); + describe('handler method', () => { + let message; + let context; + let logStub; + let dataAccessStub; + let s3ClientStub; + + beforeEach(() => { + sinon.restore(); + message = { type: 'seo', url: 'site-id' }; + logStub = { info: sinon.stub(), error: sinon.stub(), warn: sinon.stub() }; + dataAccessStub = { + getConfiguration: sinon.stub(), + getTopPagesForSite: sinon.stub(), + addAudit: sinon.stub(), + retrieveSiteBySiteId: sinon.stub(), + getSiteByID: sinon.stub().resolves({ isLive: sinon.stub().returns(true) }), + }; + s3ClientStub = { + send: sinon.stub(), + getObject: sinon.stub(), + }; + + context = { + log: logStub, + dataAccess: dataAccessStub, + s3Client: s3ClientStub, + env: { S3_BUCKET_NAME: 'test-bucket' }, + }; + }); + + it('should return notFound if site is not found', async () => { + dataAccessStub.getSiteByID.resolves(null); + + const result = await auditMetaTags(message, context); + expect(JSON.stringify(result)).to.equal(JSON.stringify(notFound('Site not found'))); + expect(logStub.info.calledOnce).to.be.true; + }); + + it('should return ok if site is not live', async () => { + dataAccessStub.getSiteByID.resolves({ isLive: sinon.stub().returns(false) }); + + const result = await auditMetaTags(message, context); + expect(JSON.stringify(result)).to.equal(JSON.stringify(ok())); + expect(logStub.info.calledTwice).to.be.true; + }); + + it('should return ok if audit type is disabled for site', async () => { + dataAccessStub.getConfiguration.resolves({ + isHandlerEnabledForSite: sinon.stub().returns(false), + }); + const result = await auditMetaTags(message, context); + expect(JSON.stringify(result)).to.equal(JSON.stringify(ok())); + expect(logStub.info.calledTwice).to.be.true; + }); + + it('should return notFound if extracted tags are not available', async () => { + dataAccessStub.getConfiguration.resolves({ + isHandlerEnabledForSite: sinon.stub().returns(true), + }); + s3ClientStub.send.returns([]); + + const result = await auditMetaTags(message, context); + expect(JSON.stringify(result)).to.equal(JSON.stringify(notFound('Site tags data not available'))); + expect(logStub.error.calledOnce).to.be.true; + }); + + it('should process site tags and perform SEO checks', async () => { + const site = { isLive: sinon.stub().returns(true), getId: sinon.stub().returns('site-id') }; + const topPages = [{ getURL: 'http://example.com/blog/page1', getTopKeyword: sinon.stub().returns('page') }, + { getURL: 'http://example.com/blog/page2', getTopKeyword: sinon.stub().returns('Test') }]; + + dataAccessStub.getSiteByID.resolves(site); + dataAccessStub.getConfiguration.resolves({ + isHandlerEnabledForSite: sinon.stub().returns(true), + }); + dataAccessStub.getTopPagesForSite.resolves(topPages); + + s3ClientStub.send + .withArgs(sinon.match.instanceOf(ListObjectsV2Command).and(sinon.match.has('input', { + Bucket: 'test-bucket', + Prefix: 'scrapes/site-id/', + MaxKeys: 1000, + }))) + .resolves({ + Contents: [ + { Key: 'scrapes/site-id/blog/page1.json' }, + { Key: 'scrapes/site-id/blog/page2.json' }, + ], + }); + + s3ClientStub.getObject.withArgs({ + Bucket: 'test-bucket', + Key: 'scrapes/site-id/blog/page1.json', + }).returns({ + promise: sinon.stub().resolves({ + Body: { + rawBody: 'Test Page', + }, + }), + }); + s3ClientStub.getObject.withArgs({ + Bucket: 'test-bucket', + Key: 'scrapes/site-id/blog/page2.json', + }).returns({ + promise: sinon.stub().resolves({ + Body: { + rawBody: 'Test Page

This is a dummy H1 that is overly length from SEO perspective

', + }, + }), + }); + const addAuditStub = sinon.stub().resolves(); + dataAccessStub.addAudit = addAuditStub; + + const result = await auditMetaTags(message, context); + + expect(JSON.stringify(result)).to.equal(JSON.stringify(noContent())); + expect(addAuditStub.calledWithMatch({ + title: [ + { + pageUrl: '/blog/page1', + tagName: 'title', + tagContent: 'Test Page', + seoImpact: 'Moderate', + seoOpportunityText: 'The title tag on this page has a length of 9 characters, which is below the recommended length of 40-60 characters.', + }, + { + pageUrl: '/blog/page2', + tagName: 'title', + tagContent: 'Test Page', + seoImpact: 'Moderate', + seoOpportunityText: 'The title tag on this page has a length of 9 characters, which is below the recommended length of 40-60 characters.', + }, + { + pageUrl: '/blog/page2', + tagName: 'title', + tagContent: 'Test Page', + seoImpact: 'High', + seoOpportunityText: "The title tag on this page is identical to the one on /blog/page1. It's recommended to have unique title tags for each page.", + }, + ], + description: [ + { + pageUrl: '/blog/page1', + tagName: 'description', + tagContent: '', + seoImpact: 'Moderate', + seoOpportunityText: 'The description tag on this page has a length of 0 characters, which is below the recommended length of 140-160 characters.', + }, + { + pageUrl: '/blog/page1', + tagName: 'description', + tagContent: '', + seoImpact: 'High', + seoOpportunityText: "The description tag on this page is missing the page's top keyword 'page'. It's recommended to include the primary keyword in the description tag.", + }, + { + pageUrl: '/blog/page2', + tagName: 'description', + tagContent: '', + seoImpact: 'High', + seoOpportunityText: "The description tag on this page is missing. It's recommended to have a description tag on each page.", + }, + { + pageUrl: '/blog/page2', + tagName: 'description', + seoImpact: 'High', + seoOpportunityText: "The description tag on this page is missing the page's top keyword 'test'. It's recommended to include the primary keyword in the description tag.", + }, + ], + h1: [ + { + pageUrl: '/blog/page1', + tagName: 'h1', + tagContent: '', + seoImpact: 'High', + seoOpportunityText: "The h1 tag on this page is missing. It's recommended to have a h1 tag on each page.", + }, + { + pageUrl: '/blog/page1', + tagName: 'h1', + seoImpact: 'High', + seoOpportunityText: "The h1 tag on this page is missing the page's top keyword 'page'. It's recommended to include the primary keyword in the h1 tag.", + }, + { + pageUrl: '/blog/page2', + tagName: 'h1', + tagContent: 'This is a dummy H1 that is overly length from SEO perspective', + seoImpact: 'Moderate', + seoOpportunityText: 'The h1 tag on this page has a length of 61 characters, which is above the recommended length of 60 characters.', + }, + { + pageUrl: '/blog/page2', + tagName: 'h1', + tagContent: 'This is a dummy H1 that is overly length from SEO perspective', + seoImpact: 'High', + seoOpportunityText: "The h1 tag on this page is missing the page's top keyword 'test'. It's recommended to include the primary keyword in the h1 tag.", + }, + ], + })); + expect(addAuditStub.calledOnce).to.be.true; + expect(logStub.info.calledTwice).to.be.true; + }); + + it('should handle errors and return internalServerError', async () => { + dataAccessStub.getSiteByID.rejects(new Error('Some error')); + + const result = await auditMetaTags(message, context); + expect(JSON.stringify(result)).to.equal(JSON.stringify(internalServerError('Internal server error: Some error'))); + expect(logStub.error.calledOnce).to.be.true; + }); + + it('should handle gracefully if S3 object has no rawbody', async () => { + const site = { isLive: sinon.stub().returns(true), getId: sinon.stub().returns('site-id') }; + const topPages = [{ getURL: 'http://example.com/blog/page1', getTopKeyword: sinon.stub().returns('page') }]; + + dataAccessStub.getSiteByID.resolves(site); + dataAccessStub.getConfiguration.resolves({ + isHandlerEnabledForSite: sinon.stub().returns(true), + }); + dataAccessStub.getTopPagesForSite.resolves(topPages); + + s3ClientStub.send + .withArgs(sinon.match.instanceOf(ListObjectsV2Command).and(sinon.match.has('input', { + Bucket: 'test-bucket', + Prefix: 'scrapes/site-id/', + MaxKeys: 1000, + }))) + .resolves({ + Contents: [ + { Key: 'scrapes/site-id/blog/page1.json' }, + ], + }); + + s3ClientStub.getObject.withArgs({ + Bucket: 'test-bucket', + Key: 'scrapes/site-id/blog/page1.json', + }).returns({ + promise: sinon.stub().resolves({ + Body: { + }, + }), + }); + const addAuditStub = sinon.stub().resolves(); + dataAccessStub.addAudit = addAuditStub; + + const result = await auditMetaTags(message, context); + + expect(JSON.stringify(result)).to.equal(JSON.stringify(notFound('Site tags data not available'))); + expect(addAuditStub.calledOnce).to.be.false; + expect(logStub.error.calledTwice).to.be.true; + }); + + it('should handle gracefully if S3 object is not a html', async () => { + const site = { isLive: sinon.stub().returns(true), getId: sinon.stub().returns('site-id') }; + const topPages = [{ getURL: 'http://example.com/blog/page1', getTopKeyword: sinon.stub().returns('page') }, + { getURL: 'http://example.com/blog/page2', getTopKeyword: sinon.stub().returns('Test') }]; + + dataAccessStub.getSiteByID.resolves(site); + dataAccessStub.getConfiguration.resolves({ + isHandlerEnabledForSite: sinon.stub().returns(true), + }); + dataAccessStub.getTopPagesForSite.resolves(topPages); + + s3ClientStub.send + .withArgs(sinon.match.instanceOf(ListObjectsV2Command).and(sinon.match.has('input', { + Bucket: 'test-bucket', + Prefix: 'scrapes/site-id/', + MaxKeys: 1000, + }))) + .resolves({ + Contents: [ + { Key: 'page1.json' }, + ], + }); + + s3ClientStub.getObject.returns({ + promise: sinon.stub().resolves({ + Body: { + rawBody: 5, + }, + }), + }); + const result = await auditMetaTags(message, context); + + expect(JSON.stringify(result)).to.equal(JSON.stringify(notFound('Site tags data not available'))); + expect(logStub.error.calledTwice).to.be.true; + }); + }); +}); diff --git a/test/support/s3-client.test.js b/test/support/s3-client.test.js new file mode 100644 index 00000000..7c657e1d --- /dev/null +++ b/test/support/s3-client.test.js @@ -0,0 +1,75 @@ +/* + * Copyright 2024 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 chaiAsPromised from 'chai-as-promised'; +import { S3Client } from '@aws-sdk/client-s3'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import s3Client from '../../src/support/s3-client.js'; + +use(sinonChai); +use(chaiAsPromised); + +describe('s3Client middleware', () => { + let mockFn; + let request; + let context; + + beforeEach(() => { + mockFn = sinon.stub().resolves({ statusCode: 200, body: 'Success' }); + request = {}; + context = {}; + }); + + afterEach(() => { + sinon.restore(); + }); + + it('should add an S3Client instance to context if not already present', async () => { + const wrappedFunction = s3Client(mockFn); + + await wrappedFunction(request, context); + + expect(context.s3Client).to.be.an.instanceof(S3Client); + expect(mockFn).to.have.been.calledOnceWith(request, context); + }); + + it('should not overwrite existing S3Client instance in context', async () => { + const existingS3Client = new S3Client(); + context.s3Client = existingS3Client; + + const wrappedFunction = s3Client(mockFn); + + await wrappedFunction(request, context); + + expect(context.s3Client).to.equal(existingS3Client); + expect(mockFn).to.have.been.calledOnceWith(request, context); + }); + + it('should return the response from the passed function', async () => { + const wrappedFunction = s3Client(mockFn); + + const response = await wrappedFunction(request, context); + + expect(response).to.deep.equal({ statusCode: 200, body: 'Success' }); + }); + + it('should throw an error if the passed function throws', async () => { + mockFn.rejects(new Error('Some error')); + const wrappedFunction = s3Client(mockFn); + + await expect(wrappedFunction(request, context)).to.be.rejectedWith('Some error'); + }); +}); diff --git a/test/utils/s3-utils.test.js b/test/utils/s3-utils.test.js new file mode 100644 index 00000000..469ef91a --- /dev/null +++ b/test/utils/s3-utils.test.js @@ -0,0 +1,112 @@ +/* + * Copyright 2024 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 chaiAsPromised from 'chai-as-promised'; +import { getObjectKeysUsingPrefix, getObjectFromKey } from '../../src/utils/s3-utils.js'; + +use(chaiAsPromised); + +describe('S3 Utility Functions', () => { + const logMock = { + error: () => {}, + }; + + describe('getObjectKeysUsingPrefix', () => { + it('should return a list of object keys when S3 returns data', async () => { + const bucketName = 'test-bucket'; + const prefix = 'test-prefix'; + const expectedKeys = ['file1.txt', 'file2.txt']; + + const s3ClientMock = { + send: async () => ({ + Contents: expectedKeys.map((key) => ({ Key: key })), + }), + }; + + const keys = await getObjectKeysUsingPrefix(s3ClientMock, bucketName, prefix, logMock); + expect(keys).to.deep.equal(expectedKeys); + }); + + it('should return an empty list when S3 returns no data', async () => { + const bucketName = 'test-bucket'; + const prefix = 'test-prefix'; + + const s3ClientMock = { + send: async () => ({ Contents: [] }), + }; + + const keys = await getObjectKeysUsingPrefix(s3ClientMock, bucketName, prefix, logMock); + expect(keys).to.deep.equal([]); + }); + + it('should log an error and return an empty list when S3 call fails', async () => { + const bucketName = 'test-bucket'; + const prefix = 'test-prefix'; + + const s3ClientMock = { + send: async () => { + throw new Error('S3 error'); + }, + }; + const logMock2 = { + error: (msg, err) => { + expect(msg).to.equal(`Error while fetching S3 object keys using bucket ${bucketName} and prefix ${prefix}`); + expect(err.message).to.equal('S3 error'); + }, + }; + + const keys = await getObjectKeysUsingPrefix(s3ClientMock, bucketName, prefix, logMock2); + expect(keys).to.deep.equal([]); + }); + }); + + describe('getObjectFromKey', () => { + it('should return the S3 object when getObject succeeds', async () => { + const bucketName = 'test-bucket'; + const key = 'test-key'; + const expectedObject = { Body: 'file contents' }; + + const s3ClientMock = { + getObject: () => ({ + promise: async () => expectedObject, + }), + }; + + const result = await getObjectFromKey(s3ClientMock, bucketName, key, logMock); + expect(result).to.deep.equal(expectedObject); + }); + + it('should return null and log an error when getObject fails', async () => { + const bucketName = 'test-bucket'; + const key = 'test-key'; + + const s3ClientMock = { + getObject: () => ({ + promise: async () => { + throw new Error('S3 getObject error'); + }, + }), + }; + const logMock2 = { + error: (msg, err) => { + expect(msg).to.equal(`Error while fetching S3 object from bucket ${bucketName} using key ${key}`); + expect(err.message).to.equal('S3 getObject error'); + }, + }; + + const result = await getObjectFromKey(s3ClientMock, bucketName, key, logMock2); + expect(result).to.be.null; + }); + }); +});