diff --git a/package-lock.json b/package-lock.json index 5a07c4a..60e2c00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,5 @@ { "name": "eps-cdk-utils", - "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { @@ -284,45 +283,45 @@ } }, "node_modules/@aws-sdk/client-cloudformation": { - "version": "3.975.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudformation/-/client-cloudformation-3.975.0.tgz", - "integrity": "sha512-xPFcBlpTDuTod9zAAnEsbezFOOqMfQfcd9RCl1LL4Q+qjmazuBSqlnzGE3Djr8Ax/PTV0TR3H2LuepO/ygXwsA==", + "version": "3.978.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudformation/-/client-cloudformation-3.978.0.tgz", + "integrity": "sha512-wEhfmWHvhPbRDE8aUCBxiKFJ/71jLVY2RkKGamVtgotbC/6VYaRN3WlRgZX/BXgkDHpGxwCGdWV9GAiJ3Ewcgw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/credential-provider-node": "^3.972.1", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.2", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", + "@aws-sdk/core": "^3.973.4", + "@aws-sdk/credential-provider-node": "^3.972.2", + "@aws-sdk/middleware-host-header": "^3.972.2", + "@aws-sdk/middleware-logger": "^3.972.2", + "@aws-sdk/middleware-recursion-detection": "^3.972.2", + "@aws-sdk/middleware-user-agent": "^3.972.4", + "@aws-sdk/region-config-resolver": "^3.972.2", + "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", + "@aws-sdk/util-user-agent-browser": "^3.972.2", + "@aws-sdk/util-user-agent-node": "^3.972.2", "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.1", + "@smithy/core": "^3.22.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.11", - "@smithy/middleware-retry": "^4.4.27", + "@smithy/middleware-endpoint": "^4.4.12", + "@smithy/middleware-retry": "^4.4.29", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", + "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.26", - "@smithy/util-defaults-mode-node": "^4.2.29", + "@smithy/util-defaults-mode-browser": "^4.3.28", + "@smithy/util-defaults-mode-node": "^4.2.31", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -335,26 +334,26 @@ } }, "node_modules/@aws-sdk/client-lambda": { - "version": "3.975.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.975.0.tgz", - "integrity": "sha512-55+/Ku+fd1HY3TVKep/4GqgiR65p09/Xfgebknx8mqy18lTohO/8VFn7AusoZGOVypfRv3yVuYktCvINBBrkKw==", + "version": "3.978.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.978.0.tgz", + "integrity": "sha512-R3XJh7r0m7iimku6IgDJ6mS/s2CUJVA1oicIf9/YVudEVkU3drOV3MZpzBHJwmBvBXVM8jDOA7qkfDmgVHHJSA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/credential-provider-node": "^3.972.1", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.2", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", + "@aws-sdk/core": "^3.973.4", + "@aws-sdk/credential-provider-node": "^3.972.2", + "@aws-sdk/middleware-host-header": "^3.972.2", + "@aws-sdk/middleware-logger": "^3.972.2", + "@aws-sdk/middleware-recursion-detection": "^3.972.2", + "@aws-sdk/middleware-user-agent": "^3.972.4", + "@aws-sdk/region-config-resolver": "^3.972.2", + "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", + "@aws-sdk/util-user-agent-browser": "^3.972.2", + "@aws-sdk/util-user-agent-node": "^3.972.2", "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.1", + "@smithy/core": "^3.22.0", "@smithy/eventstream-serde-browser": "^4.2.8", "@smithy/eventstream-serde-config-resolver": "^4.3.8", "@smithy/eventstream-serde-node": "^4.2.8", @@ -362,21 +361,21 @@ "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.11", - "@smithy/middleware-retry": "^4.4.27", + "@smithy/middleware-endpoint": "^4.4.12", + "@smithy/middleware-retry": "^4.4.29", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", + "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.26", - "@smithy/util-defaults-mode-node": "^4.2.29", + "@smithy/util-defaults-mode-browser": "^4.3.28", + "@smithy/util-defaults-mode-node": "^4.2.31", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -389,35 +388,87 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/client-route-53": { + "version": "3.978.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-route-53/-/client-route-53-3.978.0.tgz", + "integrity": "sha512-sXay1jhv4JiSjXUem1HFGisI0HMGw3J1+lQYp7IUNI7MOgvBcy9pyqO7MmFsZl2R9FrJqZhUoPSpLOai2Dj/IQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.4", + "@aws-sdk/credential-provider-node": "^3.972.2", + "@aws-sdk/middleware-host-header": "^3.972.2", + "@aws-sdk/middleware-logger": "^3.972.2", + "@aws-sdk/middleware-recursion-detection": "^3.972.2", + "@aws-sdk/middleware-sdk-route53": "^3.972.2", + "@aws-sdk/middleware-user-agent": "^3.972.4", + "@aws-sdk/region-config-resolver": "^3.972.2", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.972.0", + "@aws-sdk/util-user-agent-browser": "^3.972.2", + "@aws-sdk/util-user-agent-node": "^3.972.2", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.22.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.12", + "@smithy/middleware-retry": "^4.4.29", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.28", + "@smithy/util-defaults-mode-node": "^4.2.31", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "@smithy/util-waiter": "^4.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/client-s3": { - "version": "3.975.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.975.0.tgz", - "integrity": "sha512-aF1M/iMD29BPcpxjqoym0YFa4WR9Xie1/IhVumwOGH6TB45DaqYO7vLwantDBcYNRn/cZH6DFHksO7RmwTFBhw==", + "version": "3.978.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.978.0.tgz", + "integrity": "sha512-2chs05VbfgRNb5ZEYIwooeHCaL+DjwvrW3ElkslI71ltEqVNdeWvB7hbkLWPPKazV3kjY3H90pLDY8mMqsET+A==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/credential-provider-node": "^3.972.1", - "@aws-sdk/middleware-bucket-endpoint": "^3.972.1", - "@aws-sdk/middleware-expect-continue": "^3.972.1", - "@aws-sdk/middleware-flexible-checksums": "^3.972.1", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-location-constraint": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-sdk-s3": "^3.972.2", - "@aws-sdk/middleware-ssec": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.2", - "@aws-sdk/region-config-resolver": "^3.972.1", + "@aws-sdk/core": "^3.973.4", + "@aws-sdk/credential-provider-node": "^3.972.2", + "@aws-sdk/middleware-bucket-endpoint": "^3.972.2", + "@aws-sdk/middleware-expect-continue": "^3.972.2", + "@aws-sdk/middleware-flexible-checksums": "^3.972.2", + "@aws-sdk/middleware-host-header": "^3.972.2", + "@aws-sdk/middleware-location-constraint": "^3.972.2", + "@aws-sdk/middleware-logger": "^3.972.2", + "@aws-sdk/middleware-recursion-detection": "^3.972.2", + "@aws-sdk/middleware-sdk-s3": "^3.972.4", + "@aws-sdk/middleware-ssec": "^3.972.2", + "@aws-sdk/middleware-user-agent": "^3.972.4", + "@aws-sdk/region-config-resolver": "^3.972.2", "@aws-sdk/signature-v4-multi-region": "3.972.0", - "@aws-sdk/types": "^3.973.0", + "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", + "@aws-sdk/util-user-agent-browser": "^3.972.2", + "@aws-sdk/util-user-agent-node": "^3.972.2", "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.1", + "@smithy/core": "^3.22.0", "@smithy/eventstream-serde-browser": "^4.2.8", "@smithy/eventstream-serde-config-resolver": "^4.3.8", "@smithy/eventstream-serde-node": "^4.2.8", @@ -428,21 +479,21 @@ "@smithy/invalid-dependency": "^4.2.8", "@smithy/md5-js": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.11", - "@smithy/middleware-retry": "^4.4.27", + "@smithy/middleware-endpoint": "^4.4.12", + "@smithy/middleware-retry": "^4.4.29", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", + "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.26", - "@smithy/util-defaults-mode-node": "^4.2.29", + "@smithy/util-defaults-mode-browser": "^4.3.28", + "@smithy/util-defaults-mode-node": "^4.2.31", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -505,19 +556,19 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.973.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.3.tgz", - "integrity": "sha512-ZbM2Xy8ytAcfnNpkBltr6Qdw36W/4NW5nZdZieCuTfacoBFpi/NYiwb8U05KNJvLKeZnrV9Vi696i+r2DQFORg==", + "version": "3.973.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.4.tgz", + "integrity": "sha512-8Rk+kPP74YiR47x54bxYlKZswsaSh0a4XvvRUMLvyS/koNawhsGu/+qSZxREqUeTO+GkKpFvSQIsAZR+deUP+g==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.2", - "@smithy/core": "^3.21.1", + "@smithy/core": "^3.22.0", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", + "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", @@ -558,18 +609,18 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.3.tgz", - "integrity": "sha512-IbBGWhaxiEl64fznwh5PDEB0N7YJEAvK5b6nRtPVUKdKAHlOPgo6B9XB8mqWDs8Ct0oF/E34ZLiq2U0L5xDkrg==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.4.tgz", + "integrity": "sha512-OC7F3ipXV12QfDEWybQGHLzoeHBlAdx/nLzPfHP0Wsabu3JBffu5nlzSaJNf7to9HGtOW8Bpu8NX0ugmDrCbtw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.2", + "@aws-sdk/core": "^3.973.4", "@aws-sdk/types": "^3.973.1", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/node-http-handler": "^4.4.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", + "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/util-stream": "^4.5.10", "tslib": "^2.6.2" @@ -623,13 +674,13 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.2.tgz", - "integrity": "sha512-Lz1J5IZdTjLYTVIcDP5DVDgi1xlgsF3p1cnvmbfKbjCRhQpftN2e2J4NFfRRvPD54W9+bZ8l5VipPXtTYK7aEg==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.3.tgz", + "integrity": "sha512-iu+JwWHM7tHowKqE+8wNmI3sM6mPEiI9Egscz2BEV7adyKmV95oR9tBO4VIOl72FGDi7X9mXg19VtqIpSkEEsA==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.2", - "@aws-sdk/credential-provider-http": "^3.972.3", + "@aws-sdk/credential-provider-http": "^3.972.4", "@aws-sdk/credential-provider-ini": "^3.972.2", "@aws-sdk/credential-provider-process": "^3.972.2", "@aws-sdk/credential-provider-sso": "^3.972.2", @@ -816,20 +867,34 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/middleware-sdk-route53": { + "version": "3.972.2", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-route53/-/middleware-sdk-route53-3.972.2.tgz", + "integrity": "sha512-qq8DKlxMvBjBXGXxH0eNvmdB4PtUo+rXV2LvD334rIav0FnUbHgJGaD3+ImDUjhlhgm5GazUMvS3Qz/7hnR9/w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.3.tgz", - "integrity": "sha512-ZVtakKpQ7vI9l7tE2SJjQgoPYv2f/Bw/HMip5wBigsQBDvVbN300h+6nPnm0gnEQwIGGG0yJF3XCvr1/4pZW9A==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.4.tgz", + "integrity": "sha512-lradfn72Td7lswhZKi86VKRNkDtmQR7bq9shX1kaPK1itjThxfcx7ogXSvMm/0cuqoYGic8UUXQOaK4kpU933g==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.2", + "@aws-sdk/core": "^3.973.4", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-arn-parser": "^3.972.2", - "@smithy/core": "^3.21.1", + "@smithy/core": "^3.22.0", "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", + "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-middleware": "^4.2.8", @@ -856,15 +921,15 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.3.tgz", - "integrity": "sha512-zq6aTiO/BiAIOA8EH8nB+wYvvnZ14Md9Gomm5DDhParshVEVglAyNPO5ADK4ZXFQbftIoO+Vgcvf4gewW/+iYQ==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.4.tgz", + "integrity": "sha512-6sU8jrSJvY/lqSnU6IYsa8SrCKwOZ4Enl6O4xVJo8RCq9Bdr5Giuw2eUaJAk9GPcpr4OFcmSFv3JOLhpKGeRZA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.2", + "@aws-sdk/core": "^3.973.4", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.972.0", - "@smithy/core": "^3.21.1", + "@smithy/core": "^3.22.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" @@ -1116,15 +1181,15 @@ } }, "node_modules/@aws-sdk/util-locate-window": { - "version": "3.893.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.893.0.tgz", - "integrity": "sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==", + "version": "3.965.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.4.tgz", + "integrity": "sha512-H1onv5SkgPBK2P6JR2MjGgbOnttoNzSPIRoeZTNPZYyaplwGg50zS3amXvXqF0/qfXpWEC9rLWU564QTB9bSog==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/util-user-agent-browser": { @@ -1178,22 +1243,22 @@ } }, "node_modules/@aws/lambda-invoke-store": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.2.tgz", - "integrity": "sha512-C0NBLsIqzDIae8HFw9YIrIBsbc0xTiOtt7fAukGPnqQ/+zZNaq+4jhuccltK0QuWHBnNm/a6kLIRA6GFiM10eg==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz", + "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==", "license": "Apache-2.0", "engines": { "node": ">=18.0.0" } }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -1209,9 +1274,9 @@ "license": "MIT" }, "node_modules/@babel/compat-data": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", - "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", + "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", "dev": true, "license": "MIT", "engines": { @@ -1219,22 +1284,22 @@ } }, "node_modules/@babel/core": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", - "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", + "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5", + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -1261,14 +1326,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", - "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", + "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -1278,13 +1343,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.2", + "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", @@ -1315,29 +1380,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1347,9 +1412,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "dev": true, "license": "MIT", "engines": { @@ -1387,27 +1452,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.5" + "@babel/types": "^7.28.6" }, "bin": { "parser": "bin/babel-parser.js" @@ -1472,13 +1537,13 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1514,13 +1579,13 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1640,13 +1705,13 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1656,42 +1721,42 @@ } }, "node_modules/@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", - "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", + "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6", "debug": "^4.3.1" }, "engines": { @@ -1699,9 +1764,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", "dev": true, "license": "MIT", "dependencies": { @@ -1747,9 +1812,9 @@ } }, "node_modules/@emnapi/core": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", - "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", + "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", "dev": true, "license": "MIT", "optional": true, @@ -1759,9 +1824,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", - "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", "dev": true, "license": "MIT", "optional": true, @@ -2291,9 +2356,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2303,7 +2368,7 @@ "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", + "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, @@ -3017,9 +3082,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.0.tgz", - "integrity": "sha512-tPgXB6cDTndIe1ah7u6amCI1T0SsnlOuKgg10Xh3uizJk4e5M1JGaUMk7J4ciuAUcFpbOiNhm2XIjP9ON0dUqA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", "cpu": [ "arm" ], @@ -3031,9 +3096,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.0.tgz", - "integrity": "sha512-sa4LyseLLXr1onr97StkU1Nb7fWcg6niokTwEVNOO7awaKaoRObQ54+V/hrF/BP1noMEaaAW6Fg2d/CfLiq3Mg==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", "cpu": [ "arm64" ], @@ -3045,9 +3110,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.0.tgz", - "integrity": "sha512-/NNIj9A7yLjKdmkx5dC2XQ9DmjIECpGpwHoGmA5E1AhU0fuICSqSWScPhN1yLCkEdkCwJIDu2xIeLPs60MNIVg==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", "cpu": [ "arm64" ], @@ -3059,9 +3124,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.0.tgz", - "integrity": "sha512-xoh8abqgPrPYPr7pTYipqnUi1V3em56JzE/HgDgitTqZBZ3yKCWI+7KUkceM6tNweyUKYru1UMi7FC060RyKwA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", "cpu": [ "x64" ], @@ -3073,9 +3138,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.0.tgz", - "integrity": "sha512-PCkMh7fNahWSbA0OTUQ2OpYHpjZZr0hPr8lId8twD7a7SeWrvT3xJVyza+dQwXSSq4yEQTMoXgNOfMCsn8584g==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", "cpu": [ "arm64" ], @@ -3087,9 +3152,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.0.tgz", - "integrity": "sha512-1j3stGx+qbhXql4OCDZhnK7b01s6rBKNybfsX+TNrEe9JNq4DLi1yGiR1xW+nL+FNVvI4D02PUnl6gJ/2y6WJA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", "cpu": [ "x64" ], @@ -3101,9 +3166,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.0.tgz", - "integrity": "sha512-eyrr5W08Ms9uM0mLcKfM/Uzx7hjhz2bcjv8P2uynfj0yU8GGPdz8iYrBPhiLOZqahoAMB8ZiolRZPbbU2MAi6Q==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", "cpu": [ "arm" ], @@ -3115,9 +3180,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.0.tgz", - "integrity": "sha512-Xds90ITXJCNyX9pDhqf85MKWUI4lqjiPAipJ8OLp8xqI2Ehk+TCVhF9rvOoN8xTbcafow3QOThkNnrM33uCFQA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", "cpu": [ "arm" ], @@ -3129,9 +3194,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.0.tgz", - "integrity": "sha512-Xws2KA4CLvZmXjy46SQaXSejuKPhwVdaNinldoYfqruZBaJHqVo6hnRa8SDo9z7PBW5x84SH64+izmldCgbezw==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", "cpu": [ "arm64" ], @@ -3143,9 +3208,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.0.tgz", - "integrity": "sha512-hrKXKbX5FdaRJj7lTMusmvKbhMJSGWJ+w++4KmjiDhpTgNlhYobMvKfDoIWecy4O60K6yA4SnztGuNTQF+Lplw==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", "cpu": [ "arm64" ], @@ -3157,9 +3222,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.0.tgz", - "integrity": "sha512-6A+nccfSDGKsPm00d3xKcrsBcbqzCTAukjwWK6rbuAnB2bHaL3r9720HBVZ/no7+FhZLz/U3GwwZZEh6tOSI8Q==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", "cpu": [ "loong64" ], @@ -3171,9 +3236,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.0.tgz", - "integrity": "sha512-4P1VyYUe6XAJtQH1Hh99THxr0GKMMwIXsRNOceLrJnaHTDgk1FTcTimDgneRJPvB3LqDQxUmroBclQ1S0cIJwQ==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", "cpu": [ "loong64" ], @@ -3185,9 +3250,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.0.tgz", - "integrity": "sha512-8Vv6pLuIZCMcgXre6c3nOPhE0gjz1+nZP6T+hwWjr7sVH8k0jRkH+XnfjjOTglyMBdSKBPPz54/y1gToSKwrSQ==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", "cpu": [ "ppc64" ], @@ -3199,9 +3264,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.0.tgz", - "integrity": "sha512-r1te1M0Sm2TBVD/RxBPC6RZVwNqUTwJTA7w+C/IW5v9Ssu6xmxWEi+iJQlpBhtUiT1raJ5b48pI8tBvEjEFnFA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", "cpu": [ "ppc64" ], @@ -3213,9 +3278,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.0.tgz", - "integrity": "sha512-say0uMU/RaPm3CDQLxUUTF2oNWL8ysvHkAjcCzV2znxBr23kFfaxocS9qJm+NdkRhF8wtdEEAJuYcLPhSPbjuQ==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", "cpu": [ "riscv64" ], @@ -3227,9 +3292,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.0.tgz", - "integrity": "sha512-/MU7/HizQGsnBREtRpcSbSV1zfkoxSTR7wLsRmBPQ8FwUj5sykrP1MyJTvsxP5KBq9SyE6kH8UQQQwa0ASeoQQ==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", "cpu": [ "riscv64" ], @@ -3241,9 +3306,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.0.tgz", - "integrity": "sha512-Q9eh+gUGILIHEaJf66aF6a414jQbDnn29zeu0eX3dHMuysnhTvsUvZTCAyZ6tJhUjnvzBKE4FtuaYxutxRZpOg==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", "cpu": [ "s390x" ], @@ -3255,9 +3320,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.0.tgz", - "integrity": "sha512-OR5p5yG5OKSxHReWmwvM0P+VTPMwoBS45PXTMYaskKQqybkS3Kmugq1W+YbNWArF8/s7jQScgzXUhArzEQ7x0A==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", "cpu": [ "x64" ], @@ -3269,9 +3334,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.0.tgz", - "integrity": "sha512-XeatKzo4lHDsVEbm1XDHZlhYZZSQYym6dg2X/Ko0kSFgio+KXLsxwJQprnR48GvdIKDOpqWqssC3iBCjoMcMpw==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", "cpu": [ "x64" ], @@ -3283,9 +3348,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.0.tgz", - "integrity": "sha512-Lu71y78F5qOfYmubYLHPcJm74GZLU6UJ4THkf/a1K7Tz2ycwC2VUbsqbJAXaR6Bx70SRdlVrt2+n5l7F0agTUw==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", "cpu": [ "x64" ], @@ -3297,9 +3362,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.0.tgz", - "integrity": "sha512-v5xwKDWcu7qhAEcsUubiav7r+48Uk/ENWdr82MBZZRIm7zThSxCIVDfb3ZeRRq9yqk+oIzMdDo6fCcA5DHfMyA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", "cpu": [ "arm64" ], @@ -3311,9 +3376,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.0.tgz", - "integrity": "sha512-XnaaaSMGSI6Wk8F4KK3QP7GfuuhjGchElsVerCplUuxRIzdvZ7hRBpLR0omCmw+kI2RFJB80nenhOoGXlJ5TfQ==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", "cpu": [ "arm64" ], @@ -3325,9 +3390,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.0.tgz", - "integrity": "sha512-3K1lP+3BXY4t4VihLw5MEg6IZD3ojSYzqzBG571W3kNQe4G4CcFpSUQVgurYgib5d+YaCjeFow8QivWp8vuSvA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", "cpu": [ "ia32" ], @@ -3339,9 +3404,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.0.tgz", - "integrity": "sha512-MDk610P/vJGc5L5ImE4k5s+GZT3en0KoK1MKPXCRgzmksAMk79j4h3k1IerxTNqwDLxsGxStEZVBqG0gIqZqoA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", "cpu": [ "x64" ], @@ -3353,9 +3418,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.0.tgz", - "integrity": "sha512-Zv7v6q6aV+VslnpwzqKAmrk5JdVkLUzok2208ZXGipjb+msxBr/fJPZyeEXiFgH7k62Ak0SLIfxQRZQvTuf7rQ==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", "cpu": [ "x64" ], @@ -3367,9 +3432,9 @@ ] }, "node_modules/@sinclair/typebox": { - "version": "0.34.41", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", - "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", + "version": "0.34.48", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", + "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", "dev": true, "license": "MIT" }, @@ -4276,9 +4341,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.10.tgz", - "integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==", + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.1.0.tgz", + "integrity": "sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==", "dev": true, "license": "MIT", "peer": true, @@ -5136,18 +5201,15 @@ } }, "node_modules/aws-cdk": { - "version": "2.1102.0", - "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1102.0.tgz", - "integrity": "sha512-M/TeLiZERo88hB7FIbT+LyzKQBQuxiGW34oSSfy9+rYTuGwJrO9z6ZZrBNO8/jODT2iR7z0pa3CPsdUVC71oOw==", + "version": "2.1104.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1104.0.tgz", + "integrity": "sha512-TGIK2Mpfqi0BA6Np9aJz0d5HAvTxWd17FrwtXlJuwqdQbR9R/IRqsabF6xRAuhFTz7/YrrHHU9H4VK/Xfnh7Vg==", "license": "Apache-2.0", "bin": { "cdk": "bin/cdk" }, "engines": { "node": ">= 18.0.0" - }, - "optionalDependencies": { - "fsevents": "2.3.2" } }, "node_modules/aws-cdk-lib": { @@ -5603,13 +5665,12 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.31", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.31.tgz", - "integrity": "sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==", + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -5626,7 +5687,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -5646,9 +5706,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", - "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -5667,11 +5727,11 @@ "license": "MIT", "peer": true, "dependencies": { - "baseline-browser-mapping": "^2.8.25", - "caniuse-lite": "^1.0.30001754", - "electron-to-chromium": "^1.5.249", + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", - "update-browserslist-db": "^1.1.4" + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -5731,9 +5791,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001757", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz", - "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==", + "version": "1.0.30001766", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", + "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", "dev": true, "funding": [ { @@ -5799,9 +5859,9 @@ } }, "node_modules/ci-info": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", - "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", "dev": true, "funding": [ { @@ -5815,9 +5875,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.1.1.tgz", - "integrity": "sha512-+CmxIZ/L2vNcEfvNtLdU0ZQ6mbq3FZnwAP2PPTiKP+1QOoKwlKlPgb8UKV0Dds7QVaMnHm+FwSft2VB0s/SLjQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", "dev": true, "license": "MIT" }, @@ -6010,9 +6070,9 @@ } }, "node_modules/dedent": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", - "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", + "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -6063,9 +6123,9 @@ } }, "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -6080,9 +6140,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.259", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.259.tgz", - "integrity": "sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ==", + "version": "1.5.283", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.283.tgz", + "integrity": "sha512-3vifjt1HgrGW/h76UEeny+adYApveS9dH2h3p57JYzBSXJIKUJAvtmIytDKjcSCt9xHfrNCFJ7gts6vkhuq++w==", "dev": true, "license": "ISC" }, @@ -6387,9 +6447,9 @@ } }, "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -6502,9 +6562,9 @@ } }, "node_modules/expect-type": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", - "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -6649,9 +6709,10 @@ "license": "ISC" }, "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -6842,7 +6903,6 @@ "version": "7.0.5", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -7347,21 +7407,6 @@ "fsevents": "^2.3.3" } }, - "node_modules/jest-haste-map/node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/jest-junit": { "version": "16.0.0", "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-16.0.0.tgz", @@ -8136,7 +8181,6 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -8724,7 +8768,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -8922,9 +8965,9 @@ } }, "node_modules/rollup": { - "version": "4.57.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.0.tgz", - "integrity": "sha512-e5lPJi/aui4TO1LpAXIRLySmwXSE8k3b9zoGfd42p67wzxog4WHjiZF3M2uheQih4DGyc25QEV4yRBbpueNiUA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", "dev": true, "license": "MIT", "dependencies": { @@ -8938,31 +8981,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.57.0", - "@rollup/rollup-android-arm64": "4.57.0", - "@rollup/rollup-darwin-arm64": "4.57.0", - "@rollup/rollup-darwin-x64": "4.57.0", - "@rollup/rollup-freebsd-arm64": "4.57.0", - "@rollup/rollup-freebsd-x64": "4.57.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.57.0", - "@rollup/rollup-linux-arm-musleabihf": "4.57.0", - "@rollup/rollup-linux-arm64-gnu": "4.57.0", - "@rollup/rollup-linux-arm64-musl": "4.57.0", - "@rollup/rollup-linux-loong64-gnu": "4.57.0", - "@rollup/rollup-linux-loong64-musl": "4.57.0", - "@rollup/rollup-linux-ppc64-gnu": "4.57.0", - "@rollup/rollup-linux-ppc64-musl": "4.57.0", - "@rollup/rollup-linux-riscv64-gnu": "4.57.0", - "@rollup/rollup-linux-riscv64-musl": "4.57.0", - "@rollup/rollup-linux-s390x-gnu": "4.57.0", - "@rollup/rollup-linux-x64-gnu": "4.57.0", - "@rollup/rollup-linux-x64-musl": "4.57.0", - "@rollup/rollup-openbsd-x64": "4.57.0", - "@rollup/rollup-openharmony-arm64": "4.57.0", - "@rollup/rollup-win32-arm64-msvc": "4.57.0", - "@rollup/rollup-win32-ia32-msvc": "4.57.0", - "@rollup/rollup-win32-x64-gnu": "4.57.0", - "@rollup/rollup-win32-x64-msvc": "4.57.0", + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" } }, @@ -8970,7 +9013,6 @@ "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -9397,9 +9439,9 @@ } }, "node_modules/synckit": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", - "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9823,9 +9865,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", - "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { @@ -9919,7 +9961,6 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -10007,21 +10048,6 @@ } } }, - "node_modules/vite/node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/vite/node_modules/picomatch": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", @@ -10431,6 +10457,7 @@ "license": "MIT", "dependencies": { "@aws-sdk/client-cloudformation": "^3.975.0", + "@aws-sdk/client-route-53": "^3.975.0", "@aws-sdk/client-s3": "^3.975.0", "aws-cdk": "^2.1102.0", "aws-cdk-lib": "^2.236.0", diff --git a/packages/cdkConstructs/package.json b/packages/cdkConstructs/package.json index c4a659e..9032d51 100644 --- a/packages/cdkConstructs/package.json +++ b/packages/cdkConstructs/package.json @@ -21,6 +21,7 @@ "type": "module", "dependencies": { "@aws-sdk/client-cloudformation": "^3.975.0", + "@aws-sdk/client-route-53": "^3.975.0", "@aws-sdk/client-s3": "^3.975.0", "aws-cdk": "^2.1102.0", "aws-cdk-lib": "^2.236.0", diff --git a/packages/cdkConstructs/src/index.ts b/packages/cdkConstructs/src/index.ts index 426bfbe..d9c09c8 100644 --- a/packages/cdkConstructs/src/index.ts +++ b/packages/cdkConstructs/src/index.ts @@ -4,3 +4,4 @@ export * from "./constructs/PythonLambdaFunction.js" export * from "./apps/createApp.js" export * from "./config/index.js" export * from "./utils/helpers.js" +export * from "./stacks/deleteUnusedStacks.js" diff --git a/packages/cdkConstructs/src/stacks/deleteUnusedStacks.ts b/packages/cdkConstructs/src/stacks/deleteUnusedStacks.ts new file mode 100644 index 0000000..8f25a40 --- /dev/null +++ b/packages/cdkConstructs/src/stacks/deleteUnusedStacks.ts @@ -0,0 +1,207 @@ +import { + CloudFormationClient, + DeleteStackCommand, + ListStacksCommand, + StackSummary +} from "@aws-sdk/client-cloudformation" +import {ChangeResourceRecordSetsCommand, ListHostedZonesByNameCommand, Route53Client} from "@aws-sdk/client-route-53" + +const CNAME_HOSTED_ZONE_NAME = "dev.eps.national.nhs.uk." + +/** + * Deletes unused CloudFormation stacks and their associated Route 53 CNAME records. + * + * A stack is considered unused if: + * - it represents a pull request deployment whose PR has been closed; or + * - it is a superseded version of the base stack (and is not within the 24‑hour embargo window). + * + * @param baseStackName - Base name/prefix of the CloudFormation stacks to evaluate. + * @param repoName - GitHub repository name used to look up pull request state. + * @param basePath - Base path of the API used to determine the currently active version. + * @returns A promise that resolves when all eligible stacks have been processed. + */ +export async function deleteUnusedStacks(baseStackName: string, repoName: string, basePath: string): Promise { + const cloudFormationClient = new CloudFormationClient({}) + const route53Client = new Route53Client({}) + const hostedZoneId = await getHostedZoneId(route53Client) + const activeVersions = await getActiveVersions(basePath) + + console.log("checking cloudformation stacks") + + const allStacks = await listAllStacks(cloudFormationClient) + const activeVersionDeployed = allStacks.find(stack => { + const versionInfo = getVersion(stack.StackName!, baseStackName) + if (!versionInfo) { + return false + } + const {version, isSandbox} = versionInfo + return !isSandbox && version === activeVersions.baseEnvVersion?.replaceAll(".", "-") + })?.CreationTime + const keepAllNonPRStacks = isEmbargoed(activeVersionDeployed) + + for (const stack of allStacks) { + if (stack.StackStatus === "DELETE_COMPLETE" || !stack.StackName) { + continue + } + + const stackName = stack.StackName + const deleteSuperseded = !keepAllNonPRStacks && isSupersededVersion(stack, baseStackName, activeVersions) + if (!deleteSuperseded && !(await isClosedPullRequest(stackName, baseStackName, repoName))) { + continue + } + + await cloudFormationClient.send(new DeleteStackCommand({StackName: stackName})) + console.log("** Sleeping for 60 seconds to avoid 429 on delete stack **") + await new Promise((resolve) => setTimeout(resolve, 60_000)) + + const recordName = `${stackName}.${CNAME_HOSTED_ZONE_NAME}` + console.log(`** going to delete CNAME record ${recordName} **`) + await route53Client.send( + new ChangeResourceRecordSetsCommand({ + HostedZoneId: hostedZoneId, + ChangeBatch: { + Changes: [ + { + Action: "DELETE", + ResourceRecordSet: { + Name: recordName, + Type: "CNAME" + } + } + ] + } + }) + ) + + console.log(`CNAME record ${recordName} deleted`) + } +} + +async function listAllStacks(cloudFormationClient: CloudFormationClient): Promise> { + const stacks: Array = [] + let nextToken: string | undefined + + do { + const response = await cloudFormationClient.send(new ListStacksCommand({NextToken: nextToken})) + + if (response.StackSummaries) { + stacks.push(...response.StackSummaries) + } + + nextToken = response.NextToken + } while (nextToken) + + return stacks +} + +async function getHostedZoneId(route53Client: Route53Client): Promise { + const response = await route53Client.send( + new ListHostedZonesByNameCommand({ + DNSName: CNAME_HOSTED_ZONE_NAME + }) + ) + + return response.HostedZones?.[0]?.Id +} + +async function isClosedPullRequest(stackName: string, baseStackName: string, repoName: string): Promise { + const match = new RegExp(String.raw`^${baseStackName}-pr-(?\d+)(-sandbox)?$`).exec(stackName) + if (!match?.groups?.pullRequestId) { + return false + } + + const pullRequestId = match.groups.pullRequestId + console.log(`Checking pull request id ${pullRequestId}`) + const url = `https://api.github.com/repos/NHSDigital/${repoName}/pulls/${pullRequestId}` + + const headers: Record = { + Accept: "application/vnd.github+json", + Authorization: `Bearer ${process.env.GITHUB_TOKEN}` + } + + const response = await fetch(url, {headers}) + if (!response.ok) { + console.log(`Failed to fetch PR ${pullRequestId}: ${response.status} ${await response.text()}`) + return false + } + + const data = (await response.json()) as {state?: string} + if (data.state !== "closed") { + console.log(`not going to delete stack ${stackName} as PR state is ${data.state}`) + return false + } + + console.log(`** going to delete stack ${stackName} as PR state is ${data.state} **`) + return true +} + +type ActiveVersions = { + baseEnvVersion: string + sandboxEnvVersion: string | null +} + +async function getActiveVersions(basePath: string): Promise { + let apigeeEnv = process.env.APIGEE_ENVIRONMENT! + const baseEnvVersion = await getActiveVersion(apigeeEnv, basePath) + let sandboxEnvVersion: string | null = null + try { + if (apigeeEnv === "int") { + sandboxEnvVersion = await getActiveVersion("sandbox", basePath) + } else if (apigeeEnv === "internal-dev") { + sandboxEnvVersion = await getActiveVersion("internal-dev-sandbox", basePath) + } + } catch (error) { + console.log(`Failed to get active version for sandbox environment: ${(error as Error).message}`) + } + return {baseEnvVersion, sandboxEnvVersion} +} + +async function getActiveVersion(apimDomain: string, basePath: string): Promise { + const headers: Record = { + Accept: "application/json", + apikey: `${process.env.APIM_STATUS_API_KEY}` + } + const url = `https://${apimDomain}/${basePath}/_status` + console.log(`Checking live api status endpoint at ${url} for active version`) + const response = await fetch(url, {headers}) + if (!response.ok) { + throw new Error(`Failed to fetch active version from ${url}: ${response.status} ${await response.text()}`) + } + + const data = (await response.json()) as {checks: {healthcheck: {outcome: {versionNumber: string}}}} + return data.checks.healthcheck.outcome.versionNumber +} + +function getVersion(stackName: string, baseStackName: string): {version: string, isSandbox: boolean} | null { + const pattern = String.raw`^${baseStackName}(?-sandbox)?-(?v[\da-z-]+)?$` + const match = new RegExp(pattern).exec(stackName) + if (!match?.groups?.version) { + return null + } + return {version: match.groups.version, isSandbox: match.groups.sandbox === "-sandbox"} +} + +function isEmbargoed(deployDate: Date | undefined): boolean { + return !!deployDate && Date.now() - deployDate.getTime() < 24 * 60 * 60 * 1000 +} + +function isSupersededVersion( + stack: StackSummary, + baseStackName: string, + activeVersions: ActiveVersions +): boolean { + const versionInfo = getVersion(stack.StackName!, baseStackName) + if (!versionInfo) { + return false + } + if (isEmbargoed(stack.CreationTime)) { + console.log(`Stack ${stack.StackName} created less than 24 hours ago, keeping for potential rollback`) + return false + } + const {version, isSandbox} = versionInfo + const currentVersion = isSandbox ? activeVersions.sandboxEnvVersion : activeVersions.baseEnvVersion + if (!currentVersion) { + return false + } + return version !== currentVersion.replaceAll(".", "-") +} diff --git a/packages/cdkConstructs/tests/stacks/deleteUnusedStacks.test.ts b/packages/cdkConstructs/tests/stacks/deleteUnusedStacks.test.ts new file mode 100644 index 0000000..49f7324 --- /dev/null +++ b/packages/cdkConstructs/tests/stacks/deleteUnusedStacks.test.ts @@ -0,0 +1,605 @@ +import { + describe, + test, + beforeEach, + afterEach, + expect, + vi +} from "vitest" + +import {deleteUnusedStacks} from "../../src/stacks/deleteUnusedStacks" + +const mockListStacksSend = vi.fn() +const mockDeleteStackSend = vi.fn() +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const mockListHostedZonesByNameSend = vi.fn((_) => ({HostedZones: [{Id: "Z123"}]})) +const mockChangeResourceRecordSetsSend = vi.fn() + +vi.mock("@aws-sdk/client-cloudformation", () => { + class CloudFormationClient { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(public config: any = {}) {} + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + send(command: any) { + if (command instanceof ListStacksCommand) { + return mockListStacksSend(command.input) + } else if (command instanceof DeleteStackCommand) { + return mockDeleteStackSend(command.input) + } else { + throw new TypeError("Unknown command") + } + } + } + + class ListStacksCommand { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + input: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(input: any) { + this.input = input + } + } + + class DeleteStackCommand { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + input: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(input: any) { + this.input = input + } + } + + return {CloudFormationClient, ListStacksCommand, DeleteStackCommand} +}) + +vi.mock("@aws-sdk/client-route-53", () => { + class Route53Client { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(public config: any = {}) {} + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + send(command: any) { + if (command instanceof ListHostedZonesByNameCommand) { + return mockListHostedZonesByNameSend(command.input) + } else if (command instanceof ChangeResourceRecordSetsCommand) { + return mockChangeResourceRecordSetsSend(command.input) + } else { + throw new TypeError("Unknown command") + } + } + } + + class ListHostedZonesByNameCommand { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + input: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(input: any) { + this.input = input + } + } + + class ChangeResourceRecordSetsCommand { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + input: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(input: any) { + this.input = input + } + } + + return {Route53Client, ListHostedZonesByNameCommand, ChangeResourceRecordSetsCommand} +}) + +const originalEnv = process.env +const originalFetch = globalThis.fetch + +const mockActiveVersion = "v1.2.3" +const mockGetPRState = vi.fn<(url: string) => string>((url: string) => { + throw new Error(`Unexpected URL: ${url}`) +}) + +describe("deleteUnusedStacks", () => { + const baseStackName = "eps-api" + const repoName = "eps-cdk-utils" + const basePath = "status-path" + + beforeEach(() => { + process.env = { + ...originalEnv, + APIGEE_ENVIRONMENT: "prod", + APIM_STATUS_API_KEY: "test-api-key", + GITHUB_TOKEN: "test-github-token" + } + + mockListStacksSend.mockReset() + mockDeleteStackSend.mockReset() + mockListHostedZonesByNameSend.mockReset() + mockChangeResourceRecordSetsSend.mockReset() + mockGetPRState.mockReset() + + // assign mocked fetch for HTTP calls + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ;(globalThis as any).fetch = (url: string) => { + if (url.includes("api.github.com")) { + return Promise.resolve({ + ok: true, + status: 200, + text: async () => "", + json: async () => ({state: mockGetPRState(url)}) + }) + } + // Default mock for other fetch calls + return Promise.resolve({ + ok: true, + status: 200, + text: async () => "", + json: async () => ({checks: {healthcheck: {outcome: {versionNumber: mockActiveVersion}}}}) + }) + } + + vi.useFakeTimers() + vi.setSystemTime(new Date("2024-01-03T00:00:00.000Z")) + }) + + afterEach(() => { + process.env = originalEnv + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ;(globalThis as any).fetch = originalFetch + vi.useRealTimers() + }) + + test("deletes closed PR stacks and CNAME records", async () => { + const now = new Date() + const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000) + + mockListStacksSend.mockReturnValue({ + StackSummaries: [ + { + StackName: `${baseStackName}-v1-2-3`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + }, + { + StackName: `${baseStackName}-pr-123`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + } + ] + }) + + mockGetPRState.mockImplementation((url: string) => { + if (url.endsWith("/repos/NHSDigital/eps-cdk-utils/pulls/123")) { + return "closed" + } + throw new Error(`Unexpected URL: ${url}`) + }) + + const promise = deleteUnusedStacks(baseStackName, repoName, basePath) + await vi.runAllTimersAsync() + await promise + + // One delete stack call for the PR stack + expect(mockDeleteStackSend).toHaveBeenCalledTimes(1) + expect(mockDeleteStackSend).toHaveBeenCalledWith({StackName: `${baseStackName}-pr-123`}) + + // CNAME deletion for the PR stack + expect(mockChangeResourceRecordSetsSend).toHaveBeenCalledTimes(1) + expect(mockChangeResourceRecordSetsSend).toHaveBeenCalledWith({ + HostedZoneId: "Z123", + ChangeBatch: { + Changes: [{ + Action: "DELETE", + ResourceRecordSet: { + Name: `${baseStackName}-pr-123.dev.eps.national.nhs.uk.`, + Type: "CNAME" + } + }] + } + }) + }) + + test("does not delete open PR stacks", async () => { + const now = new Date() + const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000) + + mockListStacksSend.mockReturnValue({ + StackSummaries: [ + { + StackName: `${baseStackName}-pr-456`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + } + ] + }) + + mockGetPRState.mockImplementation((url: string) => { + if (url.endsWith("/repos/NHSDigital/eps-cdk-utils/pulls/456")) { + return "open" + } + throw new Error(`Unexpected URL: ${url}`) + }) + + const promise = deleteUnusedStacks(baseStackName, repoName, basePath) + await vi.runAllTimersAsync() + await promise + + // No delete stack call should have been made + expect(mockDeleteStackSend).not.toHaveBeenCalled() + + // No CNAME deletion should have been made + expect(mockChangeResourceRecordSetsSend).not.toHaveBeenCalled() + }) + + test("deletes superseded non-PR stacks when embargo has passed", async () => { + const now = new Date() + const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000) + + mockListStacksSend.mockReturnValue({ + StackSummaries: [ + { + StackName: `${baseStackName}-v1-2-3`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + }, + { + StackName: `${baseStackName}-v1-2-2`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + } + ] + }) + + const promise = deleteUnusedStacks(baseStackName, repoName, basePath) + await vi.runAllTimersAsync() + await promise + + // Superseded version should be deleted + expect(mockDeleteStackSend).toHaveBeenCalledTimes(1) + expect(mockDeleteStackSend).toHaveBeenCalledWith({StackName: `${baseStackName}-v1-2-2`}) + }) + + test("does not delete embargoed versions even if active version is outside embargo period", async () => { + const now = new Date() + const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000) + const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000) + + mockListStacksSend.mockReturnValue({ + StackSummaries: [ + { + StackName: `${baseStackName}-v1-2-3`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + }, + { + StackName: `${baseStackName}-v1-2-4`, + StackStatus: "CREATE_COMPLETE", + CreationTime: oneHourAgo + } + ] + }) + + const promise = deleteUnusedStacks(baseStackName, repoName, basePath) + await vi.runAllTimersAsync() + await promise + + // No delete stack call should have been made + expect(mockDeleteStackSend).not.toHaveBeenCalled() + }) + + test("does not delete superseded non-PR stack when active version is within embargo period", async () => { + const now = new Date() + const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000) + const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000) + + mockListStacksSend.mockReturnValue({ + StackSummaries: [ + { + StackName: `${baseStackName}-v1-2-3`, + StackStatus: "CREATE_COMPLETE", + CreationTime: oneHourAgo + }, + { + StackName: `${baseStackName}-v1-2-2`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + } + ] + }) + + const promise = deleteUnusedStacks(baseStackName, repoName, basePath) + await vi.runAllTimersAsync() + await promise + + // No delete stack call should have been made + expect(mockDeleteStackSend).not.toHaveBeenCalled() + }) + + test("deletes superseded sandbox stacks when embargo has passed", async () => { + const now = new Date() + const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000) + + process.env.APIGEE_ENVIRONMENT = "int" + + mockListStacksSend.mockReturnValue({ + StackSummaries: [ + { + StackName: `${baseStackName}-v1-2-3`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + }, + { + StackName: `${baseStackName}-sandbox-v1-2-3`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + }, + { + StackName: `${baseStackName}-sandbox-v1-2-2`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + } + ] + }) + + const promise = deleteUnusedStacks(baseStackName, repoName, basePath) + await vi.runAllTimersAsync() + await promise + + // Superseded sandbox version should be deleted + expect(mockDeleteStackSend).toHaveBeenCalledTimes(1) + expect(mockDeleteStackSend).toHaveBeenCalledWith({StackName: `${baseStackName}-sandbox-v1-2-2`}) + + // CNAME deletion for the superseded sandbox stack + expect(mockChangeResourceRecordSetsSend).toHaveBeenCalledTimes(1) + expect(mockChangeResourceRecordSetsSend).toHaveBeenCalledWith({ + HostedZoneId: "Z123", + ChangeBatch: { + Changes: [{ + Action: "DELETE", + ResourceRecordSet: { + Name: `${baseStackName}-sandbox-v1-2-2.dev.eps.national.nhs.uk.`, + Type: "CNAME" + } + }] + } + }) + }) + + test("deletes superseded internal-dev sandbox stacks when embargo has passed", async () => { + const now = new Date() + const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000) + + process.env.APIGEE_ENVIRONMENT = "internal-dev" + + mockListStacksSend.mockReturnValue({ + StackSummaries: [ + { + StackName: `${baseStackName}-v1-2-3`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + }, + { + StackName: `${baseStackName}-sandbox-v1-2-3`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + }, + { + StackName: `${baseStackName}-sandbox-v1-2-2`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + } + ] + }) + + const promise = deleteUnusedStacks(baseStackName, repoName, basePath) + await vi.runAllTimersAsync() + await promise + + // Superseded sandbox version should be deleted + expect(mockDeleteStackSend).toHaveBeenCalledTimes(1) + expect(mockDeleteStackSend).toHaveBeenCalledWith({StackName: `${baseStackName}-sandbox-v1-2-2`}) + + // CNAME deletion for the superseded sandbox stack + expect(mockChangeResourceRecordSetsSend).toHaveBeenCalledTimes(1) + expect(mockChangeResourceRecordSetsSend).toHaveBeenCalledWith({ + HostedZoneId: "Z123", + ChangeBatch: { + Changes: [{ + Action: "DELETE", + ResourceRecordSet: { + Name: `${baseStackName}-sandbox-v1-2-2.dev.eps.national.nhs.uk.`, + Type: "CNAME" + } + }] + } + }) + }) + + test("still deletes non sandbox superseded stacks when fetching sandbox state fails", async () => { + const now = new Date() + const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000) + + process.env.APIGEE_ENVIRONMENT = "int" + + mockListStacksSend.mockReturnValue({ + StackSummaries: [ + { + StackName: `${baseStackName}-v1-2-3`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + }, + { + StackName: `${baseStackName}-v1-2-2`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + }, + { + StackName: `${baseStackName}-sandbox-v1-2-3`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + }, + { + StackName: `${baseStackName}-sandbox-v1-2-2`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + } + ] + }) + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ;(globalThis as any).fetch = (url: string) => { + if (url.includes("sandbox")) { + return Promise.resolve({ + ok: false, + status: 500, + text: async () => "Error fetching sandbox status" + }) + } + // Default mock for other fetch calls + return Promise.resolve({ + ok: true, + status: 200, + text: async () => "", + json: async () => ({checks: {healthcheck: {outcome: {versionNumber: mockActiveVersion}}}}) + }) + } + + const promise = deleteUnusedStacks(baseStackName, repoName, basePath) + await vi.runAllTimersAsync() + await promise + + // Superseded version should be deleted + expect(mockDeleteStackSend).toHaveBeenCalledTimes(1) + expect(mockDeleteStackSend).toHaveBeenCalledWith({StackName: `${baseStackName}-v1-2-2`}) + }) + + test("handles multiple pages of CloudFormation stacks", async () => { + const now = new Date() + const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000) + + mockListStacksSend.mockImplementation(({NextToken}) => { + if (!NextToken) { + return { + StackSummaries: [ + { + StackName: `${baseStackName}-v1-2-3`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + } + ], + NextToken: "token-1" + } + } + + return { + StackSummaries: [ + { + StackName: `${baseStackName}-pr-789`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + } + ] + } + }) + + mockGetPRState.mockImplementation((url: string) => { + if (url.endsWith("/repos/NHSDigital/eps-cdk-utils/pulls/789")) { + return "closed" + } + throw new Error(`Unexpected URL: ${url}`) + }) + + const promise = deleteUnusedStacks(baseStackName, repoName, basePath) + await vi.runAllTimersAsync() + await promise + + // Both pages of stacks should have been requested + expect(mockListStacksSend).toHaveBeenCalledTimes(2) + + // PR stack from the second page should be deleted + expect(mockDeleteStackSend).toHaveBeenCalledTimes(1) + expect(mockDeleteStackSend).toHaveBeenCalledWith({StackName: `${baseStackName}-pr-789`}) + }) + + test("skips stacks with DELETE_COMPLETE status", async () => { + const now = new Date() + const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000) + + mockListStacksSend.mockReturnValue({ + StackSummaries: [ + { + StackName: `${baseStackName}-pr-101`, + StackStatus: "DELETE_COMPLETE", + CreationTime: twoDaysAgo + } + ] + }) + + const promise = deleteUnusedStacks(baseStackName, repoName, basePath) + await vi.runAllTimersAsync() + await promise + + // No delete stack call should have been made + expect(mockDeleteStackSend).not.toHaveBeenCalled() + + // No CNAME deletion should have been made + expect(mockChangeResourceRecordSetsSend).not.toHaveBeenCalled() + }) + + test("skips PR stacks when fetching PR state fails", async () => { + const now = new Date() + const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000) + + mockListStacksSend.mockReturnValue({ + StackSummaries: [ + { + StackName: `${baseStackName}-pr-202`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + } + ] + }) + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ;(globalThis as any).fetch = (url: string) => { + if (url.includes("api.github.com")) { + return Promise.resolve({ + ok: false, + status: 500, + text: async () => "Error fetching PR" + }) + } + // Default mock for other fetch calls + return Promise.resolve({ + ok: true, + status: 200, + text: async () => "", + json: async () => ({checks: {healthcheck: {outcome: {versionNumber: mockActiveVersion}}}}) + }) + } + + const promise = deleteUnusedStacks(baseStackName, repoName, basePath) + await vi.runAllTimersAsync() + await promise + + // No delete stack call should have been made + expect(mockDeleteStackSend).not.toHaveBeenCalled() + + // No CNAME deletion should have been made + expect(mockChangeResourceRecordSetsSend).not.toHaveBeenCalled() + }) + + test("handles no stacks returned", async () => { + mockListStacksSend.mockReturnValue({}) + + const promise = deleteUnusedStacks(baseStackName, repoName, basePath) + await vi.runAllTimersAsync() + await promise + + // No delete stack call should have been made + expect(mockDeleteStackSend).not.toHaveBeenCalled() + + // No CNAME deletion should have been made + expect(mockChangeResourceRecordSetsSend).not.toHaveBeenCalled() + }) +}) diff --git a/packages/deploymentUtils/src/index.ts b/packages/deploymentUtils/src/index.ts index 460d8c6..b6b8443 100644 --- a/packages/deploymentUtils/src/index.ts +++ b/packages/deploymentUtils/src/index.ts @@ -1,3 +1,4 @@ export * from "./specifications/deployApi" export * from "./specifications/writeSchemas" +export * from "./specifications/deleteProxygenDeployments" export * from "./config/index" diff --git a/packages/deploymentUtils/src/specifications/deleteProxygenDeployments.ts b/packages/deploymentUtils/src/specifications/deleteProxygenDeployments.ts new file mode 100644 index 0000000..53dc69b --- /dev/null +++ b/packages/deploymentUtils/src/specifications/deleteProxygenDeployments.ts @@ -0,0 +1,120 @@ +import {LambdaClient} from "@aws-sdk/client-lambda" +import {getCFConfigValue, getCloudFormationExports} from "../config" +import {invokeLambda} from "./invokeLambda" + +interface ProxygenInstance { + name: string +} + +async function isClosedPullRequest(instanceName: string, apigeeApi: string, repoName: string): Promise { + const match = new RegExp(String.raw`^${apigeeApi}-pr-(?\d+)$`).exec(instanceName) + if (!match?.groups?.pullRequestId) { + return false + } + + const pullRequestId = match.groups.pullRequestId + console.log(`Checking pull request id ${pullRequestId}`) + const url = `https://api.github.com/repos/NHSDigital/${repoName}/pulls/${pullRequestId}` + + const headers: Record = { + Accept: "application/vnd.github+json", + Authorization: `Bearer ${process.env.GITHUB_TOKEN}` + } + + const response = await fetch(url, {headers}) + if (!response.ok) { + console.log(`Failed to fetch PR ${pullRequestId}: ${response.status} ${await response.text()}`) + return false + } + + const data = (await response.json()) as {state?: string} + if (data.state !== "closed") { + console.log(`not going to delete instance ${instanceName} as PR state is ${data.state}`) + return false + } + + console.log(`** going to delete instance ${instanceName} as PR state is ${data.state} **`) + return true +} + +async function deleteEnvProxygenDeployments( + apigeeEnvironment: string, + apigeeApi: string, + repoName: string, + proxygenPrivateKeyName: string, + proxygenKid: string +): Promise { + const lambda = new LambdaClient({}) + + const exports = await getCloudFormationExports() + const proxygenPrivateKeyArn = getCFConfigValue(exports, `account-resources:${proxygenPrivateKeyName}`) + + console.log(`Checking Apigee deployments of ${apigeeApi} on ${apigeeEnvironment}`) + const instances = JSON.parse(await invokeLambda( + lambda, + false, + "lambda-resources-ProxygenPTLInstanceGet", + { + apiName: apigeeApi, + environment: apigeeEnvironment, + kid: proxygenKid, + proxygenSecretName: proxygenPrivateKeyArn + } + )) as Array + + for (const instance of instances) { + const name = instance.name + + if (!(await isClosedPullRequest(name, apigeeApi, repoName))) { + continue + } + + await invokeLambda( + lambda, + false, + "lambda-resources-ProxygenPTLInstanceDelete", + { + apiName: apigeeApi, + environment: apigeeEnvironment, + instance: name, + kid: proxygenKid, + proxygenSecretName: proxygenPrivateKeyArn + } + ) + } +} + +/** + * Deletes Proxygen PTL deployments for closed pull requests across internal-dev and internal-dev-sandbox. + * + * For each supported Apigee environment, this function queries existing Proxygen instances + * for the given API and deletes those whose instance name corresponds to a closed GitHub PR + * in the specified repository. + * + * @param apigeeApi - The Apigee API name whose Proxygen deployments should be cleaned up. + * @param repoName - The GitHub repository name used to look up pull request state. + * @param proxygenPrivateKeyName - The CloudFormation export key for the Proxygen private key secret. + * @param proxygenKid - The key ID (kid) used when invoking the Proxygen Lambda functions. + * @returns A promise that resolves when all eligible deployments have been processed. + */ +export async function deleteProxygenDeployments( + apigeeApi: string, + repoName: string, + proxygenPrivateKeyName: string, + proxygenKid: string +): Promise { + await deleteEnvProxygenDeployments( + "internal-dev", + apigeeApi, + repoName, + proxygenPrivateKeyName, + proxygenKid + ) + await deleteEnvProxygenDeployments( + "internal-dev-sandbox", + apigeeApi, + repoName, + proxygenPrivateKeyName, + proxygenKid + ) +} diff --git a/packages/deploymentUtils/src/specifications/deployApi.ts b/packages/deploymentUtils/src/specifications/deployApi.ts index 369d6e4..8a0d69c 100644 --- a/packages/deploymentUtils/src/specifications/deployApi.ts +++ b/packages/deploymentUtils/src/specifications/deployApi.ts @@ -1,6 +1,7 @@ -import {LambdaClient, InvokeCommand} from "@aws-sdk/client-lambda" +import {LambdaClient} from "@aws-sdk/client-lambda" import {getCFConfigValue, getCloudFormationExports} from "../config/index" import {fixSpec} from "./fixSpec" +import {invokeLambda} from "./invokeLambda" export type ApiConfig = { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -19,28 +20,6 @@ export type ApiConfig = { hiddenPaths: Array } -const lambda = new LambdaClient({}) - -async function invokeLambda( - dryRun: boolean, - functionName: string, - payload: unknown -): Promise { - if (dryRun) { - console.log(`Would invoke lambda ${functionName}`) - return - } - const invokeResult = await lambda.send(new InvokeCommand({ - FunctionName: functionName, - Payload: Buffer.from(JSON.stringify(payload)) - })) - const responsePayload = Buffer.from(invokeResult.Payload!).toString() - if (invokeResult.FunctionError) { - throw new Error(`Error calling lambda ${functionName}: ${responsePayload}`) - } - console.log(`Lambda ${functionName} invoked successfully. Response:`, responsePayload) -} - export async function deployApi( { spec, @@ -60,6 +39,7 @@ export async function deployApi( blueGreen: boolean, dryRun: boolean ): Promise { + const lambda = new LambdaClient({}) const instance = fixSpec({ spec, apiName, @@ -89,6 +69,7 @@ export async function deployApi( if (!isPullRequest) { console.log("Store the secret used for mutual TLS to AWS using Proxygen proxy lambda") await invokeLambda( + lambda, dryRun, put_secret_lambda, { @@ -105,6 +86,7 @@ export async function deployApi( console.log("Deploy the API instance using Proxygen proxy lambda") await invokeLambda( + lambda, dryRun, instance_put_lambda, { @@ -132,6 +114,7 @@ export async function deployApi( delete spec.paths[path] } await invokeLambda( + lambda, dryRun, spec_publish_lambda, { diff --git a/packages/deploymentUtils/src/specifications/invokeLambda.ts b/packages/deploymentUtils/src/specifications/invokeLambda.ts new file mode 100644 index 0000000..8a3f343 --- /dev/null +++ b/packages/deploymentUtils/src/specifications/invokeLambda.ts @@ -0,0 +1,23 @@ +import {InvokeCommand, LambdaClient} from "@aws-sdk/client-lambda" + +export async function invokeLambda( + lambda: LambdaClient, + dryRun: boolean, + functionName: string, + payload: unknown +): Promise { + if (dryRun) { + console.log(`Would invoke lambda ${functionName}`) + return "null" + } + const invokeResult = await lambda.send(new InvokeCommand({ + FunctionName: functionName, + Payload: Buffer.from(JSON.stringify(payload)) + })) + const responsePayload = Buffer.from(invokeResult.Payload!).toString() + if (invokeResult.FunctionError) { + throw new Error(`Error calling lambda ${functionName}: ${responsePayload}`) + } + console.log(`Lambda ${functionName} invoked successfully. Response:`, responsePayload) + return responsePayload +} diff --git a/packages/deploymentUtils/tests/specifications/deleteProxygenDeployments.test.ts b/packages/deploymentUtils/tests/specifications/deleteProxygenDeployments.test.ts new file mode 100644 index 0000000..02b981a --- /dev/null +++ b/packages/deploymentUtils/tests/specifications/deleteProxygenDeployments.test.ts @@ -0,0 +1,132 @@ +import { + beforeEach, + afterEach, + describe, + expect, + test, + vi +} from "vitest" +import {deleteProxygenDeployments} from "../../src/specifications/deleteProxygenDeployments" + +const getCloudFormationExportsMock = vi.hoisted(() => vi.fn()) +const invokeLambdaMock = vi.hoisted(() => vi.fn()) + +vi.mock("../../src/config/index", async (importOriginal) => { + const originalModule = await importOriginal() + return { + ...originalModule, + getCloudFormationExports: getCloudFormationExportsMock + } +}) + +vi.mock("../../src/specifications/invokeLambda", async () => { + return { + invokeLambda: invokeLambdaMock + } +}) + +const originalFetch = globalThis.fetch + +function createFetchResponse(state: string, ok = true, status = 200, textBody = "") { + return Promise.resolve({ + ok, + status, + text: async () => textBody, + json: async () => ({state}) + }) as unknown as Promise +} + +describe("deleteProxygenDeployments", () => { + beforeEach(() => { + getCloudFormationExportsMock.mockReset().mockResolvedValue({ + "account-resources:proxygenKey": "arn:proxygen-key" + }) + invokeLambdaMock.mockReset() + + // default fetch mock; tests can override behaviour + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ;(globalThis as any).fetch = vi.fn((url: string) => { + if (url.includes("/pulls/456")) { + return createFetchResponse("open") + } + return createFetchResponse("closed") + }) + }) + + afterEach(() => { + // restore original fetch between tests + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ;(globalThis as any).fetch = originalFetch + }) + + test("deletes instances whose pull requests are closed in both environments", async () => { + const deletePayloads: Array<{environment: string, instance: string}> = [] + invokeLambdaMock.mockImplementation(async (_lambda, _dryRun, functionName: string, payload: unknown) => { + if (functionName === "lambda-resources-ProxygenPTLInstanceGet") { + const {apiName} = payload as {apiName: string} + return JSON.stringify([{name: `${apiName}-pr-123`}]) + } + if (functionName === "lambda-resources-ProxygenPTLInstanceDelete") { + deletePayloads.push(payload as {environment: string, instance: string}) + return "\"deleted\"" + } + return "\"ok\"" + }) + + await deleteProxygenDeployments("eps", "eps-repo", "proxygenKey", "kid-123") + + expect(deletePayloads).toEqual(expect.arrayContaining([ + expect.objectContaining({environment: "internal-dev", instance: "eps-pr-123"}), + expect.objectContaining({environment: "internal-dev-sandbox", instance: "eps-pr-123"}) + ])) + }) + + test("does not delete instances for open pull requests or non-PR names", async () => { + let deleteCalls = 0 + invokeLambdaMock.mockImplementation(async (_lambda, _dryRun, functionName: string) => { + if (functionName === "lambda-resources-ProxygenPTLInstanceGet") { + return JSON.stringify([ + {name: "eps-pr-456"}, + {name: "eps"} + ]) + } + if (functionName === "lambda-resources-ProxygenPTLInstanceDelete") { + deleteCalls++ + return "\"deleted\"" + } + return "\"ok\"" + }) + + await deleteProxygenDeployments("eps", "eps-repo", "proxygenKey", "kid-123") + + expect(deleteCalls).toBe(0) + }) + + test("does not delete instances when GitHub API call fails", async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ;(globalThis as any).fetch = vi.fn(() => { + return Promise.resolve({ + ok: false, + status: 500, + text: async () => "server error", + json: async () => ({state: "unknown"}) + }) as unknown as Promise + }) + + let deleteCalls = 0 + invokeLambdaMock.mockImplementation(async (_lambda, _dryRun, functionName: string) => { + if (functionName === "lambda-resources-ProxygenPTLInstanceGet") { + return JSON.stringify([{name: "eps-pr-999"}]) + } + if (functionName === "lambda-resources-ProxygenPTLInstanceDelete") { + deleteCalls++ + return "\"deleted\"" + } + return "\"ok\"" + }) + + await deleteProxygenDeployments("eps", "eps-repo", "proxygenKey", "kid-123") + + expect(deleteCalls).toBe(0) + }) +})