diff --git a/package-lock.json b/package-lock.json index 45151fc..31d6293 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@aws-sdk/client-cloudfront": "3.590.0", "@aws-sdk/client-dynamodb": "3.709.0", "@aws-sdk/client-elastic-beanstalk": "3.590.0", + "@aws-sdk/client-lambda": "3.787.0", "@aws-sdk/client-s3": "3.591.0", "@aws-sdk/client-secrets-manager": "3.758.0", "@aws-sdk/client-sqs": "3.682.0", @@ -1083,6 +1084,1210 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/client-lambda": { + "version": "3.787.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.787.0.tgz", + "integrity": "sha512-aPSg7YL7IpEaijsunAYtws/3dZl+VjyQ1wbv6RxdIfzww/35x31GSc6vD6paq8KC6lcns8wlli/0qCOl8Z9wZg==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.775.0", + "@aws-sdk/credential-provider-node": "3.787.0", + "@aws-sdk/middleware-host-header": "3.775.0", + "@aws-sdk/middleware-logger": "3.775.0", + "@aws-sdk/middleware-recursion-detection": "3.775.0", + "@aws-sdk/middleware-user-agent": "3.787.0", + "@aws-sdk/region-config-resolver": "3.775.0", + "@aws-sdk/types": "3.775.0", + "@aws-sdk/util-endpoints": "3.787.0", + "@aws-sdk/util-user-agent-browser": "3.775.0", + "@aws-sdk/util-user-agent-node": "3.787.0", + "@smithy/config-resolver": "^4.1.0", + "@smithy/core": "^3.2.0", + "@smithy/eventstream-serde-browser": "^4.0.2", + "@smithy/eventstream-serde-config-resolver": "^4.1.0", + "@smithy/eventstream-serde-node": "^4.0.2", + "@smithy/fetch-http-handler": "^5.0.2", + "@smithy/hash-node": "^4.0.2", + "@smithy/invalid-dependency": "^4.0.2", + "@smithy/middleware-content-length": "^4.0.2", + "@smithy/middleware-endpoint": "^4.1.0", + "@smithy/middleware-retry": "^4.1.0", + "@smithy/middleware-serde": "^4.0.3", + "@smithy/middleware-stack": "^4.0.2", + "@smithy/node-config-provider": "^4.0.2", + "@smithy/node-http-handler": "^4.0.4", + "@smithy/protocol-http": "^5.1.0", + "@smithy/smithy-client": "^4.2.0", + "@smithy/types": "^4.2.0", + "@smithy/url-parser": "^4.0.2", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.8", + "@smithy/util-defaults-mode-node": "^4.0.8", + "@smithy/util-endpoints": "^3.0.2", + "@smithy/util-middleware": "^4.0.2", + "@smithy/util-retry": "^4.0.2", + "@smithy/util-stream": "^4.2.0", + "@smithy/util-utf8": "^4.0.0", + "@smithy/util-waiter": "^4.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/client-sso": { + "version": "3.787.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.787.0.tgz", + "integrity": "sha512-L8R+Mh258G0DC73ktpSVrG4TT9i2vmDLecARTDR/4q5sRivdDQSL5bUp3LKcK80Bx+FRw3UETIlX6mYMLL9PJQ==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.775.0", + "@aws-sdk/middleware-host-header": "3.775.0", + "@aws-sdk/middleware-logger": "3.775.0", + "@aws-sdk/middleware-recursion-detection": "3.775.0", + "@aws-sdk/middleware-user-agent": "3.787.0", + "@aws-sdk/region-config-resolver": "3.775.0", + "@aws-sdk/types": "3.775.0", + "@aws-sdk/util-endpoints": "3.787.0", + "@aws-sdk/util-user-agent-browser": "3.775.0", + "@aws-sdk/util-user-agent-node": "3.787.0", + "@smithy/config-resolver": "^4.1.0", + "@smithy/core": "^3.2.0", + "@smithy/fetch-http-handler": "^5.0.2", + "@smithy/hash-node": "^4.0.2", + "@smithy/invalid-dependency": "^4.0.2", + "@smithy/middleware-content-length": "^4.0.2", + "@smithy/middleware-endpoint": "^4.1.0", + "@smithy/middleware-retry": "^4.1.0", + "@smithy/middleware-serde": "^4.0.3", + "@smithy/middleware-stack": "^4.0.2", + "@smithy/node-config-provider": "^4.0.2", + "@smithy/node-http-handler": "^4.0.4", + "@smithy/protocol-http": "^5.1.0", + "@smithy/smithy-client": "^4.2.0", + "@smithy/types": "^4.2.0", + "@smithy/url-parser": "^4.0.2", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.8", + "@smithy/util-defaults-mode-node": "^4.0.8", + "@smithy/util-endpoints": "^3.0.2", + "@smithy/util-middleware": "^4.0.2", + "@smithy/util-retry": "^4.0.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/core": { + "version": "3.775.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.775.0.tgz", + "integrity": "sha512-8vpW4WihVfz0DX+7WnnLGm3GuQER++b0IwQG35JlQMlgqnc44M//KbJPsIHA0aJUJVwJAEShgfr5dUbY8WUzaA==", + "dependencies": { + "@aws-sdk/types": "3.775.0", + "@smithy/core": "^3.2.0", + "@smithy/node-config-provider": "^4.0.2", + "@smithy/property-provider": "^4.0.2", + "@smithy/protocol-http": "^5.1.0", + "@smithy/signature-v4": "^5.0.2", + "@smithy/smithy-client": "^4.2.0", + "@smithy/types": "^4.2.0", + "@smithy/util-middleware": "^4.0.2", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.775.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.775.0.tgz", + "integrity": "sha512-6ESVxwCbGm7WZ17kY1fjmxQud43vzJFoLd4bmlR+idQSWdqlzGDYdcfzpjDKTcivdtNrVYmFvcH1JBUwCRAZhw==", + "dependencies": { + "@aws-sdk/core": "3.775.0", + "@aws-sdk/types": "3.775.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.775.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.775.0.tgz", + "integrity": "sha512-PjDQeDH/J1S0yWV32wCj2k5liRo0ssXMseCBEkCsD3SqsU8o5cU82b0hMX4sAib/RkglCSZqGO0xMiN0/7ndww==", + "dependencies": { + "@aws-sdk/core": "3.775.0", + "@aws-sdk/types": "3.775.0", + "@smithy/fetch-http-handler": "^5.0.2", + "@smithy/node-http-handler": "^4.0.4", + "@smithy/property-provider": "^4.0.2", + "@smithy/protocol-http": "^5.1.0", + "@smithy/smithy-client": "^4.2.0", + "@smithy/types": "^4.2.0", + "@smithy/util-stream": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.787.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.787.0.tgz", + "integrity": "sha512-hc2taRoDlXn2uuNuHWDJljVWYrp3r9JF1a/8XmOAZhVUNY+ImeeStylHXhXXKEA4JOjW+5PdJj0f1UDkVCHJiQ==", + "dependencies": { + "@aws-sdk/core": "3.775.0", + "@aws-sdk/credential-provider-env": "3.775.0", + "@aws-sdk/credential-provider-http": "3.775.0", + "@aws-sdk/credential-provider-process": "3.775.0", + "@aws-sdk/credential-provider-sso": "3.787.0", + "@aws-sdk/credential-provider-web-identity": "3.787.0", + "@aws-sdk/nested-clients": "3.787.0", + "@aws-sdk/types": "3.775.0", + "@smithy/credential-provider-imds": "^4.0.2", + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.787.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.787.0.tgz", + "integrity": "sha512-JioVi44B1vDMaK2CdzqimwvJD3uzvzbQhaEWXsGMBcMcNHajXAXf08EF50JG3ZhLrhhUsT1ObXpbTaPINOhh+g==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.775.0", + "@aws-sdk/credential-provider-http": "3.775.0", + "@aws-sdk/credential-provider-ini": "3.787.0", + "@aws-sdk/credential-provider-process": "3.775.0", + "@aws-sdk/credential-provider-sso": "3.787.0", + "@aws-sdk/credential-provider-web-identity": "3.787.0", + "@aws-sdk/types": "3.775.0", + "@smithy/credential-provider-imds": "^4.0.2", + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.775.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.775.0.tgz", + "integrity": "sha512-A6k68H9rQp+2+7P7SGO90Csw6nrUEm0Qfjpn9Etc4EboZhhCLs9b66umUsTsSBHus4FDIe5JQxfCUyt1wgNogg==", + "dependencies": { + "@aws-sdk/core": "3.775.0", + "@aws-sdk/types": "3.775.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.787.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.787.0.tgz", + "integrity": "sha512-fHc08bsvwm4+dEMEQKnQ7c1irEQmmxbgS+Fq41y09pPvPh31nAhoMcjBSTWAaPHvvsRbTYvmP4Mf12ZGr8/nfg==", + "dependencies": { + "@aws-sdk/client-sso": "3.787.0", + "@aws-sdk/core": "3.775.0", + "@aws-sdk/token-providers": "3.787.0", + "@aws-sdk/types": "3.775.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.787.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.787.0.tgz", + "integrity": "sha512-SobmCwNbk6TfEsF283mZPQEI5vV2j6eY5tOCj8Er4Lzraxu9fBPADV+Bib2A8F6jlB1lMPJzOuDCbEasSt/RIw==", + "dependencies": { + "@aws-sdk/core": "3.775.0", + "@aws-sdk/nested-clients": "3.787.0", + "@aws-sdk/types": "3.775.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.775.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.775.0.tgz", + "integrity": "sha512-tkSegM0Z6WMXpLB8oPys/d+umYIocvO298mGvcMCncpRl77L9XkvSLJIFzaHes+o7djAgIduYw8wKIMStFss2w==", + "dependencies": { + "@aws-sdk/types": "3.775.0", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/middleware-logger": { + "version": "3.775.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.775.0.tgz", + "integrity": "sha512-FaxO1xom4MAoUJsldmR92nT1G6uZxTdNYOFYtdHfd6N2wcNaTuxgjIvqzg5y7QIH9kn58XX/dzf1iTjgqUStZw==", + "dependencies": { + "@aws-sdk/types": "3.775.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.775.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.775.0.tgz", + "integrity": "sha512-GLCzC8D0A0YDG5u3F5U03Vb9j5tcOEFhr8oc6PDk0k0vm5VwtZOE6LvK7hcCSoAB4HXyOUM0sQuXrbaAh9OwXA==", + "dependencies": { + "@aws-sdk/types": "3.775.0", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.787.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.787.0.tgz", + "integrity": "sha512-Lnfj8SmPLYtrDFthNIaNj66zZsBCam+E4XiUDr55DIHTGstH6qZ/q6vg0GfbukxwSmUcGMwSR4Qbn8rb8yd77g==", + "dependencies": { + "@aws-sdk/core": "3.775.0", + "@aws-sdk/types": "3.775.0", + "@aws-sdk/util-endpoints": "3.787.0", + "@smithy/core": "^3.2.0", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/nested-clients": { + "version": "3.787.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.787.0.tgz", + "integrity": "sha512-xk03q1xpKNHgbuo+trEf1dFrI239kuMmjKKsqLEsHlAZbuFq4yRGMlHBrVMnKYOPBhVFDS/VineM991XI52fKg==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.775.0", + "@aws-sdk/middleware-host-header": "3.775.0", + "@aws-sdk/middleware-logger": "3.775.0", + "@aws-sdk/middleware-recursion-detection": "3.775.0", + "@aws-sdk/middleware-user-agent": "3.787.0", + "@aws-sdk/region-config-resolver": "3.775.0", + "@aws-sdk/types": "3.775.0", + "@aws-sdk/util-endpoints": "3.787.0", + "@aws-sdk/util-user-agent-browser": "3.775.0", + "@aws-sdk/util-user-agent-node": "3.787.0", + "@smithy/config-resolver": "^4.1.0", + "@smithy/core": "^3.2.0", + "@smithy/fetch-http-handler": "^5.0.2", + "@smithy/hash-node": "^4.0.2", + "@smithy/invalid-dependency": "^4.0.2", + "@smithy/middleware-content-length": "^4.0.2", + "@smithy/middleware-endpoint": "^4.1.0", + "@smithy/middleware-retry": "^4.1.0", + "@smithy/middleware-serde": "^4.0.3", + "@smithy/middleware-stack": "^4.0.2", + "@smithy/node-config-provider": "^4.0.2", + "@smithy/node-http-handler": "^4.0.4", + "@smithy/protocol-http": "^5.1.0", + "@smithy/smithy-client": "^4.2.0", + "@smithy/types": "^4.2.0", + "@smithy/url-parser": "^4.0.2", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.8", + "@smithy/util-defaults-mode-node": "^4.0.8", + "@smithy/util-endpoints": "^3.0.2", + "@smithy/util-middleware": "^4.0.2", + "@smithy/util-retry": "^4.0.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.775.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.775.0.tgz", + "integrity": "sha512-40iH3LJjrQS3LKUJAl7Wj0bln7RFPEvUYKFxtP8a+oKFDO0F65F52xZxIJbPn6sHkxWDAnZlGgdjZXM3p2g5wQ==", + "dependencies": { + "@aws-sdk/types": "3.775.0", + "@smithy/node-config-provider": "^4.0.2", + "@smithy/types": "^4.2.0", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/token-providers": { + "version": "3.787.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.787.0.tgz", + "integrity": "sha512-d7/NIqxq308Zg0RPMNrmn0QvzniL4Hx8Qdwzr6YZWLYAbUSvZYS2ppLR3BFWSkV6SsTJUx8BuDaj3P8vttkrog==", + "dependencies": { + "@aws-sdk/nested-clients": "3.787.0", + "@aws-sdk/types": "3.775.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/types": { + "version": "3.775.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.775.0.tgz", + "integrity": "sha512-ZoGKwa4C9fC9Av6bdfqcW6Ix5ot05F/S4VxWR2nHuMv7hzfmAjTOcUiWT7UR4hM/U0whf84VhDtXN/DWAk52KA==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/util-endpoints": { + "version": "3.787.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.787.0.tgz", + "integrity": "sha512-fd3zkiOkwnbdbN0Xp9TsP5SWrmv0SpT70YEdbb8wAj2DWQwiCmFszaSs+YCvhoCdmlR3Wl9Spu0pGpSAGKeYvQ==", + "dependencies": { + "@aws-sdk/types": "3.775.0", + "@smithy/types": "^4.2.0", + "@smithy/util-endpoints": "^3.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.775.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.775.0.tgz", + "integrity": "sha512-txw2wkiJmZKVdDbscK7VBK+u+TJnRtlUjRTLei+elZg2ADhpQxfVAQl436FUeIv6AhB/oRHW6/K/EAGXUSWi0A==", + "dependencies": { + "@aws-sdk/types": "3.775.0", + "@smithy/types": "^4.2.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.787.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.787.0.tgz", + "integrity": "sha512-mG7Lz8ydfG4SF9e8WSXiPQ/Lsn3n8A5B5jtPROidafi06I3ckV2WxyMLdwG14m919NoS6IOfWHyRGSqWIwbVKA==", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.787.0", + "@aws-sdk/types": "3.775.0", + "@smithy/node-config-provider": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/abort-controller": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.2.tgz", + "integrity": "sha512-Sl/78VDtgqKxN2+1qduaVE140XF+Xg+TafkncspwM4jFP/LHr76ZHmIY/y3V1M0mMLNk+Je6IGbzxy23RSToMw==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/config-resolver": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.0.tgz", + "integrity": "sha512-8smPlwhga22pwl23fM5ew4T9vfLUCeFXlcqNOCD5M5h8VmNPNUE9j6bQSuRXpDSV11L/E/SwEBQuW8hr6+nS1A==", + "dependencies": { + "@smithy/node-config-provider": "^4.0.2", + "@smithy/types": "^4.2.0", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/core": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.2.0.tgz", + "integrity": "sha512-k17bgQhVZ7YmUvA8at4af1TDpl0NDMBuBKJl8Yg0nrefwmValU+CnA5l/AriVdQNthU/33H3nK71HrLgqOPr1Q==", + "dependencies": { + "@smithy/middleware-serde": "^4.0.3", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.2", + "@smithy/util-stream": "^4.2.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/credential-provider-imds": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.2.tgz", + "integrity": "sha512-32lVig6jCaWBHnY+OEQ6e6Vnt5vDHaLiydGrwYMW9tPqO688hPGTYRamYJ1EptxEC2rAwJrHWmPoKRBl4iTa8w==", + "dependencies": { + "@smithy/node-config-provider": "^4.0.2", + "@smithy/property-provider": "^4.0.2", + "@smithy/types": "^4.2.0", + "@smithy/url-parser": "^4.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/eventstream-codec": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.0.2.tgz", + "integrity": "sha512-p+f2kLSK7ZrXVfskU/f5dzksKTewZk8pJLPvER3aFHPt76C2MxD9vNatSfLzzQSQB4FNO96RK4PSXfhD1TTeMQ==", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.2.0", + "@smithy/util-hex-encoding": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/eventstream-serde-browser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.0.2.tgz", + "integrity": "sha512-CepZCDs2xgVUtH7ZZ7oDdZFH8e6Y2zOv8iiX6RhndH69nlojCALSKK+OXwZUgOtUZEUaZ5e1hULVCHYbCn7pug==", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.1.0.tgz", + "integrity": "sha512-1PI+WPZ5TWXrfj3CIoKyUycYynYJgZjuQo8U+sphneOtjsgrttYybdqESFReQrdWJ+LKt6NEdbYzmmfDBmjX2A==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/eventstream-serde-node": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.0.2.tgz", + "integrity": "sha512-C5bJ/C6x9ENPMx2cFOirspnF9ZsBVnBMtP6BdPl/qYSuUawdGQ34Lq0dMcf42QTjUZgWGbUIZnz6+zLxJlb9aw==", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/eventstream-serde-universal": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.0.2.tgz", + "integrity": "sha512-St8h9JqzvnbB52FtckiHPN4U/cnXcarMniXRXTKn0r4b4XesZOGiAyUdj1aXbqqn1icSqBlzzUsCl6nPB018ng==", + "dependencies": { + "@smithy/eventstream-codec": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/fetch-http-handler": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.2.tgz", + "integrity": "sha512-+9Dz8sakS9pe7f2cBocpJXdeVjMopUDLgZs1yWeu7h++WqSbjUYv/JAJwKwXw1HV6gq1jyWjxuyn24E2GhoEcQ==", + "dependencies": { + "@smithy/protocol-http": "^5.1.0", + "@smithy/querystring-builder": "^4.0.2", + "@smithy/types": "^4.2.0", + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/hash-node": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.2.tgz", + "integrity": "sha512-VnTpYPnRUE7yVhWozFdlxcYknv9UN7CeOqSrMH+V877v4oqtVYuoqhIhtSjmGPvYrYnAkaM61sLMKHvxL138yg==", + "dependencies": { + "@smithy/types": "^4.2.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/invalid-dependency": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.2.tgz", + "integrity": "sha512-GatB4+2DTpgWPday+mnUkoumP54u/MDM/5u44KF9hIu8jF0uafZtQLcdfIKkIcUNuF/fBojpLEHZS/56JqPeXQ==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/middleware-content-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.2.tgz", + "integrity": "sha512-hAfEXm1zU+ELvucxqQ7I8SszwQ4znWMbNv6PLMndN83JJN41EPuS93AIyh2N+gJ6x8QFhzSO6b7q2e6oClDI8A==", + "dependencies": { + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/middleware-endpoint": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.0.tgz", + "integrity": "sha512-xhLimgNCbCzsUppRTGXWkZywksuTThxaIB0HwbpsVLY5sceac4e1TZ/WKYqufQLaUy+gUSJGNdwD2jo3cXL0iA==", + "dependencies": { + "@smithy/core": "^3.2.0", + "@smithy/middleware-serde": "^4.0.3", + "@smithy/node-config-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "@smithy/url-parser": "^4.0.2", + "@smithy/util-middleware": "^4.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/middleware-retry": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.0.tgz", + "integrity": "sha512-2zAagd1s6hAaI/ap6SXi5T3dDwBOczOMCSkkYzktqN1+tzbk1GAsHNAdo/1uzxz3Ky02jvZQwbi/vmDA6z4Oyg==", + "dependencies": { + "@smithy/node-config-provider": "^4.0.2", + "@smithy/protocol-http": "^5.1.0", + "@smithy/service-error-classification": "^4.0.2", + "@smithy/smithy-client": "^4.2.0", + "@smithy/types": "^4.2.0", + "@smithy/util-middleware": "^4.0.2", + "@smithy/util-retry": "^4.0.2", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/middleware-serde": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.3.tgz", + "integrity": "sha512-rfgDVrgLEVMmMn0BI8O+8OVr6vXzjV7HZj57l0QxslhzbvVfikZbVfBVthjLHqib4BW44QhcIgJpvebHlRaC9A==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/middleware-stack": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.2.tgz", + "integrity": "sha512-eSPVcuJJGVYrFYu2hEq8g8WWdJav3sdrI4o2c6z/rjnYDd3xH9j9E7deZQCzFn4QvGPouLngH3dQ+QVTxv5bOQ==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/node-config-provider": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.0.2.tgz", + "integrity": "sha512-WgCkILRZfJwJ4Da92a6t3ozN/zcvYyJGUTmfGbgS/FkCcoCjl7G4FJaCDN1ySdvLvemnQeo25FdkyMSTSwulsw==", + "dependencies": { + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/node-http-handler": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.4.tgz", + "integrity": "sha512-/mdqabuAT3o/ihBGjL94PUbTSPSRJ0eeVTdgADzow0wRJ0rN4A27EOrtlK56MYiO1fDvlO3jVTCxQtQmK9dZ1g==", + "dependencies": { + "@smithy/abort-controller": "^4.0.2", + "@smithy/protocol-http": "^5.1.0", + "@smithy/querystring-builder": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/property-provider": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.2.tgz", + "integrity": "sha512-wNRoQC1uISOuNc2s4hkOYwYllmiyrvVXWMtq+TysNRVQaHm4yoafYQyjN/goYZS+QbYlPIbb/QRjaUZMuzwQ7A==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/protocol-http": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.0.tgz", + "integrity": "sha512-KxAOL1nUNw2JTYrtviRRjEnykIDhxc84qMBzxvu1MUfQfHTuBlCG7PA6EdVwqpJjH7glw7FqQoFxUJSyBQgu7g==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/querystring-builder": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.2.tgz", + "integrity": "sha512-NTOs0FwHw1vimmQM4ebh+wFQvOwkEf/kQL6bSM1Lock+Bv4I89B3hGYoUEPkmvYPkDKyp5UdXJYu+PoTQ3T31Q==", + "dependencies": { + "@smithy/types": "^4.2.0", + "@smithy/util-uri-escape": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/querystring-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.2.tgz", + "integrity": "sha512-v6w8wnmZcVXjfVLjxw8qF7OwESD9wnpjp0Dqry/Pod0/5vcEA3qxCr+BhbOHlxS8O+29eLpT3aagxXGwIoEk7Q==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/service-error-classification": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.2.tgz", + "integrity": "sha512-LA86xeFpTKn270Hbkixqs5n73S+LVM0/VZco8dqd+JT75Dyx3Lcw/MraL7ybjmz786+160K8rPOmhsq0SocoJQ==", + "dependencies": { + "@smithy/types": "^4.2.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.2.tgz", + "integrity": "sha512-J9/gTWBGVuFZ01oVA6vdb4DAjf1XbDhK6sLsu3OS9qmLrS6KB5ygpeHiM3miIbj1qgSJ96GYszXFWv6ErJ8QEw==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/signature-v4": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.0.2.tgz", + "integrity": "sha512-Mz+mc7okA73Lyz8zQKJNyr7lIcHLiPYp0+oiqiMNc/t7/Kf2BENs5d63pEj7oPqdjaum6g0Fc8wC78dY1TgtXw==", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-middleware": "^4.0.2", + "@smithy/util-uri-escape": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/smithy-client": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.2.0.tgz", + "integrity": "sha512-Qs65/w30pWV7LSFAez9DKy0Koaoh3iHhpcpCCJ4waj/iqwsuSzJna2+vYwq46yBaqO5ZbP9TjUsATUNxrKeBdw==", + "dependencies": { + "@smithy/core": "^3.2.0", + "@smithy/middleware-endpoint": "^4.1.0", + "@smithy/middleware-stack": "^4.0.2", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "@smithy/util-stream": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/types": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.2.0.tgz", + "integrity": "sha512-7eMk09zQKCO+E/ivsjQv+fDlOupcFUCSC/L2YUPgwhvowVGWbPQHjEFcmjt7QQ4ra5lyowS92SV53Zc6XD4+fg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/url-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.2.tgz", + "integrity": "sha512-Bm8n3j2ScqnT+kJaClSVCMeiSenK6jVAzZCNewsYWuZtnBehEz4r2qP0riZySZVfzB+03XZHJeqfmJDkeeSLiQ==", + "dependencies": { + "@smithy/querystring-parser": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-base64": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-body-length-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-body-length-node": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", + "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-config-provider": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.8.tgz", + "integrity": "sha512-ZTypzBra+lI/LfTYZeop9UjoJhhGRTg3pxrNpfSTQLd3AJ37r2z4AXTKpq1rFXiiUIJsYyFgNJdjWRGP/cbBaQ==", + "dependencies": { + "@smithy/property-provider": "^4.0.2", + "@smithy/smithy-client": "^4.2.0", + "@smithy/types": "^4.2.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-defaults-mode-node": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.8.tgz", + "integrity": "sha512-Rgk0Jc/UDfRTzVthye/k2dDsz5Xxs9LZaKCNPgJTRyoyBoeiNCnHsYGOyu1PKN+sDyPnJzMOz22JbwxzBp9NNA==", + "dependencies": { + "@smithy/config-resolver": "^4.1.0", + "@smithy/credential-provider-imds": "^4.0.2", + "@smithy/node-config-provider": "^4.0.2", + "@smithy/property-provider": "^4.0.2", + "@smithy/smithy-client": "^4.2.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-endpoints": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.2.tgz", + "integrity": "sha512-6QSutU5ZyrpNbnd51zRTL7goojlcnuOB55+F9VBD+j8JpRY50IGamsjlycrmpn8PQkmJucFW8A0LSfXj7jjtLQ==", + "dependencies": { + "@smithy/node-config-provider": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-middleware": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.2.tgz", + "integrity": "sha512-6GDamTGLuBQVAEuQ4yDQ+ti/YINf/MEmIegrEeg7DdB/sld8BX1lqt9RRuIcABOhAGTA50bRbPzErez7SlDtDQ==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-retry": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.2.tgz", + "integrity": "sha512-Qryc+QG+7BCpvjloFLQrmlSd0RsVRHejRXd78jNO3+oREueCjwG1CCEH1vduw/ZkM1U9TztwIKVIi3+8MJScGg==", + "dependencies": { + "@smithy/service-error-classification": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-stream": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.0.tgz", + "integrity": "sha512-Vj1TtwWnuWqdgQI6YTUF5hQ/0jmFiOYsc51CSMgj7QfyO+RF4EnT2HNjoviNlOOmgzgvf3f5yno+EiC4vrnaWQ==", + "dependencies": { + "@smithy/fetch-http-handler": "^5.0.2", + "@smithy/node-http-handler": "^4.0.4", + "@smithy/types": "^4.2.0", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-waiter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.0.3.tgz", + "integrity": "sha512-JtaY3FxmD+te+KSI2FJuEcfNC9T/DGGVf551babM7fAaXhjJUt7oSYurH1Devxd2+BOSUACCgt3buinx4UnmEA==", + "dependencies": { + "@smithy/abort-controller": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/@aws-sdk/client-s3": { "version": "3.591.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.591.0.tgz", diff --git a/package.json b/package.json index 1fa20fd..0f60398 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "@aws-sdk/client-cloudfront": "3.590.0", "@aws-sdk/client-dynamodb": "3.709.0", "@aws-sdk/client-elastic-beanstalk": "3.590.0", + "@aws-sdk/client-lambda": "3.787.0", "@aws-sdk/client-s3": "3.591.0", "@aws-sdk/client-secrets-manager": "3.758.0", "@aws-sdk/client-sqs": "3.682.0", diff --git a/src/build/next.ts b/src/build/next.ts index d78a403..53df478 100644 --- a/src/build/next.ts +++ b/src/build/next.ts @@ -1,5 +1,6 @@ import childProcess from 'node:child_process' -import fs from 'fs/promises' +import fsPromises from 'fs/promises' +import fs from 'node:fs' import path from 'node:path' import type { PrerenderManifest, RoutesManifest } from 'next/dist/build' import { type ProjectPackager, type ProjectSettings } from '../common/project' @@ -16,6 +17,22 @@ interface BuildAppOptions { export const OUTPUT_FOLDER = 'serverless-next' +export const cleanOutputFolder = () => { + const outputFolderPath = path.join(process.cwd(), OUTPUT_FOLDER) + + fs.rmSync(outputFolderPath, { recursive: true, force: true }) +} + +export const createOutputFolder = () => { + const outputFolderPath = path.join(process.cwd(), OUTPUT_FOLDER) + // clean folder before creating new build output. + cleanOutputFolder() + + fs.mkdirSync(outputFolderPath) + + return outputFolderPath +} + const setNextEnvs = () => { process.env.NEXT_SERVERLESS_DEPLOYING_PHASE = 'true' } @@ -29,19 +46,23 @@ export const buildNext = async (options: BuildOptions) => { const copyAssets = async (outputPath: string, appPath: string, appRelativePath: string) => { // Copying static assets (like js, css, images, .etc) - await fs.cp(path.join(appPath, '.next'), path.join(outputPath, '.next'), { + await fsPromises.cp(path.join(appPath, '.next'), path.join(outputPath, '.next'), { recursive: true }) - await fs.cp( + await fsPromises.cp( path.join(appPath, '.next', 'static'), path.join(outputPath, '.next', 'standalone', appRelativePath, '.next', 'static'), { recursive: true } ) - await fs.cp(path.join(appPath, 'public'), path.join(outputPath, '.next', 'standalone', appRelativePath, 'public'), { - recursive: true - }) + await fsPromises.cp( + path.join(appPath, 'public'), + path.join(outputPath, '.next', 'standalone', appRelativePath, 'public'), + { + recursive: true + } + ) } const getRewritesConfig = (manifestRules: RoutesManifest['rewrites']): NextRewrites => { @@ -86,11 +107,11 @@ export const getNextCachedRoutesConfig = async ( outputPath: string, appRelativePath: string ): Promise<{ cachedRoutesMatchers: string[]; rewritesConfig: NextRewrites; redirectsConfig: NextRedirects }> => { - const prerenderManifestJSON = await fs.readFile( + const prerenderManifestJSON = await fsPromises.readFile( path.join(outputPath, '.next', 'standalone', appRelativePath, '.next', 'prerender-manifest.json'), 'utf-8' ) - const routesManifestJSON = await fs.readFile( + const routesManifestJSON = await fsPromises.readFile( path.join(outputPath, '.next', 'standalone', appRelativePath, '.next', 'routes-manifest.json'), 'utf-8' ) diff --git a/src/cdk/constructs/OriginRequestLambdaEdge.ts b/src/cdk/constructs/OriginRequestLambdaEdge.ts index 34e9a8d..f689b7b 100644 --- a/src/cdk/constructs/OriginRequestLambdaEdge.ts +++ b/src/cdk/constructs/OriginRequestLambdaEdge.ts @@ -6,16 +6,12 @@ import * as logs from 'aws-cdk-lib/aws-logs' import * as iam from 'aws-cdk-lib/aws-iam' import path from 'node:path' import { buildLambda } from '../../common/esbuild' -import { CacheConfig } from '../../types' +import { addOutput } from '../../common/cdk' interface OriginRequestLambdaEdgeProps extends cdk.StackProps { bucketName: string - renderServerDomain: string buildOutputPath: string nodejs?: string - cacheConfig: CacheConfig - bucketRegion?: string - cachedRoutesMatchers: string[] } const NodeJSEnvironmentMapping: Record = { @@ -27,22 +23,12 @@ export class OriginRequestLambdaEdge extends Construct { public readonly lambdaEdge: cloudfront.experimental.EdgeFunction constructor(scope: Construct, id: string, props: OriginRequestLambdaEdgeProps) { - const { bucketName, bucketRegion, renderServerDomain, nodejs, buildOutputPath, cacheConfig, cachedRoutesMatchers } = - props + const { bucketName, nodejs, buildOutputPath } = props super(scope, id) const nodeJSEnvironment = NodeJSEnvironmentMapping[nodejs ?? ''] ?? NodeJSEnvironmentMapping['20'] - const name = 'originRequest' - buildLambda(name, buildOutputPath, { - define: { - 'process.env.S3_BUCKET': JSON.stringify(bucketName), - 'process.env.S3_BUCKET_REGION': JSON.stringify(bucketRegion ?? ''), - 'process.env.EB_APP_URL': JSON.stringify(renderServerDomain), - 'process.env.CACHE_CONFIG': JSON.stringify(cacheConfig), - 'process.env.NEXT_CACHED_ROUTES_MATCHERS': JSON.stringify(cachedRoutesMatchers ?? []) - } - }) + buildLambda('defaultLambdaMock', buildOutputPath) const logGroup = new logs.LogGroup(this, 'OriginRequestLambdaEdgeLogGroup', { logGroupName: `/aws/lambda/${id}-originRequest`, @@ -52,7 +38,7 @@ export class OriginRequestLambdaEdge extends Construct { this.lambdaEdge = new cloudfront.experimental.EdgeFunction(this, 'OriginRequestLambdaEdge', { runtime: nodeJSEnvironment, - code: lambda.Code.fromAsset(path.join(buildOutputPath, 'server-functions', name)), + code: lambda.Code.fromAsset(path.join(buildOutputPath, 'server-functions', 'defaultLambdaMock')), handler: 'index.handler', logGroup }) @@ -65,5 +51,7 @@ export class OriginRequestLambdaEdge extends Construct { }) this.lambdaEdge.addToRolePolicy(policyStatement) + + addOutput(this, `${id}-OriginRequestLambdaEdgeName`, this.lambdaEdge.functionName) } } diff --git a/src/cdk/constructs/RenderServerDistribution.ts b/src/cdk/constructs/RenderServerDistribution.ts index 6e17b62..4375392 100644 --- a/src/cdk/constructs/RenderServerDistribution.ts +++ b/src/cdk/constructs/RenderServerDistribution.ts @@ -79,7 +79,7 @@ export class RenderServerDistribution extends Construct { applicationName: `${id}-eb-app` }) - this.ebInstanceProfileRole = new iam.Role(this, 'EbInstanceProfileRole', { + this.ebInstanceProfileRole = new iam.Role(this, 'ProfileRole', { assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('AWSElasticBeanstalkWebTier')] }) @@ -98,7 +98,7 @@ export class RenderServerDistribution extends Construct { }) ) - this.ebInstanceProfile = new iam.CfnInstanceProfile(this, 'EbInstanceProfile', { + this.ebInstanceProfile = new iam.CfnInstanceProfile(this, 'Profile', { roles: [this.ebInstanceProfileRole.roleName] }) @@ -111,7 +111,7 @@ export class RenderServerDistribution extends Construct { const privateSubnets = this.vpc.selectSubnets({ subnetType: SubnetType.PRIVATE_WITH_EGRESS }).subnetIds this.ebEnv = new elasticbeanstalk.CfnEnvironment(this, 'EbEnv', { - environmentName: `${appName}-eb-env`, + environmentName: `${appName}-env`, applicationName: this.ebApp.applicationName!, solutionStackName: nodeJSEnvironment, optionSettings: [ diff --git a/src/cdk/constructs/RenderWorkerDistribution.ts b/src/cdk/constructs/RenderWorkerDistribution.ts index 03159e1..fd5a846 100644 --- a/src/cdk/constructs/RenderWorkerDistribution.ts +++ b/src/cdk/constructs/RenderWorkerDistribution.ts @@ -152,7 +152,7 @@ export class RenderWorkerDistribution extends Construct { * Create IAM role for EC2 instances * Includes required permissions for Elastic Beanstalk Worker tier */ - this.instanceRole = new iam.Role(this, 'WorkerInstanceRole', { + this.instanceRole = new iam.Role(this, 'InstanceRole', { assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('AWSElasticBeanstalkWorkerTier')] }) @@ -187,7 +187,7 @@ export class RenderWorkerDistribution extends Construct { /** * Create Instance Profile for EC2 instances */ - this.instanceProfile = new iam.CfnInstanceProfile(this, 'WorkerInstanceProfile', { + this.instanceProfile = new iam.CfnInstanceProfile(this, 'Profile', { roles: [this.instanceRole.roleName] }) @@ -210,7 +210,7 @@ export class RenderWorkerDistribution extends Construct { * Create Elastic Beanstalk environment with worker configuration */ this.environment = new elasticbeanstalk.CfnEnvironment(this, 'WorkerEnvironment', { - environmentName: `${appName}-worker-env`, + environmentName: appName, applicationName: this.application.applicationName!, solutionStackName: NODE_VERSIONS[nodejs], tier: { diff --git a/src/cdk/constructs/RevalidateLambda.ts b/src/cdk/constructs/RevalidateLambda.ts index cf5188b..d1479ff 100644 --- a/src/cdk/constructs/RevalidateLambda.ts +++ b/src/cdk/constructs/RevalidateLambda.ts @@ -12,6 +12,7 @@ interface RevalidateLambdaProps extends cdk.StackProps { nodejs?: string sqsRegion: string sqsQueueUrl: string + secretName: string } const NodeJSEnvironmentMapping: Record = { @@ -24,7 +25,7 @@ export class RevalidateLambda extends Construct { public readonly lambdaHttpUrl: lambda.FunctionUrl constructor(scope: Construct, id: string, props: RevalidateLambdaProps) { - const { nodejs, buildOutputPath, sqsRegion, sqsQueueUrl } = props + const { nodejs, buildOutputPath, sqsRegion, sqsQueueUrl, secretName } = props super(scope, id) const nodeJSEnvironment = NodeJSEnvironmentMapping[nodejs ?? ''] ?? NodeJSEnvironmentMapping['20'] @@ -45,7 +46,7 @@ export class RevalidateLambda extends Construct { logGroup, environment: { SQS_AWS_REGION: sqsRegion, - SECRET_ID: 'x-api-key', + SECRET_ID: secretName, SQS_QUEUE_URL: sqsQueueUrl } }) diff --git a/src/cdk/constructs/SecretManagerDistribution.ts b/src/cdk/constructs/SecretManagerDistribution.ts index 02a1c53..5509246 100644 --- a/src/cdk/constructs/SecretManagerDistribution.ts +++ b/src/cdk/constructs/SecretManagerDistribution.ts @@ -1,14 +1,20 @@ import { Construct } from 'constructs' import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager' +interface SecretManagerDistributionProps { + secretName: string +} + export class SecretManagerDistribution extends Construct { public readonly xApiKey: secretsmanager.Secret - constructor(scope: Construct, id: string) { + constructor(scope: Construct, id: string, props: SecretManagerDistributionProps) { super(scope, id) + const { secretName } = props + this.xApiKey = new secretsmanager.Secret(this, 'XApiKey', { - secretName: 'x-api-key' + secretName: secretName }) } } diff --git a/src/cdk/constructs/ViewerRequestLambdaEdge.ts b/src/cdk/constructs/ViewerRequestLambdaEdge.ts index ad7cad0..881e183 100644 --- a/src/cdk/constructs/ViewerRequestLambdaEdge.ts +++ b/src/cdk/constructs/ViewerRequestLambdaEdge.ts @@ -6,15 +6,11 @@ import * as logs from 'aws-cdk-lib/aws-logs' import * as iam from 'aws-cdk-lib/aws-iam' import path from 'node:path' import { buildLambda } from '../../common/esbuild' -import { NextRedirects, NextI18nConfig, NextRewrites } from '../../types' +import { addOutput } from '../../common/cdk' interface ViewerRequestLambdaEdgeProps extends cdk.StackProps { buildOutputPath: string nodejs?: string - redirectsConfig?: NextRedirects - nextI18nConfig?: NextI18nConfig - isTrailingSlashEnabled: boolean - rewritesConfig: NextRewrites } const NodeJSEnvironmentMapping: Record = { @@ -26,20 +22,12 @@ export class ViewerRequestLambdaEdge extends Construct { public readonly lambdaEdge: cloudfront.experimental.EdgeFunction constructor(scope: Construct, id: string, props: ViewerRequestLambdaEdgeProps) { - const { nodejs, buildOutputPath, redirectsConfig, nextI18nConfig, isTrailingSlashEnabled, rewritesConfig } = props + const { nodejs, buildOutputPath } = props super(scope, id) const nodeJSEnvironment = NodeJSEnvironmentMapping[nodejs ?? ''] ?? NodeJSEnvironmentMapping['20'] - const name = 'viewerRequest' - buildLambda(name, buildOutputPath, { - define: { - 'process.env.REDIRECTS': JSON.stringify(redirectsConfig ?? []), - 'process.env.LOCALES_CONFIG': JSON.stringify(nextI18nConfig ?? null), - 'process.env.IS_TRAILING_SLASH_ENABLED': JSON.stringify(isTrailingSlashEnabled), - 'process.env.NEXT_REWRITES_CONFIG': JSON.stringify(rewritesConfig ?? []) - } - }) + buildLambda('defaultLambdaMock', buildOutputPath) const logGroup = new logs.LogGroup(this, 'ViewerRequestLambdaEdgeLogGroup', { logGroupName: `/aws/lambda/${id}-viewerRequest`, @@ -49,7 +37,7 @@ export class ViewerRequestLambdaEdge extends Construct { this.lambdaEdge = new cloudfront.experimental.EdgeFunction(this, 'ViewerRequestLambdaEdge', { runtime: nodeJSEnvironment, - code: lambda.Code.fromAsset(path.join(buildOutputPath, 'server-functions', name)), + code: lambda.Code.fromAsset(path.join(buildOutputPath, 'server-functions', 'defaultLambdaMock')), handler: 'index.handler', logGroup }) @@ -62,5 +50,7 @@ export class ViewerRequestLambdaEdge extends Construct { }) this.lambdaEdge.addToRolePolicy(policyStatement) + + addOutput(this, `${id}-ViewerRequestLambdaEdgeName`, this.lambdaEdge.functionName) } } diff --git a/src/cdk/stacks/NextCloudfrontStack.ts b/src/cdk/stacks/NextCloudfrontStack.ts index 81ddb72..6b08b4b 100644 --- a/src/cdk/stacks/NextCloudfrontStack.ts +++ b/src/cdk/stacks/NextCloudfrontStack.ts @@ -7,7 +7,7 @@ import { ViewerResponseLambdaEdge } from '../constructs/ViewerResponseLambdaEdge import { ViewerRequestLambdaEdge } from '../constructs/ViewerRequestLambdaEdge' import { RevalidateLambda } from '../constructs/RevalidateLambda' import { SecretManagerDistribution } from '../constructs/SecretManagerDistribution' -import { DeployConfig, NextRedirects, NextI18nConfig, NextRewrites } from '../../types' +import { DeployConfig } from '../../types' import * as iam from 'aws-cdk-lib/aws-iam' export interface NextCloudfrontStackProps extends StackProps { @@ -18,13 +18,9 @@ export interface NextCloudfrontStackProps extends StackProps { buildOutputPath: string deployConfig: DeployConfig imageTTL?: number - redirectsConfig?: NextRedirects - nextI18nConfig?: NextI18nConfig - cachedRoutesMatchers: string[] - rewritesConfig: NextRewrites - isTrailingSlashEnabled: boolean sqsQueueUrl: string sqsQueueArn: string + appName: string } export class NextCloudfrontStack extends Stack { @@ -43,32 +39,22 @@ export class NextCloudfrontStack extends Stack { region, deployConfig, imageTTL, - redirectsConfig, - cachedRoutesMatchers, - nextI18nConfig, - rewritesConfig, - isTrailingSlashEnabled, sqsQueueUrl, - sqsQueueArn + sqsQueueArn, + appName } = props + const secretName = `${appName}-x-api-key` + this.originRequestLambdaEdge = new OriginRequestLambdaEdge(this, `${id}-OriginRequestLambdaEdge`, { nodejs, bucketName: staticBucketName, - renderServerDomain, - buildOutputPath, - cacheConfig: deployConfig.cache, - bucketRegion: region, - cachedRoutesMatchers + buildOutputPath }) this.viewerRequestLambdaEdge = new ViewerRequestLambdaEdge(this, `${id}-ViewerRequestLambdaEdge`, { buildOutputPath, - nodejs, - redirectsConfig, - rewritesConfig, - nextI18nConfig, - isTrailingSlashEnabled + nodejs }) this.viewerResponseLambdaEdge = new ViewerResponseLambdaEdge(this, `${id}-ViewerResponseLambdaEdge`, { @@ -80,7 +66,8 @@ export class NextCloudfrontStack extends Stack { nodejs, buildOutputPath, sqsRegion: region, - sqsQueueUrl + sqsQueueUrl, + secretName }) const staticBucket = s3.Bucket.fromBucketAttributes(this, `${id}-StaticAssetsBucket`, { @@ -88,7 +75,9 @@ export class NextCloudfrontStack extends Stack { region }) - const secretManager = new SecretManagerDistribution(this, `${id}-SecretManagerDistribution`) + const secretManager = new SecretManagerDistribution(this, `${id}-SecretManagerDistribution`, { + secretName + }) secretManager.xApiKey.grantRead(this.revalidateLambda.lambda) diff --git a/src/cdk/stacks/NextRenderServerStack.ts b/src/cdk/stacks/NextRenderServerStack.ts index 970f771..ef3de51 100644 --- a/src/cdk/stacks/NextRenderServerStack.ts +++ b/src/cdk/stacks/NextRenderServerStack.ts @@ -60,7 +60,7 @@ export class NextRenderServerStack extends cdk.Stack { isProduction }) - this.renderServer = new RenderServerDistribution(this, `${id}-RenderServer`, { + this.renderServer = new RenderServerDistribution(this, `${id}-render`, { stage, nodejs, isProduction, @@ -74,7 +74,7 @@ export class NextRenderServerStack extends cdk.Stack { healthCheckPath }) - this.renderWorker = new RenderWorkerDistribution(this, `${id}-renderWorker`, { + this.renderWorker = new RenderWorkerDistribution(this, `${id}-worker`, { stage, nodejs, isProduction, diff --git a/src/commands/bootstrap.ts b/src/commands/bootstrap.ts index 1cc1911..0f23a5b 100644 --- a/src/commands/bootstrap.ts +++ b/src/commands/bootstrap.ts @@ -1,34 +1,16 @@ -import childProcess from 'node:child_process' import path from 'node:path' import fs from 'node:fs' import { getAWSCredentials, getSTSIdentity, AWS_EDGE_REGION } from '../common/aws' import { getProjectSettings } from '../common/project' import { OUTPUT_FOLDER } from '../build/next' import { createConfigFile } from './helpers/createConfig' +import bootstrapCdk from './tasks/bootstrapCdk' interface BootstrapProps { region?: string profile?: string } -const runTask = (command: string, env: NodeJS.ProcessEnv) => { - const task = childProcess.spawn(command, { - env, - shell: true, - stdio: 'pipe' - }) - - task.stdout.on('data', (data: Buffer) => { - console.debug(data.toString()) - }) - task.stderr.on('data', (data: Buffer) => { - console.debug(data.toString()) - }) - task.on('exit', (code) => { - console.debug('Bootstrapping CDK exited with code', code) - }) -} - const updateGitIgnore = (projectPath: string) => { const gitIgnorePath = path.join(projectPath, '.gitignore') const gitIgnore = fs.readFileSync(gitIgnorePath, 'utf8') @@ -68,13 +50,10 @@ export const bootstrap = async ({ region, profile }: BootstrapProps) => { // Creates a config file for the user createConfigFile() - runTask(`npx cdk bootstrap aws://${identity.Account}/${awsRegion}`, taskEnv) + bootstrapCdk({ accountId: identity.Account!, region: awsRegion }, taskEnv) // This is required to create AWS CDK resources for edge AWS region. if (awsRegion !== AWS_EDGE_REGION) { - runTask(`npx cdk bootstrap aws://${identity.Account}/${AWS_EDGE_REGION}`, { - ...taskEnv, - AWS_REGION: AWS_EDGE_REGION - }) + bootstrapCdk({ accountId: identity.Account!, region: AWS_EDGE_REGION }, taskEnv) } } diff --git a/src/commands/createApp.ts b/src/commands/createApp.ts new file mode 100644 index 0000000..e883b71 --- /dev/null +++ b/src/commands/createApp.ts @@ -0,0 +1,145 @@ +import type { NextConfig } from 'next/types' +import { getSTSIdentity, getAWSCredentials, AWS_EDGE_REGION } from '../common/aws' +import bootstrapCdk from './tasks/bootstrapCdk' +import { AppStack } from '../common/cdk' +import { NextRenderServerStack, type NextRenderServerStackProps } from '../cdk/stacks/NextRenderServerStack' +import { NextCloudfrontStack, type NextCloudfrontStackProps } from '../cdk/stacks/NextCloudfrontStack' +import loadConfig from './helpers/loadConfig' +import { createOutputFolder, cleanOutputFolder } from '../build/next' +import { getProjectSettings, loadFile } from '../common/project' + +interface CreateAppProps { + region?: string + profile?: string + siteName: string + stage?: string + nodejs?: string + isProduction?: boolean + renderServerInstanceType?: string + renderServerMinInstances?: number + renderServerMaxInstances?: number +} + +export const createApp = async ({ + region, + profile, + siteName, + stage = 'dev', + nodejs, + isProduction, + renderServerInstanceType, + renderServerMaxInstances, + renderServerMinInstances +}: CreateAppProps) => { + try { + const awsRegion = region || process.env.AWS_REGION + + if (!awsRegion) { + throw new Error('AWS Region is required.') + } + + const identity = await getSTSIdentity({ region: awsRegion, profile }) + const credentials = await getAWSCredentials({ region: awsRegion, profile }) + + if (!credentials.accessKeyId || !credentials.secretAccessKey) { + throw new Error('AWS Credentials are required.') + } + + const taskEnv = { + ...process.env, + AWS_ACCESS_KEY_ID: credentials.accessKeyId, + AWS_SECRET_ACCESS_KEY: credentials.secretAccessKey, + AWS_SESSION_TOKEN: credentials.sessionToken, + AWS_REGION: awsRegion, + AWS_PROFILE: profile + } + + bootstrapCdk({ accountId: identity.Account!, region: awsRegion }, taskEnv) + + if (awsRegion !== AWS_EDGE_REGION) { + bootstrapCdk({ accountId: identity.Account!, region: AWS_EDGE_REGION }, taskEnv) + } + + const projectSettings = getProjectSettings(process.cwd()) + + if (!projectSettings) { + throw new Error('Was not able to find project settings.') + } + + // .toLowerCase() is required, since AWS has limitation for resources names + // that name must contain only lowercase characters. + if (/[A-Z]/.test(siteName)) { + console.warn( + 'SiteName should not contain uppercase characters. Updating value to contain only lowercase characters.' + ) + } + const siteNameLowerCased = siteName.toLowerCase() + + const deployConfig = await loadConfig() + + const outputPath = createOutputFolder() + + const nextConfig = (await loadFile(projectSettings.nextConfigPath)) as NextConfig + + const clientAWSCredentials = { + region: awsRegion, + credentials: { + accessKeyId: credentials.accessKeyId, + secretAccessKey: credentials.secretAccessKey, + sessionToken: credentials.sessionToken + } + } + + const nextRenderServerStack = new AppStack( + `${siteNameLowerCased}-server`, + NextRenderServerStack, + { + ...clientAWSCredentials, + buildOutputPath: outputPath, + profile, + stage, + nodejs: nodejs, + isProduction: isProduction, + crossRegionReferences: true, + region: awsRegion, + renderServerInstanceType, + renderServerMaxInstances, + renderServerMinInstances, + healthCheckPath: deployConfig.healthCheckPath, + env: { + region: awsRegion + } + } + ) + const nextRenderServerStackOutput = await nextRenderServerStack.deployStack() + + const nextCloudfrontStack = new AppStack( + `${siteNameLowerCased}-cf`, + NextCloudfrontStack, + { + ...clientAWSCredentials, + profile, + nodejs, + stage, + staticBucketName: nextRenderServerStackOutput.StaticBucketName, + renderServerDomain: nextRenderServerStackOutput.RenderServerDomain, + sqsQueueUrl: nextRenderServerStackOutput.RenderWorkerQueueUrl, + sqsQueueArn: nextRenderServerStackOutput.RenderWorkerQueueArn, + buildOutputPath: outputPath, + crossRegionReferences: true, + region: awsRegion, + deployConfig, + imageTTL: nextConfig.imageTTL, + appName: `${stage}-${siteNameLowerCased}`, + env: { + region: AWS_EDGE_REGION // required since Edge can be deployed only here. + } + } + ) + await nextCloudfrontStack.deployStack() + } catch (err) { + console.error('Failed to create app:', err) + } finally { + cleanOutputFolder() + } +} diff --git a/src/commands/deploy.ts b/src/commands/deploy.ts index 4e64ce9..1a110da 100644 --- a/src/commands/deploy.ts +++ b/src/commands/deploy.ts @@ -1,27 +1,22 @@ import { ElasticBeanstalk } from '@aws-sdk/client-elastic-beanstalk' import { S3 } from '@aws-sdk/client-s3' import { CloudFront } from '@aws-sdk/client-cloudfront' +import { CloudFormationClient, DescribeStacksCommand } from '@aws-sdk/client-cloudformation' +import { LambdaClient, UpdateFunctionCodeCommand } from '@aws-sdk/client-lambda' import type { NextConfig } from 'next/types' import fs from 'node:fs' import childProcess from 'node:child_process' import path from 'node:path' -import { buildApp, OUTPUT_FOLDER } from '../build/next' -import { NextRenderServerStack, type NextRenderServerStackProps } from '../cdk/stacks/NextRenderServerStack' -import { NextCloudfrontStack, type NextCloudfrontStackProps } from '../cdk/stacks/NextCloudfrontStack' +import { buildApp, OUTPUT_FOLDER, createOutputFolder, cleanOutputFolder } from '../build/next' import { getAWSCredentials, uploadFolderToS3, uploadFileToS3, AWS_EDGE_REGION, emptyBucket } from '../common/aws' import { AppStack } from '../common/cdk' import { getProjectSettings, loadFile } from '../common/project' import loadConfig from './helpers/loadConfig' -import { buildRevalidateServer } from '../common/esbuild' +import { buildRevalidateServer, buildLambda } from '../common/esbuild' export interface DeployConfig { siteName: string stage?: string - nodejs?: string - isProduction?: boolean - renderServerInstanceType?: string - renderServerMinInstances?: number - renderServerMaxInstances?: number aws: { region?: string profile?: string @@ -39,32 +34,9 @@ export interface DeployStackProps { } } -const cleanOutputFolder = () => { - const outputFolderPath = path.join(process.cwd(), OUTPUT_FOLDER) - - fs.rmSync(outputFolderPath, { recursive: true, force: true }) -} - -const createOutputFolder = () => { - const outputFolderPath = path.join(process.cwd(), OUTPUT_FOLDER) - // clean folder before creating new build output. - cleanOutputFolder() - - fs.mkdirSync(outputFolderPath) - - return outputFolderPath -} - export const deploy = async (config: DeployConfig) => { try { - const { - siteName, - stage = 'development', - aws, - renderServerInstanceType, - renderServerMaxInstances, - renderServerMinInstances - } = config + const { siteName, stage = 'dev', aws } = config const credentials = await getAWSCredentials({ region: config.aws.region, profile: config.aws.profile }) const region = aws.region || process.env.REGION @@ -102,7 +74,15 @@ export const deploy = async (config: DeployConfig) => { const ebClient = new ElasticBeanstalk(clientAWSCredentials) const s3Client = new S3(clientAWSCredentials) const cloudfrontClient = new CloudFront(clientAWSCredentials) - + const cfClient = new CloudFormationClient(clientAWSCredentials) + const cfEdgeClient = new CloudFormationClient({ + ...clientAWSCredentials, + region: AWS_EDGE_REGION + }) + const lambdaClient = new LambdaClient({ + ...clientAWSCredentials, + region: AWS_EDGE_REGION + }) // .toLowerCase() is required, since AWS has limitation for resources names // that name must contain only lowercase characters. if (/[A-Z]/.test(siteName)) { @@ -110,7 +90,7 @@ export const deploy = async (config: DeployConfig) => { 'SiteName should not contain uppercase characters. Updating value to contain only lowercase characters.' ) } - const siteNameLowerCased = siteName.toLowerCase() + const siteNameLowerCased = `${stage}-${siteName.toLowerCase()}` // Build and zip app. const { cachedRoutesMatchers, rewritesConfig, redirectsConfig } = await buildApp({ @@ -118,57 +98,29 @@ export const deploy = async (config: DeployConfig) => { outputPath }) - const nextRenderServerStack = new AppStack( - `${siteNameLowerCased}-server`, - NextRenderServerStack, - { - ...clientAWSCredentials, - buildOutputPath: outputPath, - profile: config.aws.profile, - stage, - nodejs: config.nodejs, - isProduction: config.isProduction, - crossRegionReferences: true, - region, - renderServerInstanceType, - renderServerMaxInstances, - renderServerMinInstances, - healthCheckPath: deployConfig.healthCheckPath, - env: { - region - } - } - ) + const nextRenderStackOutput = await cfClient + .send(new DescribeStacksCommand({ StackName: `${siteNameLowerCased}-server` })) + .then((r) => r?.Stacks?.[0]) - const nextRenderServerStackOutput = await nextRenderServerStack.deployStack() + if (!nextRenderStackOutput) { + throw new Error( + `Stack ${siteNameLowerCased}-server not found. Please make sure that app was created and you run createApp command.` + ) + } - const nextCloudfrontStack = new AppStack( - `${siteNameLowerCased}-cf`, - NextCloudfrontStack, - { - ...clientAWSCredentials, - profile: config.aws.profile, - nodejs: config.nodejs, - staticBucketName: nextRenderServerStackOutput.StaticBucketName, - renderServerDomain: nextRenderServerStackOutput.RenderServerDomain, - sqsQueueUrl: nextRenderServerStackOutput.RenderWorkerQueueUrl, - sqsQueueArn: nextRenderServerStackOutput.RenderWorkerQueueArn, - buildOutputPath: outputPath, - crossRegionReferences: true, - region, - deployConfig, - imageTTL: nextConfig.imageTTL, - nextI18nConfig, - redirectsConfig, - cachedRoutesMatchers, - rewritesConfig, - isTrailingSlashEnabled, - env: { - region: AWS_EDGE_REGION // required since Edge can be deployed only here. - } - } - ) - const nextCloudfrontStackOutput = await nextCloudfrontStack.deployStack() + const nextRenderStackOutputVariables = AppStack.transformStackOutput(nextRenderStackOutput) + + const nextCloudFrontStackOutput = await cfEdgeClient + .send(new DescribeStacksCommand({ StackName: `${siteNameLowerCased}-cf` })) + .then((r) => r?.Stacks?.[0]) + + if (!nextCloudFrontStackOutput) { + throw new Error( + `Stack ${siteNameLowerCased}-cf not found. Please make sure that app was created and you run createApp command.` + ) + } + + const nextCloudFrontStackOutputVariables = AppStack.transformStackOutput(nextCloudFrontStackOutput) const now = Date.now() const archivedRenderServerFolderName = `${OUTPUT_FOLDER}-render-server-v${now}.zip` @@ -203,80 +155,122 @@ export const deploy = async (config: DeployConfig) => { } ) - // prune static bucket before upload - await emptyBucket(s3Client, nextRenderServerStackOutput.StaticBucketName) - - await uploadFolderToS3(s3Client, { - Bucket: nextRenderServerStackOutput.StaticBucketName, - Key: '_next/static', - folderRootPath: path.join(outputPath, '.next', 'static') - }) - - await uploadFolderToS3(s3Client, { - Bucket: nextRenderServerStackOutput.StaticBucketName, - Key: 'public', - folderRootPath: path.join( - outputPath, - '.next', - 'standalone', - path.relative(projectSettings.root, projectSettings.projectPath), - 'public' - ) - }) - - // upload code version to bucket. - await uploadFileToS3(s3Client, { - Bucket: nextRenderServerStackOutput.RenderServerVersionsBucketName, - Key: `${versionLabel}.zip`, - Body: fs.readFileSync(buildOutputRenderServerPathArchived) - }) - - await ebClient.createApplicationVersion({ - ApplicationName: nextRenderServerStackOutput.RenderServerApplicationName, - VersionLabel: versionLabel, - SourceBundle: { - S3Bucket: nextRenderServerStackOutput.RenderServerVersionsBucketName, - S3Key: `${versionLabel}.zip` - } - }) - - await ebClient.updateEnvironment({ - ApplicationName: nextRenderServerStackOutput.RenderServerApplicationName, - EnvironmentName: nextRenderServerStackOutput.RenderServerEnvironmentName, - VersionLabel: versionLabel - }) - - // upload code version to bucket. - await uploadFileToS3(s3Client, { - Bucket: nextRenderServerStackOutput.RenderWorkerVersionsBucketName, - Key: `${versionLabel}.zip`, - Body: fs.readFileSync(buildOutputRenderWorkerPathArchived) - }) + // build edge lambdas code + await Promise.all([ + buildLambda('originRequest', outputPath, { + define: { + 'process.env.S3_BUCKET': JSON.stringify(nextRenderStackOutputVariables.StaticBucketName), + 'process.env.S3_BUCKET_REGION': JSON.stringify(region), + 'process.env.EB_APP_URL': JSON.stringify(nextRenderStackOutputVariables.RenderServerDomain), + 'process.env.CACHE_CONFIG': JSON.stringify(deployConfig.cache), + 'process.env.NEXT_CACHED_ROUTES_MATCHERS': JSON.stringify(cachedRoutesMatchers ?? []) + } + }), + buildLambda('viewerRequest', outputPath, { + define: { + 'process.env.REDIRECTS': JSON.stringify(redirectsConfig ?? []), + 'process.env.LOCALES_CONFIG': JSON.stringify(nextI18nConfig ?? null), + 'process.env.IS_TRAILING_SLASH_ENABLED': JSON.stringify(isTrailingSlashEnabled), + 'process.env.NEXT_REWRITES_CONFIG': JSON.stringify(rewritesConfig ?? []) + } + }) + ]) - await ebClient.createApplicationVersion({ - ApplicationName: nextRenderServerStackOutput.RenderWorkerApplicationName, - VersionLabel: versionLabel, - SourceBundle: { - S3Bucket: nextRenderServerStackOutput.RenderWorkerVersionsBucketName, - S3Key: `${versionLabel}.zip` - } - }) + // zip lambdas + childProcess.execSync(`cd ${outputPath}/server-functions && zip -r originRequest.zip ./originRequest`) + childProcess.execSync(`cd ${outputPath}/server-functions && zip -r viewerRequest.zip ./viewerRequest`) - await ebClient.updateEnvironment({ - ApplicationName: nextRenderServerStackOutput.RenderWorkerApplicationName, - EnvironmentName: nextRenderServerStackOutput.RenderWorkerEnvironmentName, - VersionLabel: versionLabel, - OptionSettings: [ - { - Namespace: 'aws:elasticbeanstalk:application:environment', - OptionName: 'CLOUDFRONT_DISTRIBUTION_ID', - Value: nextCloudfrontStackOutput.CloudfrontDistributionId! + // prune static bucket before upload + await emptyBucket(s3Client, nextRenderStackOutputVariables.StaticBucketName) + + // upload artifacts to s3 + await Promise.all([ + uploadFolderToS3(s3Client, { + Bucket: nextRenderStackOutputVariables.StaticBucketName, + Key: '_next/static', + folderRootPath: path.join(outputPath, '.next', 'static') + }), + uploadFolderToS3(s3Client, { + Bucket: nextRenderStackOutputVariables.StaticBucketName, + Key: 'public', + folderRootPath: path.join( + outputPath, + '.next', + 'standalone', + path.relative(projectSettings.root, projectSettings.projectPath), + 'public' + ) + }), + uploadFileToS3(s3Client, { + Bucket: nextRenderStackOutputVariables.RenderServerVersionsBucketName, + Key: `${versionLabel}.zip`, + Body: fs.readFileSync(buildOutputRenderServerPathArchived) + }), + uploadFileToS3(s3Client, { + Bucket: nextRenderStackOutputVariables.RenderWorkerVersionsBucketName, + Key: `${versionLabel}.zip`, + Body: fs.readFileSync(buildOutputRenderWorkerPathArchived) + }) + ]) + + // create new applications versions + await Promise.all([ + ebClient.createApplicationVersion({ + ApplicationName: nextRenderStackOutputVariables.RenderServerApplicationName, + VersionLabel: versionLabel, + SourceBundle: { + S3Bucket: nextRenderStackOutputVariables.RenderServerVersionsBucketName, + S3Key: `${versionLabel}.zip` } - ] - }) + }), + ebClient.createApplicationVersion({ + ApplicationName: nextRenderStackOutputVariables.RenderWorkerApplicationName, + VersionLabel: versionLabel, + SourceBundle: { + S3Bucket: nextRenderStackOutputVariables.RenderWorkerVersionsBucketName, + S3Key: `${versionLabel}.zip` + } + }) + ]) + + // update environments with new versions + await Promise.all([ + ebClient.updateEnvironment({ + ApplicationName: nextRenderStackOutputVariables.RenderServerApplicationName, + EnvironmentName: nextRenderStackOutputVariables.RenderServerEnvironmentName, + VersionLabel: versionLabel + }), + ebClient.updateEnvironment({ + ApplicationName: nextRenderStackOutputVariables.RenderWorkerApplicationName, + EnvironmentName: nextRenderStackOutputVariables.RenderWorkerEnvironmentName, + VersionLabel: versionLabel, + OptionSettings: [ + { + Namespace: 'aws:elasticbeanstalk:application:environment', + OptionName: 'CLOUDFRONT_DISTRIBUTION_ID', + Value: nextCloudFrontStackOutputVariables.CloudfrontDistributionId! + } + ] + }) + ]) + + await Promise.all([ + lambdaClient.send( + new UpdateFunctionCodeCommand({ + FunctionName: nextCloudFrontStackOutputVariables.OriginRequestLambdaEdgeName, + ZipFile: new Uint8Array(fs.readFileSync(path.join(outputPath, 'server-functions', 'originRequest.zip'))) + }) + ), + lambdaClient.send( + new UpdateFunctionCodeCommand({ + FunctionName: nextCloudFrontStackOutputVariables.ViewerRequestLambdaEdgeName, + ZipFile: new Uint8Array(fs.readFileSync(path.join(outputPath, 'server-functions', 'viewerRequest.zip'))) + }) + ) + ]) await cloudfrontClient.createInvalidation({ - DistributionId: nextCloudfrontStackOutput.CloudfrontDistributionId!, + DistributionId: nextCloudFrontStackOutputVariables.CloudfrontDistributionId!, InvalidationBatch: { CallerReference: `deploy-cache-invalidation-${now}`, Paths: { diff --git a/src/commands/helpers/runChildTask.ts b/src/commands/helpers/runChildTask.ts new file mode 100644 index 0000000..56ee615 --- /dev/null +++ b/src/commands/helpers/runChildTask.ts @@ -0,0 +1,21 @@ +import childProcess from 'node:child_process' + +const runChildTask = (command: string, env: NodeJS.ProcessEnv) => { + const task = childProcess.spawn(command, { + env, + shell: true, + stdio: 'pipe' + }) + + task.stdout.on('data', (data: Buffer) => { + console.debug(data.toString()) + }) + task.stderr.on('data', (data: Buffer) => { + console.debug(data.toString()) + }) + task.on('exit', (code) => { + console.debug('Bootstrapping CDK exited with code', code) + }) +} + +export default runChildTask diff --git a/src/commands/index.ts b/src/commands/index.ts index 9e707bb..891b2bd 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -3,6 +3,7 @@ import yargs from 'yargs' import { hideBin } from 'yargs/helpers' import { deploy } from './deploy' import { bootstrap } from './bootstrap' +import { createApp } from './createApp' interface CLIOptions { siteName: string @@ -38,15 +39,14 @@ cli.command( cli .command( - 'deploy', - 'app deployment', + 'createApp', + 'creates application stack', () => {}, async (argv) => { const { - siteName, - stage, - region, profile, + region, + siteName, nodejs, production, renderServerInstanceType, @@ -54,18 +54,15 @@ cli renderServerMaxInstances } = argv - await deploy({ + await createApp({ + profile, + region, siteName, - stage, nodejs, isProduction: production, renderServerInstanceType, renderServerMinInstances, - renderServerMaxInstances, - aws: { - region, - profile - } + renderServerMaxInstances }) } ) @@ -99,5 +96,33 @@ cli describe: 'Set max render server instances. Default is 2.' }) +cli + .command( + 'deploy', + 'app deployment', + () => {}, + async (argv) => { + const { siteName, stage, region, profile } = argv + + await deploy({ + siteName, + stage, + aws: { + region, + profile + } + }) + } + ) + .option('siteName', { + type: 'string', + requiresArg: true, + describe: 'The name is used to create CDK stack and components.' + }) + .option('stage', { + type: 'string', + describe: 'The stage of the app, defaults to production' + }) + cli.help() cli.parse() diff --git a/src/commands/tasks/bootstrapCdk.ts b/src/commands/tasks/bootstrapCdk.ts new file mode 100644 index 0000000..00f29b7 --- /dev/null +++ b/src/commands/tasks/bootstrapCdk.ts @@ -0,0 +1,15 @@ +import runChildTask from '../helpers/runChildTask' + +interface BootstrapCdkProps { + accountId: string + region: string +} + +const bootstrapCdk = ({ region, accountId }: BootstrapCdkProps, env: NodeJS.ProcessEnv) => { + runChildTask(`npx cdk bootstrap aws://${accountId}/${region}`, { + ...env, + AWS_REGION: region + }) +} + +export default bootstrapCdk diff --git a/src/common/cdk.ts b/src/common/cdk.ts index 0fb0572..fbcbf71 100644 --- a/src/common/cdk.ts +++ b/src/common/cdk.ts @@ -10,10 +10,10 @@ import { waitUntilStackCreateComplete, waitUntilStackDeleteComplete, waitUntilStackUpdateComplete, - GetTemplateCommand + GetTemplateCommand, + Stack } from '@aws-sdk/client-cloudformation' import path from 'node:path' -import isEqual from 'lodash/isEqual' import { getCDKAssetsPublisher } from './aws' @@ -64,11 +64,6 @@ export class AppStack { public static CLOUDFORMATION_STACK_WAIT_TIME_SEC = 30 * 60 // 30 minutes - public describeCurrentStack = async () => { - const command = new DescribeStacksCommand({ StackName: this.stackName }) - - return this.cfClient.send(command).then((r) => r?.Stacks?.[0]) - } public getCurrentStackTemplate = async () => { const command = new GetTemplateCommand({ StackName: this.stackName }) @@ -76,15 +71,15 @@ export class AppStack { return response.TemplateBody || '' } - public checkIfStackExists = async () => { + public describeCurrentStack = async () => { try { - const res = await this.describeCurrentStack() + const command = new DescribeStacksCommand({ StackName: this.stackName }) - return !!res + return await this.cfClient.send(command).then((r) => r?.Stacks?.[0]) } catch (err) { if (err instanceof CloudFormationServiceException) { if (err.name === 'ValidationError') { - return false + return null } } @@ -132,35 +127,35 @@ export class AppStack { ) } + public static transformStackOutput = (stack?: Stack | null) => { + return (stack?.Outputs ?? []).reduce((prev: Record, curr) => { + const key = curr.ExportName!.split('-').pop()! + + return { + ...prev, + [key]: curr.OutputValue! + } + }, {}) + } + public deployStack = async (): Promise> => { const { buildOutputPath, region, profile } = this.options - const assetsPublisher = getCDKAssetsPublisher(path.join(buildOutputPath, `${this.stackName}.assets.json`), { region: region, profile: profile }) await assetsPublisher.publish() - const ifStackExists = await this.checkIfStackExists() + let currentStackInfo = await this.describeCurrentStack() - if (ifStackExists) { - const currentTemplate = await this.getCurrentStackTemplate() - if (!isEqual(JSON.parse(currentTemplate), this.stackTemplate)) { - await this.updateStack() - } - } else { - await this.createStack() + if (currentStackInfo) { + return AppStack.transformStackOutput(currentStackInfo) } - const currentStackInfo = await this.describeCurrentStack() + await this.createStack() - return (currentStackInfo?.Outputs ?? []).reduce((prev: Record, curr) => { - const key = curr.ExportName!.split('-').pop()! + currentStackInfo = await this.describeCurrentStack() - return { - ...prev, - [key]: curr.OutputValue! - } - }, {}) + return AppStack.transformStackOutput(currentStackInfo) } } diff --git a/src/lambdas/defaultLambdaMock.ts b/src/lambdas/defaultLambdaMock.ts new file mode 100644 index 0000000..9794f86 --- /dev/null +++ b/src/lambdas/defaultLambdaMock.ts @@ -0,0 +1,11 @@ +import type { CloudFrontRequestCallback, Context, CloudFrontResponseEvent } from 'aws-lambda' + +export const handler = async ( + event: CloudFrontResponseEvent, + _context: Context, + callback: CloudFrontRequestCallback +) => { + const request = event.Records[0].cf.request + + return callback(null, request) +}